UNPKG

browserchannel

Version:
393 lines (312 loc) 13.3 kB
# # Tests for the bare BrowserChannel client. # # Run them by first launching # # % coffee test/support/runserver.coffee # # ... Then browsing to localhost:4321 in your browser or running: # # % mocha test/bcsocket.coffee # # from the command line. You should do both kinds of testing before pushing. # # # These tests are pretty simple and primitive. The reality is, google's browserchannel # client library is pretty bloody well tested. (I'm not interested in rewriting that test suite) # # However, its important to do some sanity checks on the exported browserchannel bits to # make sure closure isn't doing anything wacky. Also this acts as a nice little integration # test for the server, _and_ its useful to make sure that all the browsers node-browserchannel # supports are behaving themselves. # # Oh yeah, and these tests will be run on the nodejs version of browserchannel, which has # a lot of silly little parts. # # These tests will also be useful if the browserchannel protocol ever changes. # # Interestingly, most of the benefits of this test suite come from a single test (really, any # test). If any test passes, they'll all probably pass. # # # ## Known Issues # # There's three weird issues with this test suite: # # - Sometimes (maybe, 1 in 10) times the test is run from nodejs, it dies in a weird inconsistant # state. # - After a test run, 4 sessions are allowed to time out by the server. (Its odd because I'm calling # disconnect() in tearDown). # assert = require 'assert' if typeof window is 'undefined' try require('./runserver').listen 4321 catch e console.warn e.stack bc = require '..' # If coffeescript declares a variable called 'BCSocket' here, it will shadow # the BCSocket variable that is already defined in the browser. Doing it this # way is pretty ugly, but it works and the ugliness is constrained to a test. global.BCSocket = bc.BCSocket bc.setDefaultLocation 'http://localhost:4321' # This is a little logging function for old IE. It adds a log() function which # simply appends HTML messages to the document. window?.log = log = (str) -> div = document.createElement 'div' div.innerHTML = str document.body.appendChild div suite 'bcsocket', -> # IE6 takes about 12 seconds to do the large stress test @timeout 20000 teardown (callback) -> if @socket? and @socket.readyState isnt BCSocket.CLOSED @socket.onclose = -> callback() @socket.close() @socket = null else callback() # These match websocket codes test 'states and errors are mapped', -> assert.strictEqual BCSocket.CONNECTING, 0 assert.strictEqual BCSocket.OPEN, 1 assert.strictEqual BCSocket.CLOSING, 2 assert.strictEqual BCSocket.CLOSED, 3 assert.strictEqual BCSocket.prototype.CONNECTING, 0 assert.strictEqual BCSocket.prototype.OPEN, 1 assert.strictEqual BCSocket.prototype.CLOSING, 2 assert.strictEqual BCSocket.prototype.CLOSED, 3 assert.strictEqual BCSocket.canSendWhileConnecting, true assert.strictEqual BCSocket.prototype.canSendWhileConnecting, true assert.strictEqual BCSocket.canSendJSON, true assert.strictEqual BCSocket.prototype.canSendJSON, true # Can we connect to the server? test 'connect', (done) -> @socket = new BCSocket '/notify' assert.strictEqual @socket.readyState, BCSocket.CONNECTING openCalled = false @socket.onopen = => assert.strictEqual @socket.readyState, BCSocket.OPEN openCalled = true @socket.onerror = (reason) -> throw new Error reason @socket.onmessage = (message) -> assert.deepEqual message.data, {appVersion: null} assert.ok openCalled done() # The socket interface exposes browserchannel's app version thingy through # option arguments test 'connect sends app version', (done) -> @socket = new BCSocket '/notify', appVersion: 321 @socket.onmessage = (message) -> assert.deepEqual message.data, {appVersion:321} done() # BrowserChannel's native send method sends a string->string map. # # I want to test that I can send and recieve messages both before we've connected # (they should be sent as soon as the connection is established) and after the # connection has opened normally. suite 'send maps', -> # I'll throw some random unicode characters in here just to make sure... data = {'foo': 'bar', 'zot': '(◔ ◡ ◔)'} m = (callback) -> (done) -> @socket = new BCSocket '/echomap', appVersion: 321 @socket.onmessage = (message) -> assert.deepEqual message.data, data done() callback.apply this test 'immediately', m -> @socket.sendMap data test 'after we have connected', m -> @socket.onopen = => @socket.sendMap data # I'll also test the normal send method. This is pretty much the same as above, whereby # I'll do the test two ways. suite 'can send and receive', -> test 'unicode', (done) -> # Vim gets formatting errors with the cat face glyph here. Sad. data = '⚗☗⚑☯' @socket = new BCSocket '/echo', appVersion: 321 @socket.onmessage = (message) -> assert.deepEqual message.data, data done() @socket.onopen = => @socket.send data suite 'JSON messages', -> # Vim gets formatting errors with the cat face glyph here. Sad. data = [null, 1.5, "hi", {}, [1,2,3], '⚗☗⚑☯'] m = (callback) -> (done) -> # Using the /echo server not /echomap @socket = new BCSocket '/echo', appVersion: 321 @socket.onmessage = (message) -> assert.deepEqual message.data, data done() callback.apply this test 'immediately', m -> # Calling send() instead of sendMap() @socket.send data test 'after we have connected', m -> @socket.onopen = => @socket.send data suite 'string messages', -> # Vim gets formatting errors with the cat face glyph here. Sad. data = ["hi", "", " ", "\n", "\t", '⚗☗⚑☯', "\u2028 \u2029", ('x' for [1..1000]).join()] # I'm going to send each message in the array in sequence. We should get # them back in the same sequence. pos = 0 m = (callback) -> (done) -> # Using the /echo server not /echomap @socket = new BCSocket '/echo', appVersion: 321 @socket.onmessage = (message) -> assert pos < data.length assert.deepEqual message.data, data[pos++] done() if pos == data.length callback.apply this test 'immediately', m -> pos = 0 # Calling send() instead of sendMap() @socket.send str for str in data return test 'after we have connected', m -> pos = 0 @socket.onopen = => @socket.send str for str in data return # This is a little stress test to make sure I haven't missed anything. # Sending and recieving this much data pushes the client to use multiple # forward channel connections. It doesn't use multiple backchannel # connections - I should probably put some logic there whereby I close the # backchannel after awhile. test 'Lots of data', (done) -> num = 5000 @socket = new BCSocket '/echomap' received = 0 @socket.onmessage = (message) -> assert.equal message.data.data, received received++ done() if received == num setTimeout => # Maps aren't actual JSON. They're just key-value pairs. I don't need to # encode i as a string here, but thats now its sent anyway. @socket.sendMap {data:"#{i}", juuuuuuuuuuuuuuuuunnnnnnnnnk:'waaaazzzzzzuuuuuppppppp'} for i in [0...num] , 0 # I have 2 disconnect servers which have slightly different timing regarding # when they call close() on the session. If close is called immediately, the # initial bind request is rejected with a 403 response, before the client # connects. test 'disconnecting immediately results in REQUEST_FAILED and a 403', (done) -> @socket = new BCSocket '/dc1', reconnect: no @socket.onopen = -> throw new Error 'Socket should not have opened' onErrorCalled = no @socket.onerror = (message, errCode) => assert.strictEqual message, 'Request failed' assert.strictEqual errCode, 2 onErrorCalled = yes @socket.onclose = -> # This will be called because technically, the websocket does go into the # close state! # # This is exactly what websockets do. assert.ok onErrorCalled done() test 'disconnecting momentarily allows the client to connect, then onclose() is called', (done) -> @socket = new BCSocket '/dc2', failFast: yes onErrorCalled = no @socket.onerror = (message, errCode) => # The error code varies here, depending on some timing parameters & # browser. I've seen NO_DATA, REQUEST_FAILED and UNKNOWN_SESSION_ID. assert.strictEqual @socket.readyState, @socket.CLOSING assert.ok message assert.ok errCode onErrorCalled = yes @socket.onclose = (reason, pendingMaps, undeliveredMaps) => # The error code varies here, depending on some timing parameters & browser. # These will probably be undefined, but == will catch that. assert.strictEqual @socket.readyState, @socket.CLOSED assert.equal pendingMaps, null assert.equal undeliveredMaps, null assert.ok onErrorCalled done() test 'passing a previous session will ghost that session', (done) -> @socket1 = new BCSocket '/echo' @socket1.onopen = => @socket2 = new BCSocket '/echo', prev:@socket1 @socket1.onclose = => @socket2.close() done() suite 'The client keeps reconnecting', -> m = (base) -> (done) -> @socket = new BCSocket base, failFast: yes, reconnect: yes, reconnectTime: 300 openCount = 0 @socket.onopen = => throw new Error 'Should not keep trying to open once the test is done' if openCount == 2 assert.strictEqual @socket.readyState, @socket.OPEN @socket.onclose = (reason, pendingMaps, undeliveredMaps) => assert.strictEqual @socket.readyState, @socket.CLOSED assert openCount < 2 openCount++ if openCount is 2 # Tell the socket to stop trying to connect @socket.close() done() test 'When the connection fails', m('dc1') # 'When the connection dies': m('dc3') suite 'stop', -> makeTest = (base) -> (done) -> # We don't need failFast for stop. @socket = new BCSocket base onErrorCalled = no @socket.onerror = (message, errCode) => assert.strictEqual @socket.readyState, @socket.CLOSING assert.strictEqual message, 'Stopped by server' assert.strictEqual errCode, 7 onErrorCalled = yes @socket.onclose = (reason, pendingMaps, undeliveredMaps) => # These will probably be undefined, but == will catch that. assert.strictEqual @socket.readyState, @socket.CLOSED assert.equal pendingMaps, null assert.equal undeliveredMaps, null assert.strictEqual reason, 'Stopped by server' assert.ok onErrorCalled done() test 'on connect', makeTest 'stop1' test 'after connect', makeTest 'stop2' # We need to be able to send \u2028 and \u2029 # http://timelessrepo.com/json-isnt-a-javascript-subset test 'Line separator and paragraph separators work', (done) -> @socket = new BCSocket '/utfsep', appVersion: 321 @socket.onmessage = (message) -> assert.strictEqual message.data, "\u2028 \u2029" done() # We should be able to specify GET variables to be sent with every request. test 'extraParams are passed to the server', (done) -> @socket = new BCSocket '/extraParams', extraParams: foo: 'bar' @socket.onmessage = (message) -> assert.strictEqual message.data.foo, 'bar' done() test 'Session affinity tokens are generated by default', (done) -> @socket = new BCSocket '/extraParams' affinity = @socket.affinity @socket.onmessage = (message) -> assert.strictEqual message.data.a, affinity done() test 'Session affinity tokens can be set manually', (done) -> @socket = new BCSocket '/extraParams', affinity: 'custom token' @socket.onmessage = (message) -> assert.strictEqual message.data.a, 'custom token' done() test 'Session affinity GET variable can be modified', (done) -> @socket = new BCSocket '/extraParams', affinityParam: 'avoidConflict' affinity = @socket.affinity @socket.onmessage = (message) -> assert.strictEqual message.data.avoidConflict, affinity done() test 'Session affinity tokens can be disabled', (done) -> @socket = new BCSocket '/extraParams', affinity: null @socket.onmessage = (message) -> assert.strictEqual message.data.a, undefined done() test 'Extra headers are sent to the server', (done) -> @socket = new BCSocket '/extraHeaders', extraHeaders: {'X-Style': 'Fabulous'} @socket.onmessage = (message) -> assert.strictEqual message.data['x-style'], 'Fabulous' done()