16750: Avoids using params on requests to make it compatible with federation.
[arvados.git] / apps / workbench / test / controllers / application_controller_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class ApplicationControllerTest < ActionController::TestCase
8   # These tests don't do state-changing API calls. Save some time by
9   # skipping the database reset.
10   reset_api_fixtures :after_each_test, false
11   reset_api_fixtures :after_suite, true
12
13   setup do
14     @user_dataclass = ArvadosBase.resource_class_for_uuid(api_fixture('users')['active']['uuid'])
15   end
16
17   test "links for object" do
18     use_token :active
19
20     ac = ApplicationController.new
21
22     link_head_uuid = api_fixture('links')['foo_file_readable_by_active']['head_uuid']
23
24     links = ac.send :links_for_object, link_head_uuid
25
26     assert links, 'Expected links'
27     assert links.is_a?(Array), 'Expected an array'
28     assert links.size > 0, 'Expected at least one link'
29     assert links[0][:uuid], 'Expected uuid for the head_link'
30   end
31
32   test "preload links for objects and uuids" do
33     use_token :active
34
35     ac = ApplicationController.new
36
37     link1_head_uuid = api_fixture('links')['foo_file_readable_by_active']['head_uuid']
38     link2_uuid = api_fixture('links')['bar_file_readable_by_active']['uuid']
39     link3_head_uuid = api_fixture('links')['bar_file_readable_by_active']['head_uuid']
40
41     link2_object = User.find(api_fixture('users')['active']['uuid'])
42     link2_object_uuid = link2_object['uuid']
43
44     uuids = [link1_head_uuid, link2_object, link3_head_uuid]
45     links = ac.send :preload_links_for_objects, uuids
46
47     assert links, 'Expected links'
48     assert links.is_a?(Hash), 'Expected a hash'
49     assert links.size == 3, 'Expected two objects in the preloaded links hash'
50     assert links[link1_head_uuid], 'Expected links for the passed in link head_uuid'
51     assert links[link2_object_uuid], 'Expected links for the passed in object uuid'
52     assert links[link3_head_uuid], 'Expected links for the passed in link head_uuid'
53
54     # invoke again for this same input. this time, the preloaded data will be returned
55     links = ac.send :preload_links_for_objects, uuids
56     assert links, 'Expected links'
57     assert links.is_a?(Hash), 'Expected a hash'
58     assert links.size == 3, 'Expected two objects in the preloaded links hash'
59     assert links[link1_head_uuid], 'Expected links for the passed in link head_uuid'
60   end
61
62   [ [:preload_links_for_objects, [] ],
63     [:preload_collections_for_objects, [] ],
64     [:preload_log_collections_for_objects, [] ],
65     [:preload_objects_for_dataclass, [] ],
66     [:preload_for_pdhs, [] ],
67   ].each do |input|
68     test "preload data for empty array input #{input}" do
69       use_token :active
70
71       ac = ApplicationController.new
72
73       if input[0] == :preload_objects_for_dataclass
74         objects = ac.send input[0], @user_dataclass, input[1]
75       else
76         objects = ac.send input[0], input[1]
77       end
78
79       assert objects, 'Expected objects'
80       assert objects.is_a?(Hash), 'Expected a hash'
81       assert objects.size == 0, 'Expected no objects in the preloaded hash'
82     end
83   end
84
85   [ [:preload_links_for_objects, 'input not an array'],
86     [:preload_links_for_objects, nil],
87     [:links_for_object, nil],
88     [:preload_collections_for_objects, 'input not an array'],
89     [:preload_collections_for_objects, nil],
90     [:collections_for_object, nil],
91     [:preload_log_collections_for_objects, 'input not an array'],
92     [:preload_log_collections_for_objects, nil],
93     [:log_collections_for_object, nil],
94     [:preload_objects_for_dataclass, 'input not an array'],
95     [:preload_objects_for_dataclass, nil],
96     [:object_for_dataclass, 'some_dataclass', nil],
97     [:object_for_dataclass, nil, 'some_uuid'],
98     [:preload_for_pdhs, 'input not an array'],
99     [:preload_for_pdhs, nil],
100   ].each do |input|
101     test "preload data for wrong type input #{input}" do
102       use_token :active
103
104       ac = ApplicationController.new
105
106       if input[0] == :object_for_dataclass
107         assert_raise ArgumentError do
108           ac.send input[0], input[1], input[2]
109         end
110       else
111         assert_raise ArgumentError do
112           ac.send input[0], input[1]
113         end
114       end
115     end
116   end
117
118   [ [:links_for_object, 'no-such-uuid' ],
119     [:collections_for_object, 'no-such-uuid' ],
120     [:log_collections_for_object, 'no-such-uuid' ],
121     [:object_for_dataclass, 'no-such-uuid' ],
122     [:collection_for_pdh, 'no-such-pdh' ],
123   ].each do |input|
124     test "get data for no such uuid #{input}" do
125       use_token :active
126
127       ac = ApplicationController.new
128
129       if input[0] == :object_for_dataclass
130         object = ac.send input[0], @user_dataclass, input[1]
131         assert_not object, 'Expected no object'
132       else
133         objects = ac.send input[0], input[1]
134         assert objects, 'Expected objects'
135         assert objects.is_a?(Array), 'Expected a array'
136         assert_empty objects
137       end
138     end
139   end
140
141   test "get 10 objects of data class user" do
142     use_token :active
143
144     ac = ApplicationController.new
145
146     objects = ac.send :get_n_objects_of_class, @user_dataclass, 10
147
148     assert objects, 'Expected objects'
149     assert objects.is_a?(ArvadosResourceList), 'Expected an ArvadosResourceList'
150
151     first_object = objects.first
152     assert first_object, 'Expected at least one object'
153     assert_equal 'User', first_object.class.name, 'Expected user object'
154
155     # invoke it again. this time, the preloaded info will be returned
156     objects = ac.send :get_n_objects_of_class, @user_dataclass, 10
157     assert objects, 'Expected objects'
158     assert_equal 'User', objects.first.class.name, 'Expected user object'
159   end
160
161   [ ['User', 10],
162     [nil, 10],
163     [@user_dataclass, 0],
164     [@user_dataclass, -1],
165     [@user_dataclass, nil] ].each do |input|
166     test "get_n_objects for incorrect input #{input}" do
167       use_token :active
168
169       ac = ApplicationController.new
170
171       assert_raise ArgumentError do
172         ac.send :get_n_objects_of_class, input[0], input[1]
173       end
174     end
175   end
176
177   test "collections for object" do
178     use_token :active
179
180     ac = ApplicationController.new
181
182     uuid = api_fixture('collections')['foo_file']['uuid']
183
184     collections = ac.send :collections_for_object, uuid
185
186     assert collections, 'Expected collections'
187     assert collections.is_a?(Array), 'Expected an array'
188     assert collections.size == 1, 'Expected one collection object'
189     assert_equal collections[0][:uuid], uuid, 'Expected uuid not found in collections'
190   end
191
192   test "preload collections for given uuids" do
193     use_token :active
194
195     ac = ApplicationController.new
196
197     uuid1 = api_fixture('collections')['foo_file']['uuid']
198     uuid2 = api_fixture('collections')['bar_file']['uuid']
199
200     uuids = [uuid1, uuid2]
201     collections = ac.send :preload_collections_for_objects, uuids
202
203     assert collections, 'Expected collection'
204     assert collections.is_a?(Hash), 'Expected a hash'
205     assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
206     assert collections[uuid1], 'Expected collections for the passed in uuid'
207     assert_equal collections[uuid1].size, 1, 'Expected one collection for the passed in uuid'
208     assert collections[uuid2], 'Expected collections for the passed in uuid'
209     assert_equal collections[uuid2].size, 1, 'Expected one collection for the passed in uuid'
210
211     # invoke again for this same input. this time, the preloaded data will be returned
212     collections = ac.send :preload_collections_for_objects, uuids
213     assert collections, 'Expected collection'
214     assert collections.is_a?(Hash), 'Expected a hash'
215     assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
216     assert collections[uuid1], 'Expected collections for the passed in uuid'
217   end
218
219   test "log collections for object" do
220     use_token :active
221
222     ac = ApplicationController.new
223
224     uuid = api_fixture('logs')['system_adds_foo_file']['object_uuid']
225
226     collections = ac.send :log_collections_for_object, uuid
227
228     assert collections, 'Expected collections'
229     assert collections.is_a?(Array), 'Expected an array'
230     assert collections.size == 1, 'Expected one collection object'
231     assert_equal collections[0][:uuid], uuid, 'Expected uuid not found in collections'
232   end
233
234   test "preload log collections for given uuids" do
235     use_token :active
236
237     ac = ApplicationController.new
238
239     uuid1 = api_fixture('logs')['system_adds_foo_file']['object_uuid']
240     uuid2 = api_fixture('collections')['bar_file']['uuid']
241
242     uuids = [uuid1, uuid2]
243     collections = ac.send :preload_log_collections_for_objects, uuids
244
245     assert collections, 'Expected collection'
246     assert collections.is_a?(Hash), 'Expected a hash'
247     assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
248     assert collections[uuid1], 'Expected collections for the passed in uuid'
249     assert_equal collections[uuid1].size, 1, 'Expected one collection for the passed in uuid'
250     assert collections[uuid2], 'Expected collections for the passed in uuid'
251     assert_equal collections[uuid2].size, 1, 'Expected one collection for the passed in uuid'
252
253     # invoke again for this same input. this time, the preloaded data will be returned
254     collections = ac.send :preload_log_collections_for_objects, uuids
255     assert collections, 'Expected collection'
256     assert collections.is_a?(Hash), 'Expected a hash'
257     assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
258     assert collections[uuid1], 'Expected collections for the passed in uuid'
259   end
260
261   test "object for dataclass" do
262     use_token :active
263
264     ac = ApplicationController.new
265
266     dataclass = ArvadosBase.resource_class_for_uuid(api_fixture('jobs')['running']['uuid'])
267     uuid = api_fixture('jobs')['running']['uuid']
268
269     obj = ac.send :object_for_dataclass, dataclass, uuid
270
271     assert obj, 'Expected object'
272     assert 'Job', obj.class
273     assert_equal uuid, obj['uuid'], 'Expected uuid not found'
274     assert_equal api_fixture('jobs')['running']['script_version'], obj['script_version'],
275       'Expected script_version not found'
276   end
277
278   test "preload objects for dataclass" do
279     use_token :active
280
281     ac = ApplicationController.new
282
283     dataclass = ArvadosBase.resource_class_for_uuid(api_fixture('jobs')['running']['uuid'])
284
285     uuid1 = api_fixture('jobs')['running']['uuid']
286     uuid2 = api_fixture('jobs')['running_cancelled']['uuid']
287
288     uuids = [uuid1, uuid2]
289     users = ac.send :preload_objects_for_dataclass, dataclass, uuids
290
291     assert users, 'Expected objects'
292     assert users.is_a?(Hash), 'Expected a hash'
293
294     assert users.size == 2, 'Expected two objects in the preloaded hash'
295     assert users[uuid1], 'Expected user object for the passed in uuid'
296     assert users[uuid2], 'Expected user object for the passed in uuid'
297
298     # invoke again for this same input. this time, the preloaded data will be returned
299     users = ac.send :preload_objects_for_dataclass, dataclass, uuids
300     assert users, 'Expected objects'
301     assert users.is_a?(Hash), 'Expected a hash'
302     assert users.size == 2, 'Expected two objects in the preloaded hash'
303
304     # invoke again for this with one more uuid
305     uuids << api_fixture('jobs')['foobar']['uuid']
306     users = ac.send :preload_objects_for_dataclass, dataclass, uuids
307     assert users, 'Expected objects'
308     assert users.is_a?(Hash), 'Expected a hash'
309     assert users.size == 3, 'Expected two objects in the preloaded hash'
310   end
311
312   test "preload one collection each for given portable_data_hash list" do
313     use_token :active
314
315     ac = ApplicationController.new
316
317     pdh1 = api_fixture('collections')['foo_file']['portable_data_hash']
318     pdh2 = api_fixture('collections')['bar_file']['portable_data_hash']
319
320     pdhs = [pdh1, pdh2]
321     collections = ac.send :preload_for_pdhs, pdhs
322
323     assert collections, 'Expected collections map'
324     assert collections.is_a?(Hash), 'Expected a hash'
325     # Each pdh has more than one collection; however, we should get only one for each
326     assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
327     assert collections[pdh1], 'Expected collections for the passed in pdh #{pdh1}'
328     assert_equal collections[pdh1].size, 1, "Expected one collection for the passed in pdh #{pdh1}"
329     assert collections[pdh2], 'Expected collections for the passed in pdh #{pdh2}'
330     assert_equal collections[pdh2].size, 1, "Expected one collection for the passed in pdh #{pdh2}"
331   end
332
333   test "requesting a nonexistent object returns 404" do
334     # We're really testing ApplicationController's find_object_by_uuid.
335     # It's easiest to do that by instantiating a concrete controller.
336     @controller = NodesController.new
337     get(:show, params: {id: "zzzzz-zzzzz-zzzzzzzzzzzzzzz"}, session: session_for(:admin))
338     assert_response 404
339   end
340
341   test "requesting to the API server includes X-Request-Id header" do
342     got_header = nil
343     stub_api_calls
344     stub_api_client.stubs(:post).with do |url, query, header={}|
345       got_header = header
346       true
347     end.returns fake_api_response('{}', 200, {})
348
349     Rails.configuration.Users.AnonymousUserToken =
350       api_fixture("api_client_authorizations", "anonymous", "api_token")
351     @controller = ProjectsController.new
352     test_uuid = "zzzzz-j7d0g-zzzzzzzzzzzzzzz"
353     get(:show, params: {id: test_uuid})
354
355     assert_not_nil got_header
356     assert_includes got_header, 'X-Request-Id'
357     assert_match /^req-[0-9a-zA-Z]{20}$/, got_header["X-Request-Id"]
358   end
359
360   test "current request_id is nil after a request" do
361     @controller = NodesController.new
362     get(:index, params: {}, session: session_for(:active))
363     assert_nil Thread.current[:request_id]
364   end
365
366   test "X-Request-Id header" do
367     @controller = NodesController.new
368     get(:index, params: {}, session: session_for(:active))
369     assert_match /^req-[0-9a-zA-Z]{20}$/, response.headers['X-Request-Id']
370   end
371
372   [".navbar .login-menu a",
373    ".navbar .login-menu .dropdown-menu a"
374   ].each do |css_selector|
375     test "login link at #{css_selector.inspect} includes return_to param" do
376       # Without an anonymous token, we're immediately redirected to login.
377       Rails.configuration.Users.AnonymousUserToken =
378         api_fixture("api_client_authorizations", "anonymous", "api_token")
379       @controller = ProjectsController.new
380       test_uuid = "zzzzz-j7d0g-zzzzzzzzzzzzzzz"
381       get(:show, params: {id: test_uuid})
382       login_link = css_select(css_selector).first
383       assert_not_nil(login_link, "failed to select login link")
384       login_href = URI.unescape(login_link.attributes["href"].value)
385       # The parameter needs to include the full URL to work.
386       assert_includes(login_href, "://")
387       assert_match(/[\?&]return_to=[^&]*\/projects\/#{test_uuid}(&|$)/,
388                    login_href)
389     end
390   end
391
392   test "Workbench returns 4xx when API server is unreachable" do
393     # We're really testing ApplicationController's render_exception.
394     # Our primary concern is that it doesn't raise an error and
395     # return 500.
396     orig_api_server = Rails.configuration.Services.Controller.ExternalURL
397     begin
398       # The URL should look valid in all respects, and avoid talking over a
399       # network.  100::/64 is the IPv6 discard prefix, so it's perfect.
400       Rails.configuration.Services.Controller.ExternalURL = "https://[100::f]:1/"
401       @controller = NodesController.new
402       get(:index, params: {}, session: session_for(:active))
403       assert_includes(405..422, @response.code.to_i,
404                       "bad response code when API server is unreachable")
405     ensure
406       Rails.configuration.Services.Controller.ExternalURL = orig_api_server
407     end
408   end
409
410   [
411     [CollectionsController.new, api_fixture('collections')['user_agreement_in_anonymously_accessible_project']],
412     [CollectionsController.new, api_fixture('collections')['user_agreement_in_anonymously_accessible_project'], false],
413     [JobsController.new, api_fixture('jobs')['running_job_in_publicly_accessible_project']],
414     [JobsController.new, api_fixture('jobs')['running_job_in_publicly_accessible_project'], false],
415     [PipelineInstancesController.new, api_fixture('pipeline_instances')['pipeline_in_publicly_accessible_project']],
416     [PipelineInstancesController.new, api_fixture('pipeline_instances')['pipeline_in_publicly_accessible_project'], false],
417     [PipelineTemplatesController.new, api_fixture('pipeline_templates')['pipeline_template_in_publicly_accessible_project']],
418     [PipelineTemplatesController.new, api_fixture('pipeline_templates')['pipeline_template_in_publicly_accessible_project'], false],
419     [ProjectsController.new, api_fixture('groups')['anonymously_accessible_project']],
420     [ProjectsController.new, api_fixture('groups')['anonymously_accessible_project'], false],
421   ].each do |controller, fixture, anon_config=true|
422     test "#{controller} show method with anonymous config #{anon_config ? '' : 'not '}enabled" do
423       if anon_config
424         Rails.configuration.Users.AnonymousUserToken = api_fixture('api_client_authorizations')['anonymous']['api_token']
425       else
426         Rails.configuration.Users.AnonymousUserToken = ""
427       end
428
429       @controller = controller
430
431       get(:show, params: {id: fixture['uuid']})
432
433       if anon_config
434         assert_response 200
435         if controller.class == JobsController
436           assert_includes @response.inspect, fixture['script']
437         else
438           assert_includes @response.inspect, fixture['name']
439         end
440       else
441         assert_response :redirect
442         assert_match /\/users\/welcome/, @response.redirect_url
443       end
444     end
445   end
446
447   [
448     true,
449     false,
450   ].each do |config|
451     test "invoke show with include_accept_encoding_header config #{config}" do
452       Rails.configuration.APIResponseCompression = config
453
454       @controller = CollectionsController.new
455       get(:show, params: {id: api_fixture('collections')['foo_file']['uuid']}, session: session_for(:admin))
456
457       assert_equal([['.', 'foo', 3]], assigns(:object).files)
458     end
459   end
460
461   test 'Edit name and verify that a duplicate is not created' do
462     @controller = ProjectsController.new
463     project = api_fixture("groups")["aproject"]
464     post :update, params: {
465       id: project["uuid"],
466       project: {
467         name: 'test name'
468       },
469       format: :json
470     }, session: session_for(:active)
471     assert_includes @response.body, 'test name'
472     updated = assigns(:object)
473     assert_equal updated.uuid, project["uuid"]
474     assert_equal 'test name', updated.name
475   end
476
477   [
478     [VirtualMachinesController.new, 'hostname', false],
479     [UsersController.new, 'first_name', true],
480   ].each do |controller, expect_str, expect_home_link|
481     test "access #{controller.controller_name} index as admin and verify Home link is#{' not' if !expect_home_link} shown" do
482       @controller = controller
483
484       get :index, params: {}, session: session_for(:admin)
485
486       assert_response 200
487       assert_includes @response.body, expect_str
488
489       home_link = "/projects/#{api_fixture('users')['active']['uuid']}"
490
491       if expect_home_link
492         refute_empty css_select("[href=\"/projects/#{api_fixture('users')['active']['uuid']}\"]")
493       else
494         assert_empty css_select("[href=\"/projects/#{api_fixture('users')['active']['uuid']}\"]")
495       end
496     end
497   end
498
499   [
500     [VirtualMachinesController.new, 'hostname', true],
501     [UsersController.new, 'first_name', false],
502   ].each do |controller, expect_str, expect_delete_link|
503     test "access #{controller.controller_name} index as admin and verify Delete option is#{' not' if !expect_delete_link} shown" do
504       @controller = controller
505
506       get :index, params: {}, session: session_for(:admin)
507
508       assert_response 200
509       assert_includes @response.body, expect_str
510       if expect_delete_link
511         refute_empty css_select('[data-method=delete]')
512       else
513         assert_empty css_select('[data-method=delete]')
514       end
515     end
516   end
517 end