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