[snowflake/master] better ProxyPair jasmine specs, ensure travis using more recent npm

commit 889b3fee98d77163dc8ee8d5ff805129eb9dc341 Author: Serene Han <keroserene+git@gmail.com> Date: Fri Feb 5 09:20:01 2016 -0800 better ProxyPair jasmine specs, ensure travis using more recent npm --- .gitignore | 2 +- .travis.yml | 20 ++-- proxy/Cakefile | 17 +-- proxy/spec.coffee | 180 ---------------------------- proxy/spec/proxypair.spec.coffee | 95 +++++++++++++++ proxy/spec/util.spec.coffee | 248 +++++++++++++++++++++++++++++++++++++++ proxy/websocket.coffee | 2 - 7 files changed, 366 insertions(+), 198 deletions(-) diff --git a/.gitignore b/.gitignore index b1db25c..f9356ae 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ snowflake.log proxy/test proxy/build proxy/node_modules -proxy/spec +proxy/spec/support ignore/ diff --git a/.travis.yml b/.travis.yml index 4a7aedd..7f4e265 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,20 @@ language: go go: - - 1.5 + - 1.5 + +env: + - TRAVIS_NODE_VERSION="4.1" + +install: + - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION before_script: - npm install -g coffee-script coffeelint jasmine + npm install -g coffee-script coffeelint jasmine script: - - make check - - go test -v -race ./broker - - cd proxy - - cake lint - - cake test + - make check + - go test -v -race ./broker + - cd proxy + - cake lint + - cake test diff --git a/proxy/Cakefile b/proxy/Cakefile index c569110..a41b55b 100644 --- a/proxy/Cakefile +++ b/proxy/Cakefile @@ -1,5 +1,5 @@ fs = require 'fs' -{spawn, exec} = require 'child_process' +{exec, spawn, execSync} = require 'child_process' # All coffeescript files required. FILES = [ @@ -12,7 +12,8 @@ FILES = [ 'snowflake.coffee' ] FILES_SPEC = [ - 'spec.coffee' + 'spec/util.spec.coffee' + 'spec/proxypair.spec.coffee' ] FILES_ALL = FILES.concat FILES_SPEC OUTFILE = 'build/snowflake.coffee' @@ -20,19 +21,21 @@ STATIC = 'static' concatCoffeeFiles = -> exec 'cat ' + FILES.join(' ') + ' | cat > ' + OUTFILE -copyStaticFiles = -> exec 'cp ' + STATIC + '/* build/' +copyStaticFiles = -> exec '' + STATIC + '/* build/' compileCoffee = -> exec 'coffee -o build -b -c build/snowflake.coffee', (err, stdout, stderr) -> throw err if err task 'test', 'snowflake unit tests', -> + exec 'mkdir -p build' exec 'jasmine init >&-' + # Simply concat all the files because we're not using node exports. jasmineFiles = FILES.concat FILES_SPEC - outFile = 'spec/snowflake.bundle.coffee' + outFile = 'build/bundle.spec.coffee' exec 'cat ' + jasmineFiles.join(' ') + ' | cat > ' + outFile - exec 'coffee -o spec -cb ' + outFile - spawn 'jasmine', ['spec/snowflake.bundle.js'], { + execSync 'coffee -cb ' + outFile + spawn 'jasmine', ['build/bundle.spec.js'], { stdio: 'inherit' } @@ -51,5 +54,3 @@ task 'lint', 'ensure idiomatic coffeescript', -> task 'clean', 'remove all built files', -> exec 'rm -r build' - exec 'rm -r spec' - exec 'rm -r test/snowflake.bundle.coffee' diff --git a/proxy/spec.coffee b/proxy/spec.coffee deleted file mode 100644 index 3d8ae61..0000000 --- a/proxy/spec.coffee +++ /dev/null @@ -1,180 +0,0 @@ -### -jasmine tests for Snowflake -### - -# Stubs to fake browser functionality. -class WebSocket - OPEN: 1 - CLOSED: 0 -ui = {} - -describe 'BuildUrl', -> - it 'should parse just protocol and host', -> - expect(buildUrl('http', 'example.com')).toBe 'http://example.com' - it 'should handle different ports', -> - expect buildUrl 'http', 'example.com', 80 - .toBe 'http://example.com' - expect buildUrl 'http', 'example.com', 81 - .toBe 'http://example.com:81' - expect buildUrl 'http', 'example.com', 443 - .toBe 'http://example.com:443' - expect buildUrl 'http', 'example.com', 444 - .toBe 'http://example.com:444' - it 'should handle paths', -> - expect buildUrl 'http', 'example.com', 80, '/' - .toBe 'http://example.com/' - expect buildUrl 'http', 'example.com', 80,'/test?k=%#v' - .toBe 'http://example.com/test%3Fk%3D%25%23v' - expect buildUrl 'http', 'example.com', 80, '/test' - .toBe 'http://example.com/test' - it 'should handle params', -> - expect buildUrl 'http', 'example.com', 80, '/test', [['k', '%#v']] - .toBe 'http://example.com/test?k=%25%23v' - expect buildUrl 'http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']] - .toBe 'http://example.com/test?a=b&c=d' - it 'should handle ips', -> - expect buildUrl 'http', '1.2.3.4' - .toBe 'http://1.2.3.4' - expect buildUrl 'http', '1:2::3:4' - .toBe 'http://[1:2::3:4]' - it 'should handle bogus', -> - expect buildUrl 'http', 'bog][us' - .toBe 'http://bog%5D%5Bus' - expect buildUrl 'http', 'bog:u]s' - .toBe 'http://bog%3Au%5Ds' - -describe 'Parse', -> - - describe 'cookie', -> - it 'parses correctly', -> - expect Parse.cookie '' - .toEqual {} - expect Parse.cookie 'a=b' - .toEqual { a: 'b' } - expect Parse.cookie 'a=b=c' - .toEqual { a: 'b=c' } - expect Parse.cookie 'a=b; c=d' - .toEqual { a: 'b', c: 'd' } - expect Parse.cookie 'a=b ; c=d' - .toEqual { a: 'b', c: 'd' } - expect Parse.cookie 'a= b' - .toEqual { a: 'b' } - expect Parse.cookie 'a=' - .toEqual { a: '' } - expect Parse.cookie 'key' - .toBeNull() - expect Parse.cookie 'key=%26%20' - .toEqual { key: '& ' } - expect Parse.cookie 'a=\'\'' - .toEqual { a: '\'\'' } - - describe 'address', -> - it 'parses IPv4', -> - expect Parse.address '' - .toBeNull() - expect Parse.address '3.3.3.3:4444' - .toEqual { host: '3.3.3.3', port: 4444 } - expect Parse.address '3.3.3.3' - .toBeNull() - expect Parse.address '3.3.3.3:0x1111' - .toBeNull() - expect Parse.address '3.3.3.3:-4444' - .toBeNull() - expect Parse.address '3.3.3.3:65536' - .toBeNull() - it 'parses IPv6', -> - expect Parse.address '[1:2::a:f]:4444' - .toEqual { host: '1:2::a:f', port: 4444 } - expect Parse.address '[1:2::a:f]' - .toBeNull() - expect Parse.address '[1:2::a:f]:0x1111' - .toBeNull() - expect Parse.address '[1:2::a:f]:-4444' - .toBeNull() - expect Parse.address '[1:2::a:f]:65536' - .toBeNull() - expect Parse.address '[1:2::ffff:1.2.3.4]:4444' - .toEqual { host: '1:2::ffff:1.2.3.4', port: 4444 } - -describe 'query string', -> - it 'should parse correctly', -> - expect Query.parse '' - .toEqual {} - expect Query.parse 'a=b' - .toEqual { a: 'b' } - expect Query.parse 'a=b=c' - .toEqual { a: 'b=c' } - expect Query.parse 'a=b&c=d' - .toEqual { a: 'b', c: 'd' } - expect Query.parse 'client=&relay=1.2.3.4%3A9001' - .toEqual { client: '', relay: '1.2.3.4:9001' } - expect Query.parse 'a=b%26c=d' - .toEqual { a: 'b&c=d' } - expect Query.parse 'a%3db=d' - .toEqual { 'a=b': 'd' } - expect Query.parse 'a=b+c%20d' - .toEqual { 'a': 'b c d' } - expect Query.parse 'a=b+c%2bd' - .toEqual { 'a': 'b c+d' } - expect Query.parse 'a+b=c' - .toEqual { 'a b': 'c' } - expect Query.parse 'a=b+c+d' - .toEqual { a: 'b c d' } - it 'uses the first appearance of duplicate key', -> - expect Query.parse 'a=b&c=d&a=e' - .toEqual { a: 'b', c: 'd' } - expect Query.parse 'a' - .toEqual { a: '' } - expect Query.parse '=b' - .toEqual { '': 'b' } - expect Query.parse '&a=b' - .toEqual { '': '', a: 'b' } - expect Query.parse 'a=b&' - .toEqual { a: 'b', '':'' } - expect Query.parse 'a=b&&c=d' - .toEqual { a: 'b', '':'', c: 'd' } - -describe 'Params', -> - describe 'bool', -> - getBool = (query) -> - Params.getBool (Query.parse query), 'param', false - it 'parses correctly', -> - expect(getBool 'param=true').toEqual true - expect(getBool 'param').toEqual true - expect(getBool 'param=').toEqual true - expect(getBool 'param=1').toEqual true - expect(getBool 'param=0').toEqual false - expect(getBool 'param=false').toEqual false - expect(getBool 'param=unexpected').toBeNull() - expect(getBool 'pram=true').toEqual false - - describe 'address', -> - DEFAULT = { host: '1.1.1.1', port: 2222 } - getAddress = (query) -> - Params.getAddress query, 'addr', DEFAULT - it 'parses correctly', -> - expect(getAddress {}).toEqual DEFAULT - expect getAddress { addr: '3.3.3.3:4444' } - .toEqual { host: '3.3.3.3', port: 4444 } - expect getAddress { x: '3.3.3.3:4444' } - .toEqual DEFAULT - expect getAddress { addr: '---' } - .toBeNull() - -describe 'ProxyPair', -> - fakeRelay = Parse.address '0.0.0.0:12345' - rateLimit = new DummyRateLimit() - destination = [] - fakeClient = send: (d) -> destination.push d - pp = new ProxyPair(fakeClient, fakeRelay, rateLimit) - it 'handles relay correctly', -> - pp.connectRelay() - expect(pp.relay.onopen).not.toBeNull() - expect(pp.relay.onclose).not.toBeNull() - expect(pp.relay.onerror).not.toBeNull() - expect(pp.relay.onmessage).not.toBeNull() - # TODO: Test for flush - # pp.c2rSchedule.push { data: 'omg' } - # pp.flush() - # if destination == ['omg'] then pass 'flush' - # else fail 'flush', ['omg'], destination diff --git a/proxy/spec/proxypair.spec.coffee b/proxy/spec/proxypair.spec.coffee new file mode 100644 index 0000000..261d38d --- /dev/null +++ b/proxy/spec/proxypair.spec.coffee @@ -0,0 +1,95 @@ +### +jasmine tests for Snowflake +### + +# Stubs to fake browser functionality. +class PeerConnection +class WebSocket + OPEN: 1 + CLOSED: 0 +ui = + log: -> + setActive: -> +log = -> + +describe 'ProxyPair', -> + fakeRelay = Parse.address '0.0.0.0:12345' + rateLimit = new DummyRateLimit() + destination = [] + fakeClient = send: (d) -> destination.push d + # Fake snowflake to interact with + snowflake = { + broker: + sendAnswer: -> + } + pp = new ProxyPair(fakeClient, fakeRelay, rateLimit) + + it 'begins webrtc connection', -> + pp.begin() + expect(pp.pc).not.toBeNull() + + it 'accepts WebRTC offer from some client', -> + it 'rejects invalid offers', -> + expect(pp.receiveWebRTCOffer {}).toBe false + expect pp.receiveWebRTCOffer { + type: 'answer' + }.toBeFalse() + it 'accepts valid offers', -> + goodOffer = { + type: 'offer' + sdp: 'foo' + } + expect(pp.receiveWebRTCOffer goodOffer).toBe true + + it 'responds with a WebRTC answer correctly', -> + spyOn snowflake.broker, 'sendAnswer' + pp.pc.onicecandidate { + candidate: null + } + expect(snowflake.broker.sendAnswer).toHaveBeenCalled() + + it 'handles a new data channel correctly', -> + expect(pp.client).toBeNull() + pp.pc.ondatachannel { + channel: {} + } + expect(pp.client).not.toBeNull() + expect(pp.client.onopen).not.toBeNull() + expect(pp.client.onclose).not.toBeNull() + expect(pp.client.onerror).not.toBeNull() + expect(pp.client.onmessage).not.toBeNull() + + it 'connects to the relay once datachannel opens', -> + spyOn pp, 'connectRelay' + pp.client.onopen() + expect(pp.connectRelay).toHaveBeenCalled() + + it 'connects to a relay', -> + pp.connectRelay() + expect(pp.relay.onopen).not.toBeNull() + expect(pp.relay.onclose).not.toBeNull() + expect(pp.relay.onerror).not.toBeNull() + expect(pp.relay.onmessage).not.toBeNull() + + it 'flushes data between client and relay', -> + + it 'proxies data from client to relay', -> + spyOn pp.relay, 'send' + pp.c2rSchedule.push { data: 'foo' } + pp.flush() + expect(pp.client.send).not.toHaveBeenCalled() + expect(pp.relay.send).toHaveBeenCalledWith 'foo' + + it 'proxies data from relay to client', -> + spyOn pp.client, 'send' + pp.r2cSchedule.push { data: 'bar' } + pp.flush() + expect(pp.client.send).toHaveBeenCalledWith 'bar' + expect(pp.relay.send).not.toHaveBeenCalled() + + it 'sends nothing with nothing to flush', -> + pp.flush() + expect(pp.client.send).not.toHaveBeenCalled() + expect(pp.relay.send).not.toHaveBeenCalled() + +# TODO: rate limit tests diff --git a/proxy/spec/util.spec.coffee b/proxy/spec/util.spec.coffee new file mode 100644 index 0000000..a4281e9 --- /dev/null +++ b/proxy/spec/util.spec.coffee @@ -0,0 +1,248 @@ +### +jasmine tests for Snowflake +### + +# Stubs to fake browser functionality. +class PeerConnection +class WebSocket + OPEN: 1 + CLOSED: 0 +ui = + log: -> + setActive: -> +log = -> + +describe 'BuildUrl', -> + it 'should parse just protocol and host', -> + expect(buildUrl('http', 'example.com')).toBe 'http://example.com' + it 'should handle different ports', -> + expect buildUrl 'http', 'example.com', 80 + .toBe 'http://example.com' + expect buildUrl 'http', 'example.com', 81 + .toBe 'http://example.com:81' + expect buildUrl 'http', 'example.com', 443 + .toBe 'http://example.com:443' + expect buildUrl 'http', 'example.com', 444 + .toBe 'http://example.com:444' + it 'should handle paths', -> + expect buildUrl 'http', 'example.com', 80, '/' + .toBe 'http://example.com/' + expect buildUrl 'http', 'example.com', 80,'/test?k=%#v' + .toBe 'http://example.com/test%3Fk%3D%25%23v' + expect buildUrl 'http', 'example.com', 80, '/test' + .toBe 'http://example.com/test' + it 'should handle params', -> + expect buildUrl 'http', 'example.com', 80, '/test', [['k', '%#v']] + .toBe 'http://example.com/test?k=%25%23v' + expect buildUrl 'http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']] + .toBe 'http://example.com/test?a=b&c=d' + it 'should handle ips', -> + expect buildUrl 'http', '1.2.3.4' + .toBe 'http://1.2.3.4' + expect buildUrl 'http', '1:2::3:4' + .toBe 'http://[1:2::3:4]' + it 'should handle bogus', -> + expect buildUrl 'http', 'bog][us' + .toBe 'http://bog%5D%5Bus' + expect buildUrl 'http', 'bog:u]s' + .toBe 'http://bog%3Au%5Ds' + +describe 'Parse', -> + + describe 'cookie', -> + it 'parses correctly', -> + expect Parse.cookie '' + .toEqual {} + expect Parse.cookie 'a=b' + .toEqual { a: 'b' } + expect Parse.cookie 'a=b=c' + .toEqual { a: 'b=c' } + expect Parse.cookie 'a=b; c=d' + .toEqual { a: 'b', c: 'd' } + expect Parse.cookie 'a=b ; c=d' + .toEqual { a: 'b', c: 'd' } + expect Parse.cookie 'a= b' + .toEqual { a: 'b' } + expect Parse.cookie 'a=' + .toEqual { a: '' } + expect Parse.cookie 'key' + .toBeNull() + expect Parse.cookie 'key=%26%20' + .toEqual { key: '& ' } + expect Parse.cookie 'a=\'\'' + .toEqual { a: '\'\'' } + + describe 'address', -> + it 'parses IPv4', -> + expect Parse.address '' + .toBeNull() + expect Parse.address '3.3.3.3:4444' + .toEqual { host: '3.3.3.3', port: 4444 } + expect Parse.address '3.3.3.3' + .toBeNull() + expect Parse.address '3.3.3.3:0x1111' + .toBeNull() + expect Parse.address '3.3.3.3:-4444' + .toBeNull() + expect Parse.address '3.3.3.3:65536' + .toBeNull() + it 'parses IPv6', -> + expect Parse.address '[1:2::a:f]:4444' + .toEqual { host: '1:2::a:f', port: 4444 } + expect Parse.address '[1:2::a:f]' + .toBeNull() + expect Parse.address '[1:2::a:f]:0x1111' + .toBeNull() + expect Parse.address '[1:2::a:f]:-4444' + .toBeNull() + expect Parse.address '[1:2::a:f]:65536' + .toBeNull() + expect Parse.address '[1:2::ffff:1.2.3.4]:4444' + .toEqual { host: '1:2::ffff:1.2.3.4', port: 4444 } + +describe 'query string', -> + it 'should parse correctly', -> + expect Query.parse '' + .toEqual {} + expect Query.parse 'a=b' + .toEqual { a: 'b' } + expect Query.parse 'a=b=c' + .toEqual { a: 'b=c' } + expect Query.parse 'a=b&c=d' + .toEqual { a: 'b', c: 'd' } + expect Query.parse 'client=&relay=1.2.3.4%3A9001' + .toEqual { client: '', relay: '1.2.3.4:9001' } + expect Query.parse 'a=b%26c=d' + .toEqual { a: 'b&c=d' } + expect Query.parse 'a%3db=d' + .toEqual { 'a=b': 'd' } + expect Query.parse 'a=b+c%20d' + .toEqual { 'a': 'b c d' } + expect Query.parse 'a=b+c%2bd' + .toEqual { 'a': 'b c+d' } + expect Query.parse 'a+b=c' + .toEqual { 'a b': 'c' } + expect Query.parse 'a=b+c+d' + .toEqual { a: 'b c d' } + it 'uses the first appearance of duplicate key', -> + expect Query.parse 'a=b&c=d&a=e' + .toEqual { a: 'b', c: 'd' } + expect Query.parse 'a' + .toEqual { a: '' } + expect Query.parse '=b' + .toEqual { '': 'b' } + expect Query.parse '&a=b' + .toEqual { '': '', a: 'b' } + expect Query.parse 'a=b&' + .toEqual { a: 'b', '':'' } + expect Query.parse 'a=b&&c=d' + .toEqual { a: 'b', '':'', c: 'd' } + +describe 'Params', -> + describe 'bool', -> + getBool = (query) -> + Params.getBool (Query.parse query), 'param', false + it 'parses correctly', -> + expect(getBool 'param=true').toBe true + expect(getBool 'param').toBe true + expect(getBool 'param=').toBe true + expect(getBool 'param=1').toBe true + expect(getBool 'param=0').toBe false + expect(getBool 'param=false').toBe false + expect(getBool 'param=unexpected').toBeNull() + expect(getBool 'pram=true').toBe false + + describe 'address', -> + DEFAULT = { host: '1.1.1.1', port: 2222 } + getAddress = (query) -> + Params.getAddress query, 'addr', DEFAULT + it 'parses correctly', -> + expect(getAddress {}).toEqual DEFAULT + expect getAddress { addr: '3.3.3.3:4444' } + .toEqual { host: '3.3.3.3', port: 4444 } + expect getAddress { x: '3.3.3.3:4444' } + .toEqual DEFAULT + expect getAddress { addr: '---' } + .toBeNull() + +describe 'ProxyPair', -> + fakeRelay = Parse.address '0.0.0.0:12345' + rateLimit = new DummyRateLimit() + destination = [] + fakeClient = send: (d) -> destination.push d + # Fake snowflake to interact with + snowflake = { + broker: + sendAnswer: -> + } + pp = new ProxyPair(fakeClient, fakeRelay, rateLimit) + + it 'begins webrtc connection', -> + pp.begin() + expect(pp.pc).not.toBeNull() + + it 'accepts WebRTC offer from some client', -> + it 'rejects invalid offers', -> + expect(pp.receiveWebRTCOffer {}).toBe false + expect pp.receiveWebRTCOffer { + type: 'answer' + }.toBeFalse() + it 'accepts valid offers', -> + goodOffer = { + type: 'offer' + sdp: 'foo' + } + expect(pp.receiveWebRTCOffer goodOffer).toBe true + + it 'responds with a WebRTC answer correctly', -> + spyOn snowflake.broker, 'sendAnswer' + pp.pc.onicecandidate { + candidate: null + } + expect(snowflake.broker.sendAnswer).toHaveBeenCalled() + + it 'handles a new data channel correctly', -> + expect(pp.client).toBeNull() + pp.pc.ondatachannel { + channel: {} + } + expect(pp.client).not.toBeNull() + expect(pp.client.onopen).not.toBeNull() + expect(pp.client.onclose).not.toBeNull() + expect(pp.client.onerror).not.toBeNull() + expect(pp.client.onmessage).not.toBeNull() + + it 'connects to the relay once datachannel opens', -> + spyOn pp, 'connectRelay' + pp.client.onopen() + expect(pp.connectRelay).toHaveBeenCalled() + + it 'connects to a relay', -> + pp.connectRelay() + expect(pp.relay.onopen).not.toBeNull() + expect(pp.relay.onclose).not.toBeNull() + expect(pp.relay.onerror).not.toBeNull() + expect(pp.relay.onmessage).not.toBeNull() + + it 'flushes data between client and relay', -> + + it 'proxies data from client to relay', -> + spyOn pp.relay, 'send' + pp.c2rSchedule.push { data: 'foo' } + pp.flush() + expect(pp.client.send).not.toHaveBeenCalled() + expect(pp.relay.send).toHaveBeenCalledWith 'foo' + + it 'proxies data from relay to client', -> + spyOn pp.client, 'send' + pp.r2cSchedule.push { data: 'bar' } + pp.flush() + expect(pp.client.send).toHaveBeenCalledWith 'bar' + expect(pp.relay.send).not.toHaveBeenCalled() + + it 'sends nothing with nothing to flush', -> + pp.flush() + expect(pp.client.send).not.toHaveBeenCalled() + expect(pp.relay.send).not.toHaveBeenCalled() + + # TODO: rate limit tests diff --git a/proxy/websocket.coffee b/proxy/websocket.coffee index 03fa0e8..e4a9550 100644 --- a/proxy/websocket.coffee +++ b/proxy/websocket.coffee @@ -56,5 +56,3 @@ makeWebsocket = (addr) -> ### ws.binaryType = 'arraybuffer' ws - -# module.exports.buildUrl = buildUrl
participants (1)
-
serene@torproject.org