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