Merge branch '19215-installer' refs #19215
[arvados.git] / apps / workbench / test / integration / collections_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'integration_helper'
6 require_relative 'integration_test_utils'
7
8 class CollectionsTest < ActionDispatch::IntegrationTest
9   setup do
10     need_javascript
11   end
12
13   test "Can copy a collection to a project" do
14     collection_uuid = api_fixture('collections')['foo_file']['uuid']
15     collection_name = api_fixture('collections')['foo_file']['name']
16     project_uuid = api_fixture('groups')['aproject']['uuid']
17     project_name = api_fixture('groups')['aproject']['name']
18     visit page_with_token('active', "/collections/#{collection_uuid}")
19     click_link 'Copy to project...'
20     find('.selectable', text: project_name).click
21     find('.modal-footer a,button', text: 'Copy').click
22     # Should navigate to the Data collections tab of the project after copying
23     assert_text project_name
24     assert_text "Copy of #{collection_name}"
25   end
26
27   def check_sharing(want_state, link_regexp)
28     # We specifically want to click buttons.  See #4291.
29     if want_state == :off
30       click_button "Unshare"
31       text_assertion = :assert_no_text
32       link_assertion = :assert_empty
33     else
34       click_button "Create sharing link"
35       text_assertion = :assert_text
36       link_assertion = :refute_empty
37     end
38     using_wait_time(Capybara.default_max_wait_time * 3) do
39       send(text_assertion, "Shared at:")
40     end
41     send(link_assertion, all("a").select { |a| a[:href] =~ link_regexp })
42   end
43
44   test "Hides sharing link button when configured to do so" do
45     Rails.configuration.Workbench.DisableSharingURLsUI = true
46     coll_uuid = api_fixture("collections", "collection_owned_by_active", "uuid")
47     visit page_with_token("active_trustedclient", "/collections/#{coll_uuid}")
48     assert_no_selector 'div#sharing-button'
49   end
50
51   test "creating and uncreating a sharing link" do
52     coll_uuid = api_fixture("collections", "collection_owned_by_active", "uuid")
53     download_link_re =
54       Regexp.new(Regexp.escape("/c=#{coll_uuid}/"))
55     visit page_with_token("active_trustedclient", "/collections/#{coll_uuid}")
56     assert_selector 'div#sharing-button'
57     within "#sharing-button" do
58       check_sharing(:on, download_link_re)
59       check_sharing(:off, download_link_re)
60     end
61   end
62
63   test "can download an entire collection with a reader token" do
64     need_selenium "phantomjs does not follow redirects reliably, maybe https://github.com/ariya/phantomjs/issues/10389"
65
66     token = api_token('active')
67     data = "foo\nfile\n"
68     datablock = `echo -n #{data.shellescape} | ARVADOS_API_TOKEN=#{token.shellescape} arv-put --no-progress --raw -`.strip
69     assert $?.success?, $?
70
71     col = nil
72     use_token 'active' do
73       mtxt = ". #{datablock} 0:#{data.length}:foo\n"
74       col = Collection.create(manifest_text: mtxt)
75     end
76
77     uuid = col.uuid
78     token = api_fixture('api_client_authorizations')['active_all_collections']['api_token']
79     url_head = "/collections/download/#{uuid}/#{token}/"
80     visit url_head
81     assert_text "You can download individual files listed below"
82     # It seems that Capybara can't inspect tags outside the body, so this is
83     # a very blunt approach.
84     assert_no_match(/<\s*meta[^>]+\bnofollow\b/i, page.html,
85                     "wget prohibited from recursing the collection page")
86     # Look at all the links that wget would recurse through using our
87     # recommended options, and check that it's exactly the file list.
88     hrefs = []
89     page.html.scan(/href="(.*?)"/) { |m| hrefs << m[0] }
90     assert_equal(['./foo'], hrefs, "download page did provide strictly file links")
91     click_link "foo"
92     assert_text "foo\nfile\n"
93   end
94
95   test "combine selected collections into new collection" do
96     foo_collection = api_fixture('collections')['foo_file']
97     bar_collection = api_fixture('collections')['bar_file']
98
99     visit page_with_token('active', "/collections")
100
101     assert(page.has_text?(foo_collection['uuid']), "Collection page did not include foo file")
102     assert(page.has_text?(bar_collection['uuid']), "Collection page did not include bar file")
103
104     within "tr[data-object-uuid=\"#{foo_collection['uuid']}\"]" do
105       find('input[type=checkbox]').click
106     end
107
108     within "tr[data-object-uuid=\"#{bar_collection['uuid']}\"]" do
109       find('input[type=checkbox]').click
110     end
111
112     click_button 'Selection...'
113     within('.selection-action-container') do
114       click_link 'Create new collection with selected collections'
115     end
116
117     # now in the newly created collection page
118     assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
119     assert(page.has_no_text?(foo_collection['name']), "Collection page did not include foo file")
120     assert(page.has_text?('foo'), "Collection page did not include foo file")
121     assert(page.has_no_text?(bar_collection['name']), "Collection page did not include foo file")
122     assert(page.has_text?('bar'), "Collection page did not include bar file")
123     assert(page.has_text?('Created new collection in your Home project'),
124                           'Not found flash message that new collection is created in Home project')
125   end
126
127   [
128     ['active', 'foo_file', false],
129     ['active', 'foo_collection_in_aproject', true],
130     ['project_viewer', 'foo_file', false],
131     ['project_viewer', 'foo_collection_in_aproject', false], #aproject not writable
132   ].each do |user, collection, expect_collection_in_aproject|
133     test "combine selected collection files into new collection #{user} #{collection} #{expect_collection_in_aproject}" do
134       my_collection = api_fixture('collections')[collection]
135
136       visit page_with_token(user, "/collections")
137
138       # choose file from foo collection
139       within('tr', text: my_collection['uuid']) do
140         click_link 'Show'
141       end
142
143       # now in collection page
144       find('input[type=checkbox]').click
145
146       click_button 'Selection...'
147       within('.selection-action-container') do
148         click_link 'Create new collection with selected files'
149       end
150
151       # now in the newly created collection page
152       assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
153       assert(page.has_no_text?(my_collection['name']), "Collection page did not include foo file")
154       assert(page.has_text?('foo'), "Collection page did not include foo file")
155       if expect_collection_in_aproject
156         aproject = api_fixture('groups')['aproject']
157         assert page.has_text?("Created new collection in the project #{aproject['name']}"),
158                               'Not found flash message that new collection is created in aproject'
159       else
160         assert page.has_text?("Created new collection in your Home project"),
161                               'Not found flash message that new collection is created in Home project'
162       end
163     end
164   end
165
166   test "combine selected collection files from collection subdirectory" do
167     visit page_with_token('user1_with_load', "/collections/zzzzz-4zz18-filesinsubdir00")
168
169     # now in collection page
170     input_files = page.all('input[type=checkbox]')
171     (0..input_files.count-1).each do |i|
172       input_files[i].click
173     end
174
175     click_button 'Selection...'
176     within('.selection-action-container') do
177       click_link 'Create new collection with selected files'
178     end
179
180     # now in the newly created collection page
181     assert(page.has_text?('file_in_subdir1'), 'file not found - file_in_subdir1')
182     assert(page.has_text?('file1_in_subdir3.txt'), 'file not found - file1_in_subdir3.txt')
183     assert(page.has_text?('file2_in_subdir3.txt'), 'file not found - file2_in_subdir3.txt')
184     assert(page.has_text?('file1_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
185     assert(page.has_text?('file2_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
186   end
187
188   test "Collection portable data hash with multiple matches with more than one page of results" do
189     pdh = api_fixture('collections')['baz_file']['portable_data_hash']
190     visit page_with_token('admin', "/collections/#{pdh}")
191
192     assert_selector 'a', text: 'Collection_1'
193
194     assert_text 'The following collections have this content:'
195     assert_text 'more results are not shown'
196     assert_no_text 'Activity'
197     assert_no_text 'Sharing and permissions'
198   end
199
200   test "Filtering collection files by regexp" do
201     col = api_fixture('collections', 'multilevel_collection_1')
202     visit page_with_token('active', "/collections/#{col['uuid']}")
203
204     # Filter file list to some but not all files in the collection
205     page.find_field('file_regex').set('file[12]')
206     assert page.has_text?("file1")
207     assert page.has_text?("file2")
208     assert page.has_no_text?("file3")
209
210     # Filter file list with a regex matching all files
211     page.find_field('file_regex').set('.*')
212     assert page.has_text?("file1")
213     assert page.has_text?("file2")
214     assert page.has_text?("file3")
215
216     # Filter file list to a regex matching no files
217     page.find_field('file_regex').set('file9')
218     assert page.has_no_text?("file1")
219     assert page.has_no_text?("file2")
220     assert page.has_no_text?("file3")
221     # make sure that we actually are looking at the collections
222     # page and not e.g. a fiddlesticks
223     assert page.has_text?("multilevel_collection_1")
224     assert page.has_text?(col["name"] || col["uuid"])
225
226     # Set filename filter to a syntactically invalid regex
227     # Page loads, but stops filtering after the last valid regex parse
228     page.find_field('file_regex').set('file[2')
229     assert page.has_text?("multilevel_collection_1")
230     assert page.has_text?(col["name"] || col["uuid"])
231     assert page.has_text?("file1")
232     assert page.has_text?("file2")
233     assert page.has_text?("file3")
234
235     # Test the "Select all" button
236
237     # Note: calling .set('') on a Selenium element is not sufficient
238     # to reset the field for this test, as it does not send any key
239     # events to the browser. To clear the field, we must instead send
240     # a backspace character.
241     # See https://selenium.googlecode.com/svn/trunk/docs/api/rb/Selenium/WebDriver/Element.html#clear-instance_method
242     page.find_field('file_regex').set("\b") # backspace
243     find('button#select-all').click
244     assert_checkboxes_state('input[type=checkbox]', true, '"select all" should check all checkboxes')
245
246     # Test the "Unselect all" button
247     page.find_field('file_regex').set("\b") # backspace
248     find('button#unselect-all').click
249     assert_checkboxes_state('input[type=checkbox]', false, '"unselect all" should clear all checkboxes')
250
251     # Filter files, then "select all", then unfilter
252     page.find_field('file_regex').set("\b") # backspace
253     find('button#unselect-all').click
254     page.find_field('file_regex').set('file[12]')
255     find('button#select-all').click
256     page.find_field('file_regex').set("\b") # backspace
257
258     # all "file1" and "file2" checkboxes must be selected
259     # all "file3" checkboxes must be clear
260     assert_checkboxes_state('[value*="file1"]', true, 'checkboxes for file1 should be selected after filtering')
261     assert_checkboxes_state('[value*="file2"]', true, 'checkboxes for file2 should be selected after filtering')
262     assert_checkboxes_state('[value*="file3"]', false, 'checkboxes for file3 should be clear after filtering')
263
264     # Select all files, then filter, then "unselect all", then unfilter
265     page.find_field('file_regex').set("\b") # backspace
266     find('button#select-all').click
267     page.find_field('file_regex').set('file[12]')
268     find('button#unselect-all').click
269     page.find_field('file_regex').set("\b") # backspace
270
271     # all "file1" and "file2" checkboxes must be clear
272     # all "file3" checkboxes must be selected
273     assert_checkboxes_state('[value*="file1"]', false, 'checkboxes for file1 should be clear after filtering')
274     assert_checkboxes_state('[value*="file2"]', false, 'checkboxes for file2 should be clear after filtering')
275     assert_checkboxes_state('[value*="file3"]', true, 'checkboxes for file3 should be selected after filtering')
276   end
277
278   test "Creating collection from list of filtered files" do
279     col = api_fixture('collections', 'collection_with_files_in_subdir')
280     visit page_with_token('user1_with_load', "/collections/#{col['uuid']}")
281     assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not found'
282     assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not found'
283     assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not found'
284     assert page.has_text?('file1_in_subdir4'), 'expected file1_in_subdir4 not found'
285     assert page.has_text?('file2_in_subdir4'), 'expected file2_in_subdir4 not found'
286
287     # Select all files but then filter them to files in subdir1, subdir2 or subdir3
288     find('button#select-all').click
289     page.find_field('file_regex').set('_in_subdir[123]')
290     assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not in filtered files'
291     assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not in filtered files'
292     assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not in filtered files'
293     assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in filtered files'
294     assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in filtered files'
295
296     # Create a new collection
297     click_button 'Selection...'
298     within('.selection-action-container') do
299       click_link 'Create new collection with selected files'
300     end
301
302     # now in the newly created collection page
303     # must have files in subdir1 and subdir3 but not subdir4
304     assert page.has_text?('file_in_subdir1'), 'file_in_subdir1 missing from new collection'
305     assert page.has_text?('file1_in_subdir3'), 'file1_in_subdir3 missing from new collection'
306     assert page.has_text?('file2_in_subdir3'), 'file2_in_subdir3 missing from new collection'
307     assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in new collection'
308     assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in new collection'
309
310     # Make sure we're not still on the old collection page.
311     refute_match(%r{/collections/#{col['uuid']}}, page.current_url)
312   end
313
314   test "remove a file from collection using checkbox and dropdown option" do
315     need_selenium 'to confirm unlock'
316
317     visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
318     assert(page.has_text?('file1'), 'file not found - file1')
319
320     unlock_collection
321
322     # remove first file
323     input_files = page.all('input[type=checkbox]')
324     input_files[0].click
325
326     click_button 'Selection...'
327     within('.selection-action-container') do
328       click_link 'Remove selected files'
329     end
330
331     assert(page.has_no_text?('file1'), 'file found - file')
332     assert(page.has_text?('file2'), 'file not found - file2')
333   end
334
335   test "remove a file in collection using trash icon" do
336     need_selenium 'to confirm unlock'
337
338     visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
339     assert(page.has_text?('file1'), 'file not found - file1')
340
341     unlock_collection
342
343     first('.fa-trash-o').click
344     accept_alert
345
346     assert(page.has_no_text?('file1'), 'file found - file')
347     assert(page.has_text?('file2'), 'file not found - file2')
348   end
349
350   test "rename a file in collection" do
351     need_selenium 'to confirm unlock'
352
353     visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
354
355     unlock_collection
356
357     within('.collection_files') do
358       first('.fa-pencil').click
359       find('.editable-input input').set('file1renamed')
360       find('.editable-submit').click
361     end
362
363     assert(page.has_text?('file1renamed'), 'file not found - file1renamed')
364   end
365
366   test "remove/rename file options not presented if user cannot update a collection" do
367     # visit a publicly accessible collection as 'spectator'
368     visit page_with_token('spectator', '/collections/zzzzz-4zz18-uukreo9rbgwsujr')
369
370     click_button 'Selection'
371     within('.selection-action-container') do
372       assert_selector 'li', text: 'Create new collection with selected files'
373       assert_no_selector 'li', text: 'Remove selected files'
374     end
375
376     within('.collection_files') do
377       assert(page.has_text?('GNU_General_Public_License'), 'file not found - GNU_General_Public_License')
378       assert_nil first('.fa-pencil')
379       assert_nil first('.fa-trash-o')
380     end
381   end
382
383   test "unlock collection to modify files" do
384     need_selenium 'to confirm remove'
385
386     collection = api_fixture('collections')['collection_owned_by_active']
387
388     # On load, collection is locked, and upload tab, rename and remove options are disabled
389     visit page_with_token('active', "/collections/#{collection['uuid']}")
390
391     assert_selector 'a[data-toggle="disabled"]', text: 'Upload'
392
393     within('.collection_files') do
394       file_ctrls = page.all('.btn-collection-file-control')
395       assert_equal 2, file_ctrls.size
396       assert_equal true, file_ctrls[0]['class'].include?('disabled')
397       assert_equal true, file_ctrls[1]['class'].include?('disabled')
398       find('input[type=checkbox]').click
399     end
400
401     click_button 'Selection'
402     within('.selection-action-container') do
403       assert_selector 'li.disabled', text: 'Remove selected files'
404       assert_selector 'li', text: 'Create new collection with selected files'
405     end
406
407     unlock_collection
408
409     assert_no_selector 'a[data-toggle="disabled"]', text: 'Upload'
410     assert_selector 'a', text: 'Upload'
411
412     within('.collection_files') do
413       file_ctrls = page.all('.btn-collection-file-control')
414       assert_equal 2, file_ctrls.size
415       assert_equal false, file_ctrls[0]['class'].include?('disabled')
416       assert_equal false, file_ctrls[1]['class'].include?('disabled')
417
418       # previous checkbox selection won't result in firing a new event;
419       # undo and redo checkbox to fire the selection event again
420       find('input[type=checkbox]').click
421       find('input[type=checkbox]').click
422     end
423
424     click_button 'Selection'
425     within('.selection-action-container') do
426       assert_no_selector 'li.disabled', text: 'Remove selected files'
427       assert_selector 'li', text: 'Remove selected files'
428     end
429   end
430
431   def unlock_collection
432     first('.lock-collection-btn').click
433     accept_alert
434   end
435 end