1 require 'database_cleaner'
6 DatabaseCleaner.strategy = :deletion
8 class WebsocketTest < ActionDispatch::IntegrationTest
9 self.use_transactional_fixtures = false
20 s = TCPServer.new('0.0.0.0', 0)
23 @@pidfile = "tmp/pids/passenger.#{@@port}.pid"
25 Dir.chdir(Rails.root) do |apidir|
26 # Only passenger seems to be able to run the websockets server
28 _system('passenger', 'start', '-d',
30 "--log-file", "/dev/stderr",
31 "--pid-file", @@pidfile)
32 timeout = Time.now.tv_sec + 10
36 server_pid = IO.read(@@pidfile).to_i
37 good_pid = (server_pid > 0) and (Process.kill(0, pid) rescue false)
41 end while (not good_pid) and (Time.now.tv_sec < timeout)
43 raise RuntimeError, "could not find API server Rails pid"
45 STDERR.puts "Started websocket server on port #{@@port} with pid #{server_pid}"
50 Dir.chdir(Rails.root) do
51 _system('passenger', 'stop', "-p#{@@port}",
52 "--pid-file", @@pidfile)
54 # DatabaseCleaner leaves the database empty. Prefer to leave it full.
55 dc = DatabaseController.new
56 dc.define_singleton_method :render do |*args| end
60 def self._system(*cmd)
61 Bundler.with_clean_env do
63 'ARVADOS_WEBSOCKETS' => 'ws-only',
64 'RAILS_ENV' => 'test',
66 if not system(env, *cmd)
67 raise RuntimeError, "Command exited #{$?}: #{cmd.inspect}"
72 def ws_helper(token: nil, timeout: 8)
79 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket?api_token=#{api_client_authorizations(token).api_token}")
81 ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket")
84 ws.on :open do |event|
87 EM::Timer.new(timeout) do
88 too_long = true if close_status.nil?
94 ws.on :error do |event|
95 STDERR.puts "websocket client error: #{event.inspect}"
98 ws.on :close do |event|
99 close_status = [:close, event.code, event.reason]
106 assert opened, "Should have opened web socket"
107 assert (not too_long), "Test took too long"
108 assert_equal 1000, close_status[1], "Connection closed unexpectedly (check log for errors)"
111 test "connect with no token" do
115 ws.on :message do |event|
116 d = SafeJSON.load event.data
122 assert_equal 401, status
125 test "connect, subscribe and get response" do
128 ws_helper(token: :active) do |ws|
129 ws.on :open do |event|
130 ws.send ({method: 'subscribe'}.to_json)
133 ws.on :message do |event|
134 d = SafeJSON.load event.data
140 assert_equal 200, status
148 authorize_with :active
150 ws_helper(token: :active) do |ws|
151 ws.on :open do |event|
152 ws.send ({method: 'subscribe'}.to_json)
155 ws.on :message do |event|
156 d = SafeJSON.load event.data
159 assert_equal 200, d["status"]
160 spec = Specimen.create
163 ev_uuid = d["object_uuid"]
171 assert_equal spec.uuid, ev_uuid
174 test "connect, subscribe, get event" do
178 test "connect, subscribe, get two events" do
185 authorize_with :active
187 ws_helper(token: :active) do |ws|
188 ws.on :open do |event|
189 ws.send ({method: 'subscribe'}.to_json)
192 ws.on :message do |event|
193 d = SafeJSON.load event.data
196 assert_equal 200, d["status"]
197 spec = Specimen.create
201 spec_ev_uuid = d["object_uuid"]
204 human_ev_uuid = d["object_uuid"]
208 assert false, "Should not get any more events"
216 assert_equal spec.uuid, spec_ev_uuid
217 assert_equal human.uuid, human_ev_uuid
220 test "connect, subscribe, filter events" do
225 authorize_with :active
227 ws_helper(token: :active) do |ws|
228 ws.on :open do |event|
229 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
232 ws.on :message do |event|
233 d = SafeJSON.load event.data
236 assert_equal 200, d["status"]
241 human_ev_uuid = d["object_uuid"]
245 assert false, "Should not get any more events"
252 assert_equal human.uuid, human_ev_uuid
256 test "connect, subscribe, multiple filters" do
263 authorize_with :active
265 ws_helper(token: :active) do |ws|
266 ws.on :open do |event|
267 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
268 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#specimen']]}.to_json)
271 ws.on :message do |event|
272 d = SafeJSON.load event.data
275 assert_equal 200, d["status"]
278 assert_equal 200, d["status"]
279 spec = Specimen.create
280 Trait.create # not part of filters, should not be received
284 spec_ev_uuid = d["object_uuid"]
287 human_ev_uuid = d["object_uuid"]
291 assert false, "Should not get any more events"
299 assert_equal spec.uuid, spec_ev_uuid
300 assert_equal human.uuid, human_ev_uuid
304 test "connect, subscribe, compound filter" do
308 authorize_with :active
310 ws_helper(token: :active) do |ws|
311 ws.on :open do |event|
312 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#trait'], ['event_type', '=', 'update']]}.to_json)
315 ws.on :message do |event|
316 d = SafeJSON.load event.data
319 assert_equal 200, d["status"]
320 t1 = Trait.create("name" => "foo")
325 assert_equal 'update', d['event_type']
329 assert false, "Should not get any more events"
335 assert_equal 3, state
339 test "connect, subscribe, ask events starting at seq num" do
342 authorize_with :active
344 lastid = logs(:admin_changes_specimen).id
348 ws_helper(token: :active) do |ws|
349 ws.on :open do |event|
350 ws.send ({method: 'subscribe', last_log_id: lastid}.to_json)
353 ws.on :message do |event|
354 d = SafeJSON.load event.data
357 assert_equal 200, d["status"]
360 l1 = d["object_uuid"]
361 assert_not_nil l1, "Unexpected message: #{d}"
364 l2 = d["object_uuid"]
365 assert_not_nil l2, "Unexpected message: #{d}"
369 assert false, "Should not get any more events"
374 expect_next_logs = Log.where('id > ?', lastid).order('id asc')
375 assert_equal expect_next_logs[0].object_uuid, l1
376 assert_equal expect_next_logs[1].object_uuid, l2
379 slow_test "connect, subscribe, get event, unsubscribe" do
384 authorize_with :active
386 ws_helper(token: :active, timeout: false) do |ws|
387 ws.on :open do |event|
388 ws.send ({method: 'subscribe'}.to_json)
390 # Set a time limit on the test because after unsubscribing the server
391 # still has to process the next event (and then hopefully correctly
392 # decides not to send it because we unsubscribed.)
397 ws.on :message do |event|
398 d = SafeJSON.load event.data
401 assert_equal 200, d["status"]
402 spec = Specimen.create
405 spec_ev_uuid = d["object_uuid"]
406 ws.send ({method: 'unsubscribe'}.to_json)
414 assert_equal 200, d["status"]
417 assert false, "Should not get any more events"
424 assert_equal spec.uuid, spec_ev_uuid
427 slow_test "connect, subscribe, get event, unsubscribe with filter" do
432 authorize_with :active
434 ws_helper(token: :active, timeout: false) do |ws|
435 ws.on :open do |event|
436 ws.send ({method: 'subscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
438 # Set a time limit on the test because after unsubscribing the server
439 # still has to process the next event (and then hopefully correctly
440 # decides not to send it because we unsubscribed.)
445 ws.on :message do |event|
446 d = SafeJSON.load event.data
449 assert_equal 200, d["status"]
453 spec_ev_uuid = d["object_uuid"]
454 ws.send ({method: 'unsubscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
462 assert_equal 200, d["status"]
465 assert false, "Should not get any more events"
472 assert_equal spec.uuid, spec_ev_uuid
476 slow_test "connect, subscribe, get event, try to unsubscribe with bogus filter" do
483 authorize_with :active
485 ws_helper(token: :active) do |ws|
486 ws.on :open do |event|
487 ws.send ({method: 'subscribe'}.to_json)
490 ws.on :message do |event|
491 d = SafeJSON.load event.data
494 assert_equal 200, d["status"]
495 spec = Specimen.create
498 spec_ev_uuid = d["object_uuid"]
499 ws.send ({method: 'unsubscribe', filters: [['foo', 'bar', 'baz']]}.to_json)
507 assert_equal 404, d["status"]
510 human_ev_uuid = d["object_uuid"]
514 assert false, "Should not get any more events"
522 assert_equal spec.uuid, spec_ev_uuid
523 assert_equal human.uuid, human_ev_uuid
526 slow_test "connected, not subscribed, no event" do
527 authorize_with :active
529 ws_helper(token: :active, timeout: false) do |ws|
530 ws.on :open do |event|
540 ws.on :message do |event|
541 assert false, "Should not get any messages, message was #{event.data}"
546 slow_test "connected, not authorized to see event" do
549 authorize_with :admin
551 ws_helper(token: :active, timeout: false) do |ws|
552 ws.on :open do |event|
553 ws.send ({method: 'subscribe'}.to_json)
560 ws.on :message do |event|
561 d = SafeJSON.load event.data
564 assert_equal 200, d["status"]
568 assert false, "Should not get any messages, message was #{event.data}"
576 test "connect, try bogus method" do
579 ws_helper(token: :active) do |ws|
580 ws.on :open do |event|
581 ws.send ({method: 'frobnabble'}.to_json)
584 ws.on :message do |event|
585 d = SafeJSON.load event.data
591 assert_equal 400, status
594 test "connect, missing method" do
597 ws_helper(token: :active) do |ws|
598 ws.on :open do |event|
599 ws.send ({fizzbuzz: 'frobnabble'}.to_json)
602 ws.on :message do |event|
603 d = SafeJSON.load event.data
609 assert_equal 400, status
612 test "connect, send malformed request" do
615 ws_helper(token: :active) do |ws|
616 ws.on :open do |event|
617 ws.send '<XML4EVER></XML4EVER>'
620 ws.on :message do |event|
621 d = SafeJSON.load event.data
627 assert_equal 400, status
631 test "connect, try subscribe too many filters" do
634 authorize_with :active
636 ws_helper(token: :active) do |ws|
637 ws.on :open do |event|
639 ws.send ({method: 'subscribe', filters: [['object_uuid', '=', i]]}.to_json)
643 ws.on :message do |event|
644 d = SafeJSON.load event.data
646 when (1..Rails.configuration.websocket_max_filters)
647 assert_equal 200, d["status"]
649 when (Rails.configuration.websocket_max_filters+1)
650 assert_equal 403, d["status"]
657 assert_equal Rails.configuration.websocket_max_filters+1, state
661 slow_test "connect, subscribe, lots of events" do
664 log_start = Log.order(:id).last.id
666 authorize_with :active
668 ws_helper(token: :active, timeout: false) do |ws|
670 # Needs a longer timeout than the default
674 ws.on :open do |event|
675 ws.send ({method: 'subscribe'}.to_json)
678 ws.on :message do |event|
679 d = SafeJSON.load event.data
682 assert_equal 200, d["status"]
683 ActiveRecord::Base.transaction do
691 assert_equal d['id'], event_count+log_start
692 if event_count == 202
700 assert_equal 202, event_count
704 test "connect, subscribe with invalid filter" do
707 authorize_with :active
709 ws_helper(token: :active) do |ws|
710 ws.on :open do |event|
711 # test that #6451 is fixed (invalid filter crashes websockets)
712 ws.send ({method: 'subscribe', filters: [['object_blarg', 'is_a', 'arvados#human']]}.to_json)
715 ws.on :message do |event|
716 d = SafeJSON.load event.data
719 assert_equal 200, d["status"]
724 assert_equal 500, d["status"]
728 assert false, "Should not get any more events"
734 assert_equal 3, state
736 # Try connecting again, ensure that websockets server is still running and
737 # didn't crash per #6451