1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 require 'integration_helper'
6 require_relative 'integration_test_utils'
8 class CollectionsTest < ActionDispatch::IntegrationTest
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}"
27 def check_sharing(want_state, link_regexp)
28 # We specifically want to click buttons. See #4291.
30 click_button "Unshare"
31 text_assertion = :assert_no_text
32 link_assertion = :assert_empty
34 click_button "Create sharing link"
35 text_assertion = :assert_text
36 link_assertion = :refute_empty
38 using_wait_time(Capybara.default_max_wait_time * 3) do
39 send(text_assertion, "Shared at:")
41 send(link_assertion, all("a").select { |a| a[:href] =~ link_regexp })
44 test "creating and uncreating a sharing link" do
45 coll_uuid = api_fixture("collections", "collection_owned_by_active", "uuid")
47 Regexp.new(Regexp.escape("/collections/download/#{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)
55 test "can download an entire collection with a reader token" do
56 Capybara.current_driver = :rack_test
57 CollectionsController.any_instance.
58 stubs(:file_enumerator).returns(["foo\n", "file\n"])
59 uuid = api_fixture('collections')['foo_file']['uuid']
60 token = api_fixture('api_client_authorizations')['active_all_collections']['api_token']
61 url_head = "/collections/download/#{uuid}/#{token}/"
63 # It seems that Capybara can't inspect tags outside the body, so this is
64 # a very blunt approach.
65 assert_no_match(/<\s*meta[^>]+\bnofollow\b/i, page.html,
66 "wget prohibited from recursing the collection page")
67 # Look at all the links that wget would recurse through using our
68 # recommended options, and check that it's exactly the file list.
69 hrefs = page.all('a').map do |anchor|
70 link = anchor[:href] || ''
71 if link.start_with? url_head
72 link[url_head.size .. -1]
73 elsif link.start_with? '/'
79 assert_equal(['foo'], hrefs.compact.sort,
80 "download page did provide strictly file links")
81 within "#collection_files" do
83 assert_equal("foo\nfile\n", page.html)
87 test "combine selected collections into new collection" do
88 foo_collection = api_fixture('collections')['foo_file']
89 bar_collection = api_fixture('collections')['bar_file']
91 visit page_with_token('active', "/collections")
93 assert(page.has_text?(foo_collection['uuid']), "Collection page did not include foo file")
94 assert(page.has_text?(bar_collection['uuid']), "Collection page did not include bar file")
96 within "tr[data-object-uuid=\"#{foo_collection['uuid']}\"]" do
97 find('input[type=checkbox]').click
100 within "tr[data-object-uuid=\"#{bar_collection['uuid']}\"]" do
101 find('input[type=checkbox]').click
104 click_button 'Selection...'
105 within('.selection-action-container') do
106 click_link 'Create new collection with selected collections'
109 # now in the newly created collection page
110 assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
111 assert(page.has_no_text?(foo_collection['name']), "Collection page did not include foo file")
112 assert(page.has_text?('foo'), "Collection page did not include foo file")
113 assert(page.has_no_text?(bar_collection['name']), "Collection page did not include foo file")
114 assert(page.has_text?('bar'), "Collection page did not include bar file")
115 assert(page.has_text?('Created new collection in your Home project'),
116 'Not found flash message that new collection is created in Home project')
120 ['active', 'foo_file', false],
121 ['active', 'foo_collection_in_aproject', true],
122 ['project_viewer', 'foo_file', false],
123 ['project_viewer', 'foo_collection_in_aproject', false], #aproject not writable
124 ].each do |user, collection, expect_collection_in_aproject|
125 test "combine selected collection files into new collection #{user} #{collection} #{expect_collection_in_aproject}" do
126 my_collection = api_fixture('collections')[collection]
128 visit page_with_token(user, "/collections")
130 # choose file from foo collection
131 within('tr', text: my_collection['uuid']) do
135 # now in collection page
136 find('input[type=checkbox]').click
138 click_button 'Selection...'
139 within('.selection-action-container') do
140 click_link 'Create new collection with selected files'
143 # now in the newly created collection page
144 assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
145 assert(page.has_no_text?(my_collection['name']), "Collection page did not include foo file")
146 assert(page.has_text?('foo'), "Collection page did not include foo file")
147 if expect_collection_in_aproject
148 aproject = api_fixture('groups')['aproject']
149 assert page.has_text?("Created new collection in the project #{aproject['name']}"),
150 'Not found flash message that new collection is created in aproject'
152 assert page.has_text?("Created new collection in your Home project"),
153 'Not found flash message that new collection is created in Home project'
158 test "combine selected collection files from collection subdirectory" do
159 visit page_with_token('user1_with_load', "/collections/zzzzz-4zz18-filesinsubdir00")
161 # now in collection page
162 input_files = page.all('input[type=checkbox]')
163 (0..input_files.count-1).each do |i|
167 click_button 'Selection...'
168 within('.selection-action-container') do
169 click_link 'Create new collection with selected files'
172 # now in the newly created collection page
173 assert(page.has_text?('file_in_subdir1'), 'file not found - file_in_subdir1')
174 assert(page.has_text?('file1_in_subdir3.txt'), 'file not found - file1_in_subdir3.txt')
175 assert(page.has_text?('file2_in_subdir3.txt'), 'file not found - file2_in_subdir3.txt')
176 assert(page.has_text?('file1_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
177 assert(page.has_text?('file2_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
180 test "Collection portable data hash with multiple matches with more than one page of results" do
181 pdh = api_fixture('collections')['baz_file']['portable_data_hash']
182 visit page_with_token('admin', "/collections/#{pdh}")
184 assert_selector 'a', text: 'Collection_1'
186 assert_text 'The following collections have this content:'
187 assert_text 'more results are not shown'
188 assert_no_text 'Activity'
189 assert_no_text 'Sharing and permissions'
192 test "Filtering collection files by regexp" do
193 col = api_fixture('collections', 'multilevel_collection_1')
194 visit page_with_token('active', "/collections/#{col['uuid']}")
196 # Filter file list to some but not all files in the collection
197 page.find_field('file_regex').set('file[12]')
198 assert page.has_text?("file1")
199 assert page.has_text?("file2")
200 assert page.has_no_text?("file3")
202 # Filter file list with a regex matching all files
203 page.find_field('file_regex').set('.*')
204 assert page.has_text?("file1")
205 assert page.has_text?("file2")
206 assert page.has_text?("file3")
208 # Filter file list to a regex matching no files
209 page.find_field('file_regex').set('file9')
210 assert page.has_no_text?("file1")
211 assert page.has_no_text?("file2")
212 assert page.has_no_text?("file3")
213 # make sure that we actually are looking at the collections
214 # page and not e.g. a fiddlesticks
215 assert page.has_text?("multilevel_collection_1")
216 assert page.has_text?(col["name"] || col["uuid"])
218 # Set filename filter to a syntactically invalid regex
219 # Page loads, but stops filtering after the last valid regex parse
220 page.find_field('file_regex').set('file[2')
221 assert page.has_text?("multilevel_collection_1")
222 assert page.has_text?(col["name"] || col["uuid"])
223 assert page.has_text?("file1")
224 assert page.has_text?("file2")
225 assert page.has_text?("file3")
227 # Test the "Select all" button
229 # Note: calling .set('') on a Selenium element is not sufficient
230 # to reset the field for this test, as it does not send any key
231 # events to the browser. To clear the field, we must instead send
232 # a backspace character.
233 # See https://selenium.googlecode.com/svn/trunk/docs/api/rb/Selenium/WebDriver/Element.html#clear-instance_method
234 page.find_field('file_regex').set("\b") # backspace
235 find('button#select-all').click
236 assert_checkboxes_state('input[type=checkbox]', true, '"select all" should check all checkboxes')
238 # Test the "Unselect all" button
239 page.find_field('file_regex').set("\b") # backspace
240 find('button#unselect-all').click
241 assert_checkboxes_state('input[type=checkbox]', false, '"unselect all" should clear all checkboxes')
243 # Filter files, then "select all", then unfilter
244 page.find_field('file_regex').set("\b") # backspace
245 find('button#unselect-all').click
246 page.find_field('file_regex').set('file[12]')
247 find('button#select-all').click
248 page.find_field('file_regex').set("\b") # backspace
250 # all "file1" and "file2" checkboxes must be selected
251 # all "file3" checkboxes must be clear
252 assert_checkboxes_state('[value*="file1"]', true, 'checkboxes for file1 should be selected after filtering')
253 assert_checkboxes_state('[value*="file2"]', true, 'checkboxes for file2 should be selected after filtering')
254 assert_checkboxes_state('[value*="file3"]', false, 'checkboxes for file3 should be clear after filtering')
256 # Select all files, then filter, then "unselect all", then unfilter
257 page.find_field('file_regex').set("\b") # backspace
258 find('button#select-all').click
259 page.find_field('file_regex').set('file[12]')
260 find('button#unselect-all').click
261 page.find_field('file_regex').set("\b") # backspace
263 # all "file1" and "file2" checkboxes must be clear
264 # all "file3" checkboxes must be selected
265 assert_checkboxes_state('[value*="file1"]', false, 'checkboxes for file1 should be clear after filtering')
266 assert_checkboxes_state('[value*="file2"]', false, 'checkboxes for file2 should be clear after filtering')
267 assert_checkboxes_state('[value*="file3"]', true, 'checkboxes for file3 should be selected after filtering')
270 test "Creating collection from list of filtered files" do
271 col = api_fixture('collections', 'collection_with_files_in_subdir')
272 visit page_with_token('user1_with_load', "/collections/#{col['uuid']}")
273 assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not found'
274 assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not found'
275 assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not found'
276 assert page.has_text?('file1_in_subdir4'), 'expected file1_in_subdir4 not found'
277 assert page.has_text?('file2_in_subdir4'), 'expected file2_in_subdir4 not found'
279 # Select all files but then filter them to files in subdir1, subdir2 or subdir3
280 find('button#select-all').click
281 page.find_field('file_regex').set('_in_subdir[123]')
282 assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not in filtered files'
283 assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not in filtered files'
284 assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not in filtered files'
285 assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in filtered files'
286 assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in filtered files'
288 # Create a new collection
289 click_button 'Selection...'
290 within('.selection-action-container') do
291 click_link 'Create new collection with selected files'
294 # now in the newly created collection page
295 # must have files in subdir1 and subdir3 but not subdir4
296 assert page.has_text?('file_in_subdir1'), 'file_in_subdir1 missing from new collection'
297 assert page.has_text?('file1_in_subdir3'), 'file1_in_subdir3 missing from new collection'
298 assert page.has_text?('file2_in_subdir3'), 'file2_in_subdir3 missing from new collection'
299 assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in new collection'
300 assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in new collection'
302 # Make sure we're not still on the old collection page.
303 refute_match(%r{/collections/#{col['uuid']}}, page.current_url)
306 test "remove a file from collection using checkbox and dropdown option" do
307 need_selenium 'to confirm unlock'
309 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
310 assert(page.has_text?('file1'), 'file not found - file1')
315 input_files = page.all('input[type=checkbox]')
318 click_button 'Selection...'
319 within('.selection-action-container') do
320 click_link 'Remove selected files'
323 assert(page.has_no_text?('file1'), 'file found - file')
324 assert(page.has_text?('file2'), 'file not found - file2')
327 test "remove a file in collection using trash icon" do
328 need_selenium 'to confirm unlock'
330 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
331 assert(page.has_text?('file1'), 'file not found - file1')
335 first('.fa-trash-o').click
338 assert(page.has_no_text?('file1'), 'file found - file')
339 assert(page.has_text?('file2'), 'file not found - file2')
342 test "rename a file in collection" do
343 need_selenium 'to confirm unlock'
345 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
349 within('.collection_files') do
350 first('.fa-pencil').click
351 find('.editable-input input').set('file1renamed')
352 find('.editable-submit').click
355 assert(page.has_text?('file1renamed'), 'file not found - file1renamed')
358 test "remove/rename file options not presented if user cannot update a collection" do
359 # visit a publicly accessible collection as 'spectator'
360 visit page_with_token('spectator', '/collections/zzzzz-4zz18-uukreo9rbgwsujr')
362 click_button 'Selection'
363 within('.selection-action-container') do
364 assert_selector 'li', text: 'Create new collection with selected files'
365 assert_no_selector 'li', text: 'Remove selected files'
368 within('.collection_files') do
369 assert(page.has_text?('GNU_General_Public_License'), 'file not found - GNU_General_Public_License')
370 assert_nil first('.fa-pencil')
371 assert_nil first('.fa-trash-o')
375 test "unlock collection to modify files" do
376 need_selenium 'to confirm remove'
378 collection = api_fixture('collections')['collection_owned_by_active']
380 # On load, collection is locked, and upload tab, rename and remove options are disabled
381 visit page_with_token('active', "/collections/#{collection['uuid']}")
383 assert_selector 'a[data-toggle="disabled"]', text: 'Upload'
385 within('.collection_files') do
386 file_ctrls = page.all('.btn-collection-file-control')
387 assert_equal 2, file_ctrls.size
388 assert_equal true, file_ctrls[0]['class'].include?('disabled')
389 assert_equal true, file_ctrls[1]['class'].include?('disabled')
390 find('input[type=checkbox]').click
393 click_button 'Selection'
394 within('.selection-action-container') do
395 assert_selector 'li.disabled', text: 'Remove selected files'
396 assert_selector 'li', text: 'Create new collection with selected files'
401 assert_no_selector 'a[data-toggle="disabled"]', text: 'Upload'
402 assert_selector 'a', text: 'Upload'
404 within('.collection_files') do
405 file_ctrls = page.all('.btn-collection-file-control')
406 assert_equal 2, file_ctrls.size
407 assert_equal false, file_ctrls[0]['class'].include?('disabled')
408 assert_equal false, file_ctrls[1]['class'].include?('disabled')
410 # previous checkbox selection won't result in firing a new event;
411 # undo and redo checkbox to fire the selection event again
412 find('input[type=checkbox]').click
413 find('input[type=checkbox]').click
416 click_button 'Selection'
417 within('.selection-action-container') do
418 assert_no_selector 'li.disabled', text: 'Remove selected files'
419 assert_selector 'li', text: 'Remove selected files'
423 def unlock_collection
424 first('.lock-collection-btn').click
428 test "collection tags tab" do
431 visit page_with_token('active', '/collections/zzzzz-4zz18-bv31uwvy3neko21')
436 # verify initial state
437 assert_selector 'a', text: 'Edit'
438 assert_no_selector 'a', text: 'Add new tag'
439 assert_no_selector 'a', text: 'Save'
440 assert_no_selector 'a', text: 'Cancel'
442 # Verify controls in edit mode
443 first('.edit-collection-tags').click
444 assert_selector 'a.disabled', text: 'Edit'
445 assert_selector 'a', text: 'Add new tag'
446 assert_selector 'a', text: 'Save'
447 assert_selector 'a', text: 'Cancel'
450 first('.edit-collection-tags').click
452 first('.glyphicon-plus').click
453 first('.collection-tag-field-key').click
454 first('.collection-tag-field-key').set('key 1')
455 first('.collection-tag-field-value').click
456 first('.collection-tag-field-value').set('value 1')
458 first('.glyphicon-plus').click
459 editable_key_fields = page.all('.collection-tag-field-key')
460 editable_key_fields[1].click
461 editable_key_fields[1].set('key 2')
462 editable_val_fields = page.all('.collection-tag-field-value')
463 editable_val_fields[1].click
464 editable_val_fields[1].set('value 2')
471 assert_text 'value 1'
473 assert_text 'value 2'
474 assert_selector 'a', text: 'Edit'
475 assert_no_selector 'a', text: 'Save'
478 first('.edit-collection-tags').click
479 assert_not_nil first('.glyphicon-remove')
480 first('.glyphicon-remove').click
485 assert_text 'value 2'
486 assert_no_text 'key 1'
487 assert_no_text 'value 1'
488 assert_selector 'a', text: 'Edit'
490 # Click on cancel and verify
491 first('.edit-collection-tags').click
492 first('.collection-tag-field-key').click
493 first('.collection-tag-field-key').set('this key wont stick')
494 first('.collection-tag-field-value').click
495 first('.collection-tag-field-value').set('this value wont stick')
501 assert_text 'value 2'
502 assert_no_text 'this key wont stick'
503 assert_no_text 'this value wont stick'
506 first('.edit-collection-tags').click
507 first('.glyphicon-remove').click
511 assert_selector 'a', text: 'Edit'
512 assert_no_text 'key 2'
513 assert_no_text 'value 2'