5737: Send passenger logs to console
[arvados.git] / services / api / test / integration / websocket_test.rb
1 require 'test_helper'
2 require 'oj'
3 require 'database_cleaner'
4
5 DatabaseCleaner.strategy = :deletion
6
7 class WebsocketTest < ActionDispatch::IntegrationTest
8   self.use_transactional_fixtures = false
9
10   setup do
11     DatabaseCleaner.start
12   end
13
14   teardown do
15     DatabaseCleaner.clean
16   end
17
18   def self.startup
19     s = TCPServer.new('0.0.0.0', 0)
20     @@port = s.addr[1]
21     s.close
22     pidfile = "tmp/pids/passenger.#{@@port}.pid"
23     DatabaseCleaner.start
24     Dir.chdir(Rails.root) do |apidir|
25       # Only passenger seems to be able to run the websockets server
26       # successfully.
27       _system('passenger', 'start', '-d',
28               "-p#{@@port}",
29               "--log-file", "/dev/stderr")
30       timeout = Time.now.tv_sec + 10
31       begin
32         sleep 0.2
33         begin
34           server_pid = IO.read(pidfile).to_i
35           good_pid = (server_pid > 0) and (Process.kill(0, pid) rescue false)
36         rescue Errno::ENOENT
37           good_pid = false
38         end
39       end while (not good_pid) and (Time.now.tv_sec < timeout)
40       if not good_pid
41         raise RuntimeError, "could not find API server Rails pid"
42       end
43       STDERR.puts "Started websocket server on port #{@@port} with pid #{server_pid}"
44     end
45   end
46
47   def self.shutdown
48     Dir.chdir(Rails.root) do
49       _system('passenger', 'stop', "-p#{@@port}")
50     end
51     # DatabaseCleaner leaves the database empty. Prefer to leave it full.
52     dc = DatabaseController.new
53     dc.define_singleton_method :render do |*args| end
54     dc.reset
55   end
56
57   def self._system(*cmd)
58     Bundler.with_clean_env do
59       env = {
60         'ARVADOS_WEBSOCKETS' => 'ws-only',
61         'RAILS_ENV' => 'test',
62       }
63       if not system(env, *cmd)
64         raise RuntimeError, "Command exited #{$?}: #{cmd.inspect}"
65       end
66     end
67   end
68
69   def ws_helper(token: nil, timeout: 8)
70     opened = false
71     close_status = nil
72     too_long = false
73
74     EM.run do
75       if token
76         ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket?api_token=#{api_client_authorizations(token).api_token}")
77       else
78         ws = Faye::WebSocket::Client.new("ws://localhost:#{@@port}/websocket")
79       end
80
81       ws.on :open do |event|
82         opened = true
83         if timeout
84           EM::Timer.new(timeout) do
85             too_long = true if close_status.nil?
86             EM.stop_event_loop
87           end
88         end
89       end
90
91       ws.on :error do |event|
92         STDERR.puts "websocket client error: #{event.inspect}"
93       end
94
95       ws.on :close do |event|
96         close_status = [:close, event.code, event.reason]
97         EM.stop_event_loop
98       end
99
100       yield ws
101     end
102
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)"
106   end
107
108   test "connect with no token" do
109     status = nil
110
111     ws_helper do |ws|
112       ws.on :message do |event|
113         d = Oj.strict_load event.data
114         status = d["status"]
115         ws.close
116       end
117     end
118
119     assert_equal 401, status
120   end
121
122   test "connect, subscribe and get response" do
123     status = nil
124
125     ws_helper(token: :active) do |ws|
126       ws.on :open do |event|
127         ws.send ({method: 'subscribe'}.to_json)
128       end
129
130       ws.on :message do |event|
131         d = Oj.strict_load event.data
132         status = d["status"]
133         ws.close
134       end
135     end
136
137     assert_equal 200, status
138   end
139
140   def subscribe_test
141     state = 1
142     spec = nil
143     ev_uuid = nil
144
145     authorize_with :active
146
147     ws_helper(token: :active) do |ws|
148       ws.on :open do |event|
149         ws.send ({method: 'subscribe'}.to_json)
150       end
151
152       ws.on :message do |event|
153         d = Oj.strict_load event.data
154         case state
155         when 1
156           assert_equal 200, d["status"]
157           spec = Specimen.create
158           state = 2
159         when 2
160           ev_uuid = d["object_uuid"]
161           ws.close
162         end
163       end
164
165     end
166
167     assert_not_nil spec
168     assert_equal spec.uuid, ev_uuid
169   end
170
171   test "connect, subscribe, get event" do
172     subscribe_test()
173   end
174
175   test "connect, subscribe, get two events" do
176     state = 1
177     spec = nil
178     human = nil
179     spec_ev_uuid = nil
180     human_ev_uuid = nil
181
182     authorize_with :active
183
184     ws_helper(token: :active) do |ws|
185       ws.on :open do |event|
186         ws.send ({method: 'subscribe'}.to_json)
187       end
188
189       ws.on :message do |event|
190         d = Oj.strict_load event.data
191         case state
192         when 1
193           assert_equal 200, d["status"]
194           spec = Specimen.create
195           human = Human.create
196           state = 2
197         when 2
198           spec_ev_uuid = d["object_uuid"]
199           state = 3
200         when 3
201           human_ev_uuid = d["object_uuid"]
202           state = 4
203           ws.close
204         when 4
205           assert false, "Should not get any more events"
206         end
207       end
208
209     end
210
211     assert_not_nil spec
212     assert_not_nil human
213     assert_equal spec.uuid, spec_ev_uuid
214     assert_equal human.uuid, human_ev_uuid
215   end
216
217   test "connect, subscribe, filter events" do
218     state = 1
219     human = nil
220     human_ev_uuid = nil
221
222     authorize_with :active
223
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)
227       end
228
229       ws.on :message do |event|
230         d = Oj.strict_load event.data
231         case state
232         when 1
233           assert_equal 200, d["status"]
234           Specimen.create
235           human = Human.create
236           state = 2
237         when 2
238           human_ev_uuid = d["object_uuid"]
239           state = 3
240           ws.close
241         when 3
242           assert false, "Should not get any more events"
243         end
244       end
245
246     end
247
248     assert_not_nil human
249     assert_equal human.uuid, human_ev_uuid
250   end
251
252
253   test "connect, subscribe, multiple filters" do
254     state = 1
255     spec = nil
256     human = nil
257     spec_ev_uuid = nil
258     human_ev_uuid = nil
259
260     authorize_with :active
261
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)
266       end
267
268       ws.on :message do |event|
269         d = Oj.strict_load event.data
270         case state
271         when 1
272           assert_equal 200, d["status"]
273           state = 2
274         when 2
275           assert_equal 200, d["status"]
276           spec = Specimen.create
277           Trait.create # not part of filters, should not be received
278           human = Human.create
279           state = 3
280         when 3
281           spec_ev_uuid = d["object_uuid"]
282           state = 4
283         when 4
284           human_ev_uuid = d["object_uuid"]
285           state = 5
286           ws.close
287         when 5
288           assert false, "Should not get any more events"
289         end
290       end
291
292     end
293
294     assert_not_nil spec
295     assert_not_nil human
296     assert_equal spec.uuid, spec_ev_uuid
297     assert_equal human.uuid, human_ev_uuid
298   end
299
300
301   test "connect, subscribe, compound filter" do
302     state = 1
303     t1 = nil
304
305     authorize_with :active
306
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)
310       end
311
312       ws.on :message do |event|
313         d = Oj.strict_load event.data
314         case state
315         when 1
316           assert_equal 200, d["status"]
317           t1 = Trait.create("name" => "foo")
318           t1.name = "bar"
319           t1.save!
320           state = 2
321          when 2
322           assert_equal 'update', d['event_type']
323           state = 3
324           ws.close
325         when 3
326           assert false, "Should not get any more events"
327         end
328       end
329
330     end
331
332     assert_equal 3, state
333     assert_not_nil t1
334   end
335
336   test "connect, subscribe, ask events starting at seq num" do
337     state = 1
338     human = nil
339     human_ev_uuid = nil
340
341     authorize_with :active
342
343     lastid = logs(:admin_changes_specimen).id
344     l1 = nil
345     l2 = nil
346
347     ws_helper(token: :active) do |ws|
348       ws.on :open do |event|
349         ws.send ({method: 'subscribe', last_log_id: lastid}.to_json)
350       end
351
352       ws.on :message do |event|
353         d = Oj.strict_load event.data
354         case state
355         when 1
356           assert_equal 200, d["status"]
357           state = 2
358         when 2
359           l1 = d["object_uuid"]
360           assert_not_nil l1, "Unexpected message: #{d}"
361           state = 3
362         when 3
363           l2 = d["object_uuid"]
364           assert_not_nil l2, "Unexpected message: #{d}"
365           state = 4
366           ws.close
367         when 4
368           assert false, "Should not get any more events"
369         end
370       end
371     end
372
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
376   end
377
378   test "connect, subscribe, get event, unsubscribe" do
379     slow_test
380     state = 1
381     spec = nil
382     spec_ev_uuid = nil
383     filter_id = nil
384
385     authorize_with :active
386
387     ws_helper(token: :active, timeout: false) do |ws|
388       ws.on :open do |event|
389         ws.send ({method: 'subscribe'}.to_json)
390         EM::Timer.new 3 do
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.)
394           ws.close
395         end
396       end
397
398       ws.on :message do |event|
399         d = Oj.strict_load event.data
400         case state
401         when 1
402           assert_equal 200, d["status"]
403           spec = Specimen.create
404           state = 2
405         when 2
406           spec_ev_uuid = d["object_uuid"]
407           ws.send ({method: 'unsubscribe'}.to_json)
408
409           EM::Timer.new 1 do
410             Specimen.create
411           end
412
413           state = 3
414         when 3
415           assert_equal 200, d["status"]
416           state = 4
417         when 4
418           assert false, "Should not get any more events"
419         end
420       end
421
422     end
423
424     assert_not_nil spec
425     assert_equal spec.uuid, spec_ev_uuid
426   end
427
428   test "connect, subscribe, get event, unsubscribe with filter" do
429     slow_test
430     state = 1
431     spec = nil
432     spec_ev_uuid = nil
433
434     authorize_with :active
435
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)
439         EM::Timer.new 6 do
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.)
443           ws.close
444         end
445       end
446
447       ws.on :message do |event|
448         d = Oj.strict_load event.data
449         case state
450         when 1
451           assert_equal 200, d["status"]
452           spec = Human.create
453           state = 2
454         when 2
455           spec_ev_uuid = d["object_uuid"]
456           ws.send ({method: 'unsubscribe', filters: [['object_uuid', 'is_a', 'arvados#human']]}.to_json)
457
458           EM::Timer.new 1 do
459             Human.create
460           end
461
462           state = 3
463         when 3
464           assert_equal 200, d["status"]
465           state = 4
466         when 4
467           assert false, "Should not get any more events"
468         end
469       end
470
471     end
472
473     assert_not_nil spec
474     assert_equal spec.uuid, spec_ev_uuid
475   end
476
477
478   test "connect, subscribe, get event, try to unsubscribe with bogus filter" do
479     slow_test
480     state = 1
481     spec = nil
482     spec_ev_uuid = nil
483     human = nil
484     human_ev_uuid = nil
485
486     authorize_with :active
487
488     ws_helper(token: :active) do |ws|
489       ws.on :open do |event|
490         ws.send ({method: 'subscribe'}.to_json)
491       end
492
493       ws.on :message do |event|
494         d = Oj.strict_load event.data
495         case state
496         when 1
497           assert_equal 200, d["status"]
498           spec = Specimen.create
499           state = 2
500         when 2
501           spec_ev_uuid = d["object_uuid"]
502           ws.send ({method: 'unsubscribe', filters: [['foo', 'bar', 'baz']]}.to_json)
503
504           EM::Timer.new 1 do
505             human = Human.create
506           end
507
508           state = 3
509         when 3
510           assert_equal 404, d["status"]
511           state = 4
512         when 4
513           human_ev_uuid = d["object_uuid"]
514           state = 5
515           ws.close
516         when 5
517           assert false, "Should not get any more events"
518         end
519       end
520
521     end
522
523     assert_not_nil spec
524     assert_not_nil human
525     assert_equal spec.uuid, spec_ev_uuid
526     assert_equal human.uuid, human_ev_uuid
527   end
528
529
530
531   test "connected, not subscribed, no event" do
532     slow_test
533     authorize_with :active
534
535     ws_helper(token: :active, timeout: false) do |ws|
536       ws.on :open do |event|
537         EM::Timer.new 1 do
538           Specimen.create
539         end
540
541         EM::Timer.new 3 do
542           ws.close
543         end
544       end
545
546       ws.on :message do |event|
547         assert false, "Should not get any messages, message was #{event.data}"
548       end
549     end
550   end
551
552   test "connected, not authorized to see event" do
553     slow_test
554     state = 1
555
556     authorize_with :admin
557
558     ws_helper(token: :active, timeout: false) do |ws|
559       ws.on :open do |event|
560         ws.send ({method: 'subscribe'}.to_json)
561
562         EM::Timer.new 3 do
563           ws.close
564         end
565       end
566
567       ws.on :message do |event|
568         d = Oj.strict_load event.data
569         case state
570         when 1
571           assert_equal 200, d["status"]
572           Specimen.create
573           state = 2
574         when 2
575           assert false, "Should not get any messages, message was #{event.data}"
576         end
577       end
578
579     end
580
581   end
582
583   test "connect, try bogus method" do
584     status = nil
585
586     ws_helper(token: :active) do |ws|
587       ws.on :open do |event|
588         ws.send ({method: 'frobnabble'}.to_json)
589       end
590
591       ws.on :message do |event|
592         d = Oj.strict_load event.data
593         status = d["status"]
594         ws.close
595       end
596     end
597
598     assert_equal 400, status
599   end
600
601   test "connect, missing method" do
602     status = nil
603
604     ws_helper(token: :active) do |ws|
605       ws.on :open do |event|
606         ws.send ({fizzbuzz: 'frobnabble'}.to_json)
607       end
608
609       ws.on :message do |event|
610         d = Oj.strict_load event.data
611         status = d["status"]
612         ws.close
613       end
614     end
615
616     assert_equal 400, status
617   end
618
619   test "connect, send malformed request" do
620     status = nil
621
622     ws_helper(token: :active) do |ws|
623       ws.on :open do |event|
624         ws.send '<XML4EVER></XML4EVER>'
625       end
626
627       ws.on :message do |event|
628         d = Oj.strict_load event.data
629         status = d["status"]
630         ws.close
631       end
632     end
633
634     assert_equal 400, status
635   end
636
637
638   test "connect, try subscribe too many filters" do
639     state = 1
640
641     authorize_with :active
642
643     ws_helper(token: :active) do |ws|
644       ws.on :open do |event|
645         (1..17).each do |i|
646           ws.send ({method: 'subscribe', filters: [['object_uuid', '=', i]]}.to_json)
647         end
648       end
649
650       ws.on :message do |event|
651         d = Oj.strict_load event.data
652         case state
653         when (1..Rails.configuration.websocket_max_filters)
654           assert_equal 200, d["status"]
655           state += 1
656         when (Rails.configuration.websocket_max_filters+1)
657           assert_equal 403, d["status"]
658           ws.close
659         end
660       end
661
662     end
663
664     assert_equal Rails.configuration.websocket_max_filters+1, state
665
666   end
667
668   test "connect, subscribe, lots of events" do
669     slow_test
670     state = 1
671     event_count = 0
672     log_start = Log.order(:id).last.id
673
674     authorize_with :active
675
676     ws_helper(token: :active, timeout: false) do |ws|
677       EM::Timer.new 45 do
678         # Needs a longer timeout than the default
679         ws.close
680       end
681
682       ws.on :open do |event|
683         ws.send ({method: 'subscribe'}.to_json)
684       end
685
686       ws.on :message do |event|
687         d = Oj.strict_load event.data
688         case state
689         when 1
690           assert_equal 200, d["status"]
691           ActiveRecord::Base.transaction do
692             (1..202).each do
693               spec = Specimen.create
694             end
695           end
696           state = 2
697         when 2
698           event_count += 1
699           assert_equal d['id'], event_count+log_start
700           if event_count == 202
701             ws.close
702           end
703         end
704       end
705
706     end
707
708     assert_equal 202, event_count
709   end
710
711
712   test "connect, subscribe with invalid filter" do
713     state = 1
714     human = nil
715     human_ev_uuid = nil
716
717     authorize_with :active
718
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)
723       end
724
725       ws.on :message do |event|
726         d = Oj.strict_load event.data
727         case state
728         when 1
729           assert_equal 200, d["status"]
730           Specimen.create
731           human = Human.create
732           state = 2
733         when 2
734           assert_equal 500, d["status"]
735           state = 3
736           ws.close
737         when 3
738           assert false, "Should not get any more events"
739         end
740       end
741
742     end
743
744     assert_equal 3, state
745
746     # Try connecting again, ensure that websockets server is still running and
747     # didn't crash per #6451
748     subscribe_test()
749
750   end
751
752
753 end