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 "--pid-file", @@pidfile)
31 timeout = Time.now.tv_sec + 10
35 server_pid = IO.read(@@pidfile).to_i
36 good_pid = (server_pid > 0) and (Process.kill(0, pid) rescue false)
40 end while (not good_pid) and (Time.now.tv_sec < timeout)
42 raise RuntimeError, "could not find API server Rails pid"
44 STDERR.puts "Started websocket server on port #{@@port} with pid #{server_pid}"
49 Dir.chdir(Rails.root) do
50 _system('passenger', 'stop', "-p#{@@port}",
51 "--pid-file", @@pidfile)
53 # DatabaseCleaner leaves the database empty. Prefer to leave it full.
54 dc = DatabaseController.new
55 dc.define_singleton_method :render do |*args| end
59 def self._system(*cmd)
60 Bundler.with_clean_env do
62 'ARVADOS_WEBSOCKETS' => 'ws-only',
63 'RAILS_ENV' => 'test',
65 if not system(env, *cmd)
66 raise RuntimeError, "Command exited #{$?}: #{cmd.inspect}"
71 def ws_helper(token: nil, timeout: 8)
78 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket?api_token=#{api_client_authorizations(token).api_token}")
80 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket")
83 ws.on :open do |event|
86 EM::Timer.new(timeout) do
87 too_long = true if close_status.nil?
93 ws.on :error do |event|
94 STDERR.puts "websocket client error: #{event.inspect}"
97 ws.on :close do |event|
98 close_status = [:close, event.code, event.reason]
105 assert opened, "Should have opened web socket"
106 assert (not too_long), "Test took too long"
107 assert_equal 1000, close_status[1], "Connection closed unexpectedly (check log for errors)"
110 test "connect with no token" do
114 ws.on :message do |event|
115 d = Oj.strict_load event.data
121 assert_equal 401, status
124 test "connect, subscribe and get response" do
127 ws_helper(token: :active) do |ws|
128 ws.on :open do |event|
129 ws.send ({method: 'subscribe'}.to_json)
132 ws.on :message do |event|
133 d = Oj.strict_load event.data
139 assert_equal 200, status
147 authorize_with :active
149 ws_helper(token: :active) do |ws|
150 ws.on :open do |event|
151 ws.send ({method: 'subscribe'}.to_json)
154 ws.on :message do |event|
155 d = Oj.strict_load event.data
158 assert_equal 200, d["status"]
159 spec = Specimen.create
162 ev_uuid = d["object_uuid"]
170 assert_equal spec.uuid, ev_uuid
173 test "connect, subscribe, get event" do
177 test "connect, subscribe, get two events" do
184 authorize_with :active
186 ws_helper(token: :active) do |ws|
187 ws.on :open do |event|
188 ws.send ({method: 'subscribe'}.to_json)
191 ws.on :message do |event|
192 d = Oj.strict_load event.data
195 assert_equal 200, d["status"]
196 spec = Specimen.create
200 spec_ev_uuid = d["object_uuid"]
203 human_ev_uuid = d["object_uuid"]
207 assert false, "Should not get any more events"
215 assert_equal spec.uuid, spec_ev_uuid
216 assert_equal human.uuid, human_ev_uuid
219 test "connect, subscribe, filter events" do
224 authorize_with :active
226 ws_helper(token: :active) do |ws|
227 ws.on :open do |event|
228 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
231 ws.on :message do |event|
232 d = Oj.strict_load event.data
235 assert_equal 200, d["status"]
240 human_ev_uuid = d["object_uuid"]
244 assert false, "Should not get any more events"
251 assert_equal human.uuid, human_ev_uuid
255 test "connect, subscribe, multiple filters" do
262 authorize_with :active
264 ws_helper(token: :active) do |ws|
265 ws.on :open do |event|
266 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
267 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#specimen']]}.to_json)
270 ws.on :message do |event|
271 d = Oj.strict_load event.data
274 assert_equal 200, d["status"]
277 assert_equal 200, d["status"]
278 spec = Specimen.create
279 Trait.create # not part of filters, should not be received
283 spec_ev_uuid = d["object_uuid"]
286 human_ev_uuid = d["object_uuid"]
290 assert false, "Should not get any more events"
298 assert_equal spec.uuid, spec_ev_uuid
299 assert_equal human.uuid, human_ev_uuid
303 test "connect, subscribe, compound filter" do
307 authorize_with :active
309 ws_helper(token: :active) do |ws|
310 ws.on :open do |event|
311 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#trait'], ['event_type', '=', 'update']]}.to_json)
314 ws.on :message do |event|
315 d = Oj.strict_load event.data
318 assert_equal 200, d["status"]
319 t1 = Trait.create("name" => "foo")
324 assert_equal 'update', d['event_type']
328 assert false, "Should not get any more events"
334 assert_equal 3, state
338 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 slow_test "connect, subscribe, get event, unsubscribe" do
383 authorize_with :active
385 ws_helper(token: :active, timeout: false) do |ws|
386 ws.on :open do |event|
387 ws.send ({method: 'subscribe'}.to_json)
389 # Set a time limit on the test because after unsubscribing the server
390 # still has to process the next event (and then hopefully correctly
391 # decides not to send it because we unsubscribed.)
396 ws.on :message do |event|
397 d = Oj.strict_load event.data
400 assert_equal 200, d["status"]
401 spec = Specimen.create
404 spec_ev_uuid = d["object_uuid"]
405 ws.send ({method: 'unsubscribe'}.to_json)
413 assert_equal 200, d["status"]
416 assert false, "Should not get any more events"
423 assert_equal spec.uuid, spec_ev_uuid
426 slow_test "connect, subscribe, get event, unsubscribe with filter" do
431 authorize_with :active
433 ws_helper(token: :active, timeout: false) do |ws|
434 ws.on :open do |event|
435 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
437 # Set a time limit on the test because after unsubscribing the server
438 # still has to process the next event (and then hopefully correctly
439 # decides not to send it because we unsubscribed.)
444 ws.on :message do |event|
445 d = Oj.strict_load event.data
448 assert_equal 200, d["status"]
452 spec_ev_uuid = d["object_uuid"]
453 ws.send ({method: 'unsubscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
461 assert_equal 200, d["status"]
464 assert false, "Should not get any more events"
471 assert_equal spec.uuid, spec_ev_uuid
475 slow_test "connect, subscribe, get event, try to unsubscribe with bogus filter" do
482 authorize_with :active
484 ws_helper(token: :active) do |ws|
485 ws.on :open do |event|
486 ws.send ({method: 'subscribe'}.to_json)
489 ws.on :message do |event|
490 d = Oj.strict_load event.data
493 assert_equal 200, d["status"]
494 spec = Specimen.create
497 spec_ev_uuid = d["object_uuid"]
498 ws.send ({method: 'unsubscribe', filters: [['foo', 'bar', 'baz']]}.to_json)
506 assert_equal 404, d["status"]
509 human_ev_uuid = d["object_uuid"]
513 assert false, "Should not get any more events"
521 assert_equal spec.uuid, spec_ev_uuid
522 assert_equal human.uuid, human_ev_uuid
525 slow_test "connected, not subscribed, no event" do
526 authorize_with :active
528 ws_helper(token: :active, timeout: false) do |ws|
529 ws.on :open do |event|
539 ws.on :message do |event|
540 assert false, "Should not get any messages, message was #{event.data}"
545 slow_test "connected, not authorized to see event" do
548 authorize_with :admin
550 ws_helper(token: :active, timeout: false) do |ws|
551 ws.on :open do |event|
552 ws.send ({method: 'subscribe'}.to_json)
559 ws.on :message do |event|
560 d = Oj.strict_load event.data
563 assert_equal 200, d["status"]
567 assert false, "Should not get any messages, message was #{event.data}"
575 test "connect, try bogus method" do
578 ws_helper(token: :active) do |ws|
579 ws.on :open do |event|
580 ws.send ({method: 'frobnabble'}.to_json)
583 ws.on :message do |event|
584 d = Oj.strict_load event.data
590 assert_equal 400, status
593 test "connect, missing method" do
596 ws_helper(token: :active) do |ws|
597 ws.on :open do |event|
598 ws.send ({fizzbuzz: 'frobnabble'}.to_json)
601 ws.on :message do |event|
602 d = Oj.strict_load event.data
608 assert_equal 400, status
611 test "connect, send malformed request" do
614 ws_helper(token: :active) do |ws|
615 ws.on :open do |event|
616 ws.send '<XML4EVER></XML4EVER>'
619 ws.on :message do |event|
620 d = Oj.strict_load event.data
626 assert_equal 400, status
630 test "connect, try subscribe too many filters" do
633 authorize_with :active
635 ws_helper(token: :active) do |ws|
636 ws.on :open do |event|
638 ws.send ({method: 'subscribe', filters: [['object_uuid', '=', i]]}.to_json)
642 ws.on :message do |event|
643 d = Oj.strict_load event.data
645 when (1..Rails.configuration.websocket_max_filters)
646 assert_equal 200, d["status"]
648 when (Rails.configuration.websocket_max_filters+1)
649 assert_equal 403, d["status"]
656 assert_equal Rails.configuration.websocket_max_filters+1, state
660 slow_test "connect, subscribe, lots of events" do
663 log_start = Log.order(:id).last.id
665 authorize_with :active
667 ws_helper(token: :active, timeout: false) do |ws|
669 # Needs a longer timeout than the default
673 ws.on :open do |event|
674 ws.send ({method: 'subscribe'}.to_json)
677 ws.on :message do |event|
678 d = Oj.strict_load event.data
681 assert_equal 200, d["status"]
682 ActiveRecord::Base.transaction do
690 assert_equal d['id'], event_count+log_start
691 if event_count == 202
699 assert_equal 202, event_count
703 test "connect, subscribe with invalid filter" do
706 authorize_with :active
708 ws_helper(token: :active) do |ws|
709 ws.on :open do |event|
710 # test that #6451 is fixed (invalid filter crashes websockets)
711 ws.send ({method: 'subscribe', filters: [['object_blarg', 'is_a', 'arvados#human']]}.to_json)
714 ws.on :message do |event|
715 d = Oj.strict_load event.data
718 assert_equal 200, d["status"]
723 assert_equal 500, d["status"]
727 assert false, "Should not get any more events"
733 assert_equal 3, state
735 # Try connecting again, ensure that websockets server is still running and
736 # didn't crash per #6451