pusher-js
Version:
Pusher Channels JavaScript library for browsers, React Native, NodeJS and web workers
370 lines (306 loc) • 11.9 kB
JavaScript
var TestEnv = require('testenv');
var Mocks = require('mocks');
var Util = require('core/util').default;
var HTTPSocket = require('core/http/http_socket').default;
var Runtime = require('runtime').default;
describe("HTTP.Socket", function() {
var onOpen, onMessage, onActivity, onClose;
var hooks;
var lastXHR;
var socket;
var HTTPFactory;
beforeEach(function() {
HTTPFactory = require('runtime').default.HTTPFactory;
jasmine.clock().install();
spyOn(HTTPFactory, "createXHR").and.callFake(function(method, url) {
lastXHR = Mocks.getHTTPRequest(method, url);
return lastXHR;
});
if (TestEnv === "web") {
spyOn(HTTPFactory, "createXDR").and.callFake(function(method, url) {
lastXHR = Mocks.getHTTPRequest(method, url);
return lastXHR;
});
}
spyOn(Runtime, "isXHRSupported").and.returnValue(true);
if (TestEnv === "web") spyOn(Runtime, "isXDRSupported").and.returnValue(false);
hooks = {
getReceiveURL: jasmine.createSpy().and.callFake(function(url, session) {
return url.base + "/" + session + url.queryString;
}),
onHeartbeat: jasmine.createSpy(),
sendHeartbeat: jasmine.createSpy(),
onFinished: jasmine.createSpy()
};
socket = new HTTPSocket(hooks, "http://example.com/pusher");
onOpen = jasmine.createSpy("onOpen");
onMessage = jasmine.createSpy("onMessage");
onActivity = jasmine.createSpy("onActivity");
onClose = jasmine.createSpy("onClose");
socket.onopen = onOpen;
socket.onmessage = onMessage;
socket.onactivity = onActivity;
socket.onclose = onClose;
});
afterEach(function() {
socket.close();
jasmine.clock().uninstall();
});
it("should use XHR if it's supported", function() {
Runtime.isXHRSupported.and.returnValue(true);
if (TestEnv === "web" ) Runtime.isXDRSupported.and.returnValue(false);
var socket = new HTTPSocket(hooks, "http://example.com");
expect(HTTPFactory.createXHR).toHaveBeenCalled();
socket.close();
});
if (TestEnv === "web") {
it("should use XDR if it's supported", function() {
Runtime.isXHRSupported.and.returnValue(false);
Runtime.isXDRSupported.and.returnValue(true);
var socket = new HTTPSocket(hooks, "http://example.com");
expect(HTTPFactory.createXDR).toHaveBeenCalled();
socket.close();
});
}
it("should send a POST request to the URL constructed with getReceiveURL", function() {
var socket = new HTTPSocket(hooks, "http://example.com/x?arg=val");
expect(lastXHR.method).toEqual("POST");
expect(lastXHR.url).toMatch(
/^http:\/\/example\.com\/x\/[0-9]{1,3}\/[0-9a-z]{8}\?arg=val&t=[0-9]+&n=[0-9]+$/
);
socket.close();
});
it("should start streaming immediately", function() {
expect(lastXHR.start).toHaveBeenCalled();
});
it("should start streaming from different URLs", function() {
var socket1 = new HTTPSocket(hooks, "http://example.com");
var url1 = lastXHR.url;
var socket2 = new HTTPSocket(hooks, "http://example.com");
var url2 = lastXHR.url;
expect(url1).not.toEqual(url2);
});
describe("on HTTP request start exception", function() {
var onError;
var stream;
beforeEach(function() {
// close the default socket
socket.close();
HTTPFactory.createXHR.and.callFake(function() {
stream = Mocks.getHTTPRequest();
stream.start.and.throwError("start exception");
return stream;
});
onError = jasmine.createSpy("onError");
onClose = jasmine.createSpy("onClose");
socket = new HTTPSocket(hooks, "http://example.com");
socket.onerror = onError;
socket.onclose = onClose;
});
it("should raise an error", function() {
expect(onError).not.toHaveBeenCalled();
jasmine.clock().tick(1);
expect(onError).toHaveBeenCalledWith(new Error('start exception'));
});
it("should close itself with code 1006", function() {
expect(onClose).not.toHaveBeenCalled();
jasmine.clock().tick(1);
expect(onClose).toHaveBeenCalledWith({
code: 1006,
reason: "Could not start streaming",
wasClean: false
});
});
it("should unbind all listeners from the stream", function() {
spyOn(stream, "unbind_all");
jasmine.clock().tick(1);
expect(stream.unbind_all).toHaveBeenCalled();
});
it("should close the stream", function() {
jasmine.clock().tick(1);
expect(stream.close).toHaveBeenCalled();
});
});
describe("#ping", function() {
it("should call the sendHeartbeat hook", function() {
expect(hooks.sendHeartbeat).not.toHaveBeenCalledWith(socket);
socket.ping();
expect(hooks.sendHeartbeat).toHaveBeenCalledWith(socket);
});
});
describe("#close", function() {
it("should close itself with supplied code and reason", function() {
socket.close(2013, "test reason");
expect(onClose).toHaveBeenCalledWith({
code: 2013,
reason: "test reason",
wasClean: true
});
});
it("should unbind all listeners from the stream", function() {
spyOn(lastXHR, "unbind_all");
socket.close(2013, "test reason");
expect(lastXHR.unbind_all).toHaveBeenCalled();
});
it("should close the stream", function() {
socket.close(2013, "test reason");
expect(lastXHR.close).toHaveBeenCalled();
});
});
describe("before connecting", function() {
describe("#send", function() {
it("should return false", function() {
expect(socket.send("test")).toEqual(false);
});
it("should not trigger any HTTP requests", function() {
expect(HTTPFactory.createXHR.calls.count()).toEqual(1);
socket.send("test");
expect(HTTPFactory.createXHR.calls.count()).toEqual(1);
});
});
});
describe("when connecting", function() {
describe("before the open frame", function() {
it("should ignore heartbeat frames", function() {
var requestCount = HTTPFactory.createXHR.calls.count();
lastXHR.emit("chunk", { status: 200, data: "hhhhhhhhhhh" });
expect(HTTPFactory.createXHR.calls.count()).toEqual(requestCount);
});
});
describe("on the open frame", function() {
it("should emit an 'open' event", function() {
lastXHR.emit("chunk", { status: 200, data: "o" });
expect(onOpen).toHaveBeenCalled();
});
});
});
describe("after opening without hostname binding", function() {
beforeEach(function() {
lastXHR.emit("chunk", { status: 200, data: "hhhhhhhhhhh" });
lastXHR.emit("chunk", { status: 200, data: "o" });
});
describe("#send", function() {
it("should send an HTTP request to a correct URL", function() {
expect(HTTPFactory.createXHR.calls.count()).toEqual(1);
socket.send("test");
expect(HTTPFactory.createXHR.calls.count()).toEqual(2);
expect(lastXHR.method).toEqual("POST");
expect(lastXHR.url).toMatch(
new RegExp(
"^http:\\/\\/example\\.com\\/pusher\\/" +
socket.session +
"\\/xhr_send\\?t=[0-9]+&n=[0-9]+$"
)
);
});
it("should send a string payload", function() {
var data = "test";
socket.send(data);
expect(JSON.parse(lastXHR.start.calls.first().args[0])).toEqual([data]);
});
it("should send an array payload", function() {
var data = ["test", 1, { foo: "bar" }];
socket.send(data);
expect(JSON.parse(lastXHR.start.calls.first().args[0])).toEqual([data]);
});
it("should send an object payload", function() {
var data = { num: 1, str: "data", arr: [1, 2, 3]};
socket.send(data);
expect(JSON.parse(lastXHR.start.calls.first().args[0])).toEqual([data]);
});
it("should return true if the request did not raise an exception", function() {
expect(socket.send("test")).toBe(true);
});
it("should return false if the request raised an exception", function() {
HTTPFactory.createXHR.and.callFake(function() {
var request = Mocks.getHTTPRequest();
request.start.and.throwError("exception");
return request;
});
expect(socket.send("test")).toBe(false);
});
it("should send messages to different URLs", function() {
socket.send("test");
var url1 = lastXHR.url;
socket.send("test");
var url2 = lastXHR.url;
expect(url1).not.toEqual(url2);
});
});
describe("on a single-message frame", function() {
it("should emit the message if status is 200", function() {
lastXHR.emit("chunk", { status: 200, data: 'm{"foo": 123}' });
expect(onMessage).toHaveBeenCalledWith({ data: { foo: 123 } });
});
it("should not emit the message if status is not 200", function() {
lastXHR.emit("chunk", { status: 400, data: 'm[]' });
expect(onMessage).not.toHaveBeenCalled();
});
});
describe("on a multi-message frame", function() {
it("should emit all messages if status is 200", function() {
lastXHR.emit("chunk", { status: 200, data: 'a[1,2,3]' });
expect(onMessage.calls.first().args[0]).toEqual({ data: 1 });
expect(onMessage.calls.argsFor(1)[0]).toEqual({ data: 2 });
expect(onMessage.calls.argsFor(2)[0]).toEqual({ data: 3 });
});
it("should not emit any messages if status is not 200", function() {
lastXHR.emit("chunk", { status: 400, data: 'a[1,2,3]' });
expect(onMessage).not.toHaveBeenCalled();
});
});
describe("on a heartbeat frame", function() {
it("should call the onHeartbeat hook", function() {
lastXHR.emit("chunk", { status: 200, data: 'h' });
expect(hooks.onHeartbeat).toHaveBeenCalledWith(socket);
});
});
describe("on a close frame", function() {
it("should call onclose with the code and the reason", function() {
lastXHR.emit("chunk", { status: 200, data: 'c[1234, "testing"]' });
expect(onClose).toHaveBeenCalledWith({
code: 1234,
reason: "testing",
wasClean: true
});
});
it("should unbind all listeners from the stream", function() {
spyOn(lastXHR, "unbind_all");
lastXHR.emit("chunk", { status: 200, data: 'c[1234, "testing"]' });
expect(lastXHR.unbind_all).toHaveBeenCalled();
});
it("should close the stream", function() {
lastXHR.emit("chunk", { status: 200, data: 'c[1234, "testing"]' });
expect(lastXHR.close).toHaveBeenCalled();
});
});
describe("on any chunk", function() {
it("should call onactivity", function() {
expect(onActivity).not.toHaveBeenCalled();
lastXHR.emit("chunk", { status: 200, data: "x" });
expect(onActivity).toHaveBeenCalled();
});
});
});
describe("after opening with hostname binding", function() {
beforeEach(function() {
lastXHR.emit("chunk", { status: 200, data: "hhhhhhhhhhhhhhhhh" });
lastXHR.emit("chunk", { status: 200, data: 'o{"hostname":"example.org"}'});
});
describe("#send", function() {
it("should send an HTTP request to the updated host", function() {
socket.send("test");
// opening the connection sends the first request
expect(HTTPFactory.createXHR.calls.count()).toEqual(2);
expect(lastXHR.method).toEqual("POST");
expect(lastXHR.url).toMatch(
new RegExp(
"^http:\\/\\/example\\.org\\/pusher\\/" +
socket.session +
"\\/xhr_send\\?t=[0-9]+&n=[0-9]+$"
)
);
});
});
});
});