browserchannel
Version:
Google BrowserChannel server for NodeJS
248 lines (201 loc) • 8.87 kB
text/coffeescript
# # Tests for the bare BrowserChannel client.
#
# Run them by first launching
#
# % coffee test/runserver.coffee
#
# ... Then browsing to localhost:4321 in your browser or running:
#
# % nodeunit test/browserchannel.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.
# - Sometimes (about 1/4 times) the tests are run, the process doesn't exit for about 5 seconds after
# the tests have finished. Presumably, there's a setTimeout() in the client somewhere which has
# a race condition causing it to misbehave.
# - After a test run, 4 sessions are allowed to time out by the server. (Its odd because I'm calling
# disconnect() in tearDown).
nodeunit = window?.nodeunit or require 'nodeunit'
if process.title is 'node'
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.
`BCSocket = bc.BCSocket`
bc.setDefaultLocation 'http://localhost:4321'
module.exports = nodeunit.testCase
tearDown: (callback) ->
if @socket? and @socket.readyState isnt BCSocket.CLOSED
@socket.onclose = -> callback()
@socket.close()
@socket = null
else
callback()
# These match websocket codes
'states and errors are mapped': (test) ->
test.strictEqual BCSocket.CONNECTING, 0
test.strictEqual BCSocket.OPEN, 1
test.strictEqual BCSocket.CLOSING, 2
test.strictEqual BCSocket.CLOSED, 3
test.strictEqual BCSocket.prototype.CONNECTING, 0
test.strictEqual BCSocket.prototype.OPEN, 1
test.strictEqual BCSocket.prototype.CLOSING, 2
test.strictEqual BCSocket.prototype.CLOSED, 3
test.done()
# Can we connect to the server?
'connect': (test) ->
@socket = new BCSocket '/notify'
test.strictEqual @socket.readyState, BCSocket.CONNECTING
@socket.onopen = =>
test.strictEqual @socket.readyState, BCSocket.OPEN
@socket.onerror = (reason) ->
throw new Error reason
@socket.onmessage = (message) ->
test.deepEqual message, {appVersion: null}
test.expect 3
test.done()
# The socket interface exposes browserchannel's app version thingy through
# option arguments
'connect sends app version': (test) ->
@socket = new BCSocket '/notify', appVersion: 321
@socket.onmessage = (message) ->
test.deepEqual message, {appVersion:321}
test.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.
'send maps': do ->
# I'll throw some random unicode characters in here just to make sure...
data = {'foo': 'bar', 'zot': '(◔ ◡ ◔)'}
m = (callback) -> (test) ->
@socket = new BCSocket '/echomap', appVersion: 321
@socket.onmessage = (message) ->
test.deepEqual message, data
test.done()
callback.apply this
'immediately': m ->
@socket.sendMap data
'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.
'can send and receive JSON messages': do ->
# Vim gets formatting errors with the cat face glyph here. Sad.
data = [null, 1.5, "hi", {}, [1,2,3], '⚗☗⚑☯']
m = (callback) -> (test) ->
# Using the /echo server not /echomap
@socket = new BCSocket '/echo', appVersion: 321
@socket.onmessage = (message) ->
test.deepEqual message, data
test.done()
callback.apply this
'immediately': m ->
# Calling send() instead of sendMap()
@socket.send data
'after we have connected': m ->
@socket.onopen = =>
@socket.send data
# 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.
'disconnecting immediately results in REQUEST_FAILED and a 403': (test) ->
@socket = new BCSocket '/dc1'
@socket.onopen = -> throw new Error 'Socket should not have opened'
@socket.onerror = (message, errCode) =>
test.strictEqual message, 'Request failed'
test.strictEqual errCode, 2
test.done()
@socket.onclose = ->
throw new Error 'orly'
'disconnecting momentarily allows the client to connect, then onclose() is called': (test) ->
@socket = new BCSocket '/dc2', failFast: yes
@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.
test.strictEqual @socket.readyState, @socket.CLOSING
test.ok message
test.ok errCode
@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.
test.strictEqual @socket.readyState, @socket.CLOSED
test.equal pendingMaps, null
test.equal undeliveredMaps, null
test.expect 6
test.done()
'The client keeps reconnecting': do ->
m = (base) -> (test) ->
@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
test.strictEqual @socket.readyState, @socket.OPEN
@socket.onclose = (reason, pendingMaps, undeliveredMaps) =>
test.strictEqual @socket.readyState, @socket.CLOSED
openCount++
if openCount is 2
# Tell the socket to stop trying to connect
@socket.close()
test.done()
'When the connection fails': m('dc1')
# 'When the connection dies': m('dc3')
'stop': do ->
makeTest = (base) -> (test) ->
# We don't need failFast for stop.
@socket = new BCSocket base
@socket.onerror = (message, errCode) =>
test.strictEqual @socket.readyState, @socket.CLOSING
test.strictEqual message, 'Stopped by server'
test.strictEqual errCode, 7
@socket.onclose = (reason, pendingMaps, undeliveredMaps) =>
# These will probably be undefined, but == will catch that.
test.strictEqual @socket.readyState, @socket.CLOSED
test.equal pendingMaps, null
test.equal undeliveredMaps, null
test.strictEqual reason, 'Stopped by server'
test.expect 7
test.done()
'on connect': makeTest 'stop1'
'after connect': makeTest 'stop2'
# 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.
'Send & receive lots of data': (test) ->
num = 5000
@socket = new BCSocket '/echomap'
received = 0
@socket.onmessage = (message) ->
test.equal message.data, received
received++
test.done() if received == num
process.nextTick =>
# 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]