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
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}"
29 def check_sharing(want_state, link_regexp)
30 # We specifically want to click buttons. See #4291.
32 click_button "Unshare"
33 text_assertion = :assert_no_text
34 link_assertion = :assert_empty
36 click_button "Create sharing link"
37 text_assertion = :assert_text
38 link_assertion = :refute_empty
40 using_wait_time(Capybara.default_max_wait_time * 3) do
41 send(text_assertion, "Shared at:")
43 send(link_assertion, all("a").select { |a| a[:href] =~ link_regexp })
46 test "creating and uncreating a sharing link" do
47 coll_uuid = api_fixture("collections", "collection_owned_by_active", "uuid")
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)
57 test "can download an entire collection with a reader token" do
60 token = api_token('active')
62 datablock = `echo -n #{data.shellescape} | ARVADOS_API_TOKEN=#{token.shellescape} arv-put --no-progress --raw -`.strip
63 assert $?.success?, $?
67 mtxt = ". #{datablock} 0:#{data.length}:foo\n"
68 col = Collection.create(manifest_text: mtxt)
72 token = api_fixture('api_client_authorizations')['active_all_collections']['api_token']
73 url_head = "/collections/download/#{uuid}/#{token}/"
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? '/'
91 assert_equal(['./foo'], hrefs.compact.sort,
92 "download page did provide strictly file links")
94 assert_text "foo\nfile\n"
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']
101 visit page_with_token('active', "/collections")
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")
106 within "tr[data-object-uuid=\"#{foo_collection['uuid']}\"]" do
107 find('input[type=checkbox]').click
110 within "tr[data-object-uuid=\"#{bar_collection['uuid']}\"]" do
111 find('input[type=checkbox]').click
114 click_button 'Selection...'
115 within('.selection-action-container') do
116 click_link 'Create new collection with selected collections'
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')
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]
138 visit page_with_token(user, "/collections")
140 # choose file from foo collection
141 within('tr', text: my_collection['uuid']) do
145 # now in collection page
146 find('input[type=checkbox]').click
148 click_button 'Selection...'
149 within('.selection-action-container') do
150 click_link 'Create new collection with selected files'
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'
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'
168 test "combine selected collection files from collection subdirectory" do
169 visit page_with_token('user1_with_load', "/collections/zzzzz-4zz18-filesinsubdir00")
171 # now in collection page
172 input_files = page.all('input[type=checkbox]')
173 (0..input_files.count-1).each do |i|
177 click_button 'Selection...'
178 within('.selection-action-container') do
179 click_link 'Create new collection with selected files'
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')
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}")
194 assert_selector 'a', text: 'Collection_1'
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'
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']}")
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")
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")
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"])
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")
237 # Test the "Select all" button
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')
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')
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
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')
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
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')
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'
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'
298 # Create a new collection
299 click_button 'Selection...'
300 within('.selection-action-container') do
301 click_link 'Create new collection with selected files'
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'
312 # Make sure we're not still on the old collection page.
313 refute_match(%r{/collections/#{col['uuid']}}, page.current_url)
316 test "remove a file from collection using checkbox and dropdown option" do
317 need_selenium 'to confirm unlock'
319 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
320 assert(page.has_text?('file1'), 'file not found - file1')
325 input_files = page.all('input[type=checkbox]')
328 click_button 'Selection...'
329 within('.selection-action-container') do
330 click_link 'Remove selected files'
333 assert(page.has_no_text?('file1'), 'file found - file')
334 assert(page.has_text?('file2'), 'file not found - file2')
337 test "remove a file in collection using trash icon" do
338 need_selenium 'to confirm unlock'
340 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
341 assert(page.has_text?('file1'), 'file not found - file1')
345 first('.fa-trash-o').click
348 assert(page.has_no_text?('file1'), 'file found - file')
349 assert(page.has_text?('file2'), 'file not found - file2')
352 test "rename a file in collection" do
353 need_selenium 'to confirm unlock'
355 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
359 within('.collection_files') do
360 first('.fa-pencil').click
361 find('.editable-input input').set('file1renamed')
362 find('.editable-submit').click
365 assert(page.has_text?('file1renamed'), 'file not found - file1renamed')
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')
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'
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')
385 test "unlock collection to modify files" do
386 need_selenium 'to confirm remove'
388 collection = api_fixture('collections')['collection_owned_by_active']
390 # On load, collection is locked, and upload tab, rename and remove options are disabled
391 visit page_with_token('active', "/collections/#{collection['uuid']}")
393 assert_selector 'a[data-toggle="disabled"]', text: 'Upload'
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
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'
411 assert_no_selector 'a[data-toggle="disabled"]', text: 'Upload'
412 assert_selector 'a', text: 'Upload'
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')
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
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'
433 def unlock_collection
434 first('.lock-collection-btn').click