3 require 'database_cleaner'
5 DatabaseCleaner.strategy = :deletion
7 class WebsocketTest < ActionDispatch::IntegrationTest
8 self.use_transactional_fixtures = false
19 s = TCPServer.new('0.0.0.0', 0)
22 pidfile = "tmp/pids/passenger.#{@@port}.pid"
24 Dir.chdir(Rails.root) do |apidir|
25 # Only passenger seems to be able to run the websockets server
27 _system('passenger', 'start', '-d',
29 "--log-file", "/dev/stderr")
30 timeout = Time.now.tv_sec + 10
34 server_pid = IO.read(pidfile).to_i
35 good_pid = (server_pid > 0) and (Process.kill(0, pid) rescue false)
39 end while (not good_pid) and (Time.now.tv_sec < timeout)
41 raise RuntimeError, "could not find API server Rails pid"
43 STDERR.puts "Started websocket server on port #{@@port} with pid #{server_pid}"
48 Dir.chdir(Rails.root) do
49 _system('passenger', 'stop', "-p#{@@port}")
51 # DatabaseCleaner leaves the database empty. Prefer to leave it full.
52 dc = DatabaseController.new
53 dc.define_singleton_method :render do |*args| end
57 def self._system(*cmd)
58 Bundler.with_clean_env do
60 'ARVADOS_WEBSOCKETS' => 'ws-only',
61 'RAILS_ENV' => 'test',
63 if not system(env, *cmd)
64 raise RuntimeError, "Command exited #{$?}: #{cmd.inspect}"
69 def ws_helper(token: nil, timeout: 8)
76 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket?api_token=#{api_client_authorizations(token).api_token}")
78 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket")
81 ws.on :open do |event|
84 EM::Timer.new(timeout) do
85 too_long = true if close_status.nil?
91 ws.on :error do |event|
92 STDERR.puts "websocket client error: #{event.inspect}"
95 ws.on :close do |event|
96 close_status = [:close, event.code, event.reason]
103 assert opened, "Should have opened web socket"
104 assert (not too_long), "Test took too long"
105 assert_equal 1000, close_status[1], "Connection closed unexpectedly (check log for errors)"
108 test "connect with no token" do
112 ws.on :message do |event|
113 d = Oj.strict_load event.data
119 assert_equal 401, status
122 test "connect, subscribe and get response" do
125 ws_helper(token: :active) do |ws|
126 ws.on :open do |event|
127 ws.send ({method: 'subscribe'}.to_json)
130 ws.on :message do |event|
131 d = Oj.strict_load event.data
137 assert_equal 200, status
145 authorize_with :active
147 ws_helper(token: :active) do |ws|
148 ws.on :open do |event|
149 ws.send ({method: 'subscribe'}.to_json)
152 ws.on :message do |event|
153 d = Oj.strict_load event.data
156 assert_equal 200, d["status"]
157 spec = Specimen.create
160 ev_uuid = d["object_uuid"]
168 assert_equal spec.uuid, ev_uuid
171 test "connect, subscribe, get event" do
175 test "connect, subscribe, get two events" do
182 authorize_with :active
184 ws_helper(token: :active) do |ws|
185 ws.on :open do |event|
186 ws.send ({method: 'subscribe'}.to_json)
189 ws.on :message do |event|
190 d = Oj.strict_load event.data
193 assert_equal 200, d["status"]
194 spec = Specimen.create
198 spec_ev_uuid = d["object_uuid"]
201 human_ev_uuid = d["object_uuid"]
205 assert false, "Should not get any more events"
213 assert_equal spec.uuid, spec_ev_uuid
214 assert_equal human.uuid, human_ev_uuid
217 test "connect, subscribe, filter events" do
222 authorize_with :active
224 ws_helper(token: :active) do |ws|
225 ws.on :open do |event|
226 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
229 ws.on :message do |event|
230 d = Oj.strict_load event.data
233 assert_equal 200, d["status"]
238 human_ev_uuid = d["object_uuid"]
242 assert false, "Should not get any more events"
249 assert_equal human.uuid, human_ev_uuid
253 test "connect, subscribe, multiple filters" do
260 authorize_with :active
262 ws_helper(token: :active) do |ws|
263 ws.on :open do |event|
264 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
265 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#specimen']]}.to_json)
268 ws.on :message do |event|
269 d = Oj.strict_load event.data
272 assert_equal 200, d["status"]
275 assert_equal 200, d["status"]
276 spec = Specimen.create
277 Trait.create # not part of filters, should not be received
281 spec_ev_uuid = d["object_uuid"]
284 human_ev_uuid = d["object_uuid"]
288 assert false, "Should not get any more events"
296 assert_equal spec.uuid, spec_ev_uuid
297 assert_equal human.uuid, human_ev_uuid
301 test "connect, subscribe, compound filter" do
305 authorize_with :active
307 ws_helper(token: :active) do |ws|
308 ws.on :open do |event|
309 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#trait'], ['event_type', '=', 'update']]}.to_json)
312 ws.on :message do |event|
313 d = Oj.strict_load event.data
316 assert_equal 200, d["status"]
317 t1 = Trait.create("name" => "foo")
322 assert_equal 'update', d['event_type']
326 assert false, "Should not get any more events"
332 assert_equal 3, state
336 test "connect, subscribe, ask events starting at seq num" do
341 authorize_with :active
343 lastid = logs(:admin_changes_specimen).id
347 ws_helper(token: :active) do |ws|
348 ws.on :open do |event|
349 ws.send ({method: 'subscribe', last_log_id: lastid}.to_json)
352 ws.on :message do |event|
353 d = Oj.strict_load event.data
356 assert_equal 200, d["status"]
359 l1 = d["object_uuid"]
360 assert_not_nil l1, "Unexpected message: #{d}"
363 l2 = d["object_uuid"]
364 assert_not_nil l2, "Unexpected message: #{d}"
368 assert false, "Should not get any more events"
373 expect_next_logs = Log.where('id > ?', lastid).order('id asc')
374 assert_equal expect_next_logs[0].object_uuid, l1
375 assert_equal expect_next_logs[1].object_uuid, l2
378 test "connect, subscribe, get event, unsubscribe" do
385 authorize_with :active
387 ws_helper(token: :active, timeout: false) do |ws|
388 ws.on :open do |event|
389 ws.send ({method: 'subscribe'}.to_json)
391 # Set a time limit on the test because after unsubscribing the server
392 # still has to process the next event (and then hopefully correctly
393 # decides not to send it because we unsubscribed.)
398 ws.on :message do |event|
399 d = Oj.strict_load event.data
402 assert_equal 200, d["status"]
403 spec = Specimen.create
406 spec_ev_uuid = d["object_uuid"]
407 ws.send ({method: 'unsubscribe'}.to_json)
415 assert_equal 200, d["status"]
418 assert false, "Should not get any more events"
425 assert_equal spec.uuid, spec_ev_uuid
428 test "connect, subscribe, get event, unsubscribe with filter" do
434 authorize_with :active
436 ws_helper(token: :active, timeout: false) do |ws|
437 ws.on :open do |event|
438 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
440 # Set a time limit on the test because after unsubscribing the server
441 # still has to process the next event (and then hopefully correctly
442 # decides not to send it because we unsubscribed.)
447 ws.on :message do |event|
448 d = Oj.strict_load event.data
451 assert_equal 200, d["status"]
455 spec_ev_uuid = d["object_uuid"]
456 ws.send ({method: 'unsubscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
464 assert_equal 200, d["status"]
467 assert false, "Should not get any more events"
474 assert_equal spec.uuid, spec_ev_uuid
478 test "connect, subscribe, get event, try to unsubscribe with bogus filter" do
486 authorize_with :active
488 ws_helper(token: :active) do |ws|
489 ws.on :open do |event|
490 ws.send ({method: 'subscribe'}.to_json)
493 ws.on :message do |event|
494 d = Oj.strict_load event.data
497 assert_equal 200, d["status"]
498 spec = Specimen.create
501 spec_ev_uuid = d["object_uuid"]
502 ws.send ({method: 'unsubscribe', filters: [['foo', 'bar', 'baz']]}.to_json)
510 assert_equal 404, d["status"]
513 human_ev_uuid = d["object_uuid"]
517 assert false, "Should not get any more events"
525 assert_equal spec.uuid, spec_ev_uuid
526 assert_equal human.uuid, human_ev_uuid
531 test "connected, not subscribed, no event" do
533 authorize_with :active
535 ws_helper(token: :active, timeout: false) do |ws|
536 ws.on :open do |event|
546 ws.on :message do |event|
547 assert false, "Should not get any messages, message was #{event.data}"
552 test "connected, not authorized to see event" do
556 authorize_with :admin
558 ws_helper(token: :active, timeout: false) do |ws|
559 ws.on :open do |event|
560 ws.send ({method: 'subscribe'}.to_json)
567 ws.on :message do |event|
568 d = Oj.strict_load event.data
571 assert_equal 200, d["status"]
575 assert false, "Should not get any messages, message was #{event.data}"
583 test "connect, try bogus method" do
586 ws_helper(token: :active) do |ws|
587 ws.on :open do |event|
588 ws.send ({method: 'frobnabble'}.to_json)
591 ws.on :message do |event|
592 d = Oj.strict_load event.data
598 assert_equal 400, status
601 test "connect, missing method" do
604 ws_helper(token: :active) do |ws|
605 ws.on :open do |event|
606 ws.send ({fizzbuzz: 'frobnabble'}.to_json)
609 ws.on :message do |event|
610 d = Oj.strict_load event.data
616 assert_equal 400, status
619 test "connect, send malformed request" do
622 ws_helper(token: :active) do |ws|
623 ws.on :open do |event|
624 ws.send '<XML4EVER></XML4EVER>'
627 ws.on :message do |event|
628 d = Oj.strict_load event.data
634 assert_equal 400, status
638 test "connect, try subscribe too many filters" do
641 authorize_with :active
643 ws_helper(token: :active) do |ws|
644 ws.on :open do |event|
646 ws.send ({method: 'subscribe', filters: [['object_uuid', '=', i]]}.to_json)
650 ws.on :message do |event|
651 d = Oj.strict_load event.data
653 when (1..Rails.configuration.websocket_max_filters)
654 assert_equal 200, d["status"]
656 when (Rails.configuration.websocket_max_filters+1)
657 assert_equal 403, d["status"]
664 assert_equal Rails.configuration.websocket_max_filters+1, state
668 test "connect, subscribe, lots of events" do
672 log_start = Log.order(:id).last.id
674 authorize_with :active
676 ws_helper(token: :active, timeout: false) do |ws|
678 # Needs a longer timeout than the default
682 ws.on :open do |event|
683 ws.send ({method: 'subscribe'}.to_json)
686 ws.on :message do |event|
687 d = Oj.strict_load event.data
690 assert_equal 200, d["status"]
691 ActiveRecord::Base.transaction do
693 spec = Specimen.create
699 assert_equal d['id'], event_count+log_start
700 if event_count == 202
708 assert_equal 202, event_count
712 test "connect, subscribe with invalid filter" do
717 authorize_with :active
719 ws_helper(token: :active) do |ws|
720 ws.on :open do |event|
721 # test that #6451 is fixed (invalid filter crashes websockets)
722 ws.send ({method: 'subscribe', filters: [['object_blarg', 'is_a', 'arvados#human']]}.to_json)
725 ws.on :message do |event|
726 d = Oj.strict_load event.data
729 assert_equal 200, d["status"]
734 assert_equal 500, d["status"]
738 assert false, "Should not get any more events"
744 assert_equal 3, state
746 # Try connecting again, ensure that websockets server is still running and
747 # didn't crash per #6451