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("/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)
55 test "can download an entire collection with a reader token" do
56 token = api_token('active')
58 datablock = `echo -n #{data.shellescape} | ARVADOS_API_TOKEN=#{token.shellescape} arv-put --no-progress --raw -`.strip
59 assert $?.success?, $?
63 mtxt = ". #{datablock} 0:#{data.length}:foo\n"
64 col = Collection.create(manifest_text: mtxt)
68 token = api_fixture('api_client_authorizations')['active_all_collections']['api_token']
69 url_head = "/collections/download/#{uuid}/#{token}/"
71 # It seems that Capybara can't inspect tags outside the body, so this is
72 # a very blunt approach.
73 assert_no_match(/<\s*meta[^>]+\bnofollow\b/i, page.html,
74 "wget prohibited from recursing the collection page")
75 # Look at all the links that wget would recurse through using our
76 # recommended options, and check that it's exactly the file list.
77 hrefs = page.all('a').map do |anchor|
78 link = anchor[:href] || ''
79 if link.start_with? url_head
80 link[url_head.size .. -1]
81 elsif link.start_with? '/'
87 assert_equal(['./foo'], hrefs.compact.sort,
88 "download page did provide strictly file links")
90 assert_text "foo\nfile\n"
93 test "combine selected collections into new collection" do
94 foo_collection = api_fixture('collections')['foo_file']
95 bar_collection = api_fixture('collections')['bar_file']
97 visit page_with_token('active', "/collections")
99 assert(page.has_text?(foo_collection['uuid']), "Collection page did not include foo file")
100 assert(page.has_text?(bar_collection['uuid']), "Collection page did not include bar file")
102 within "tr[data-object-uuid=\"#{foo_collection['uuid']}\"]" do
103 find('input[type=checkbox]').click
106 within "tr[data-object-uuid=\"#{bar_collection['uuid']}\"]" do
107 find('input[type=checkbox]').click
110 click_button 'Selection...'
111 within('.selection-action-container') do
112 click_link 'Create new collection with selected collections'
115 # now in the newly created collection page
116 assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
117 assert(page.has_no_text?(foo_collection['name']), "Collection page did not include foo file")
118 assert(page.has_text?('foo'), "Collection page did not include foo file")
119 assert(page.has_no_text?(bar_collection['name']), "Collection page did not include foo file")
120 assert(page.has_text?('bar'), "Collection page did not include bar file")
121 assert(page.has_text?('Created new collection in your Home project'),
122 'Not found flash message that new collection is created in Home project')
126 ['active', 'foo_file', false],
127 ['active', 'foo_collection_in_aproject', true],
128 ['project_viewer', 'foo_file', false],
129 ['project_viewer', 'foo_collection_in_aproject', false], #aproject not writable
130 ].each do |user, collection, expect_collection_in_aproject|
131 test "combine selected collection files into new collection #{user} #{collection} #{expect_collection_in_aproject}" do
132 my_collection = api_fixture('collections')[collection]
134 visit page_with_token(user, "/collections")
136 # choose file from foo collection
137 within('tr', text: my_collection['uuid']) do
141 # now in collection page
142 find('input[type=checkbox]').click
144 click_button 'Selection...'
145 within('.selection-action-container') do
146 click_link 'Create new collection with selected files'
149 # now in the newly created collection page
150 assert(page.has_text?('Copy to project'), "Copy to project text not found in new collection page")
151 assert(page.has_no_text?(my_collection['name']), "Collection page did not include foo file")
152 assert(page.has_text?('foo'), "Collection page did not include foo file")
153 if expect_collection_in_aproject
154 aproject = api_fixture('groups')['aproject']
155 assert page.has_text?("Created new collection in the project #{aproject['name']}"),
156 'Not found flash message that new collection is created in aproject'
158 assert page.has_text?("Created new collection in your Home project"),
159 'Not found flash message that new collection is created in Home project'
164 test "combine selected collection files from collection subdirectory" do
165 visit page_with_token('user1_with_load', "/collections/zzzzz-4zz18-filesinsubdir00")
167 # now in collection page
168 input_files = page.all('input[type=checkbox]')
169 (0..input_files.count-1).each do |i|
173 click_button 'Selection...'
174 within('.selection-action-container') do
175 click_link 'Create new collection with selected files'
178 # now in the newly created collection page
179 assert(page.has_text?('file_in_subdir1'), 'file not found - file_in_subdir1')
180 assert(page.has_text?('file1_in_subdir3.txt'), 'file not found - file1_in_subdir3.txt')
181 assert(page.has_text?('file2_in_subdir3.txt'), 'file not found - file2_in_subdir3.txt')
182 assert(page.has_text?('file1_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
183 assert(page.has_text?('file2_in_subdir4.txt'), 'file not found - file1_in_subdir4.txt')
186 test "Collection portable data hash with multiple matches with more than one page of results" do
187 pdh = api_fixture('collections')['baz_file']['portable_data_hash']
188 visit page_with_token('admin', "/collections/#{pdh}")
190 assert_selector 'a', text: 'Collection_1'
192 assert_text 'The following collections have this content:'
193 assert_text 'more results are not shown'
194 assert_no_text 'Activity'
195 assert_no_text 'Sharing and permissions'
198 test "Filtering collection files by regexp" do
199 col = api_fixture('collections', 'multilevel_collection_1')
200 visit page_with_token('active', "/collections/#{col['uuid']}")
202 # Filter file list to some but not all files in the collection
203 page.find_field('file_regex').set('file[12]')
204 assert page.has_text?("file1")
205 assert page.has_text?("file2")
206 assert page.has_no_text?("file3")
208 # Filter file list with a regex matching all files
209 page.find_field('file_regex').set('.*')
210 assert page.has_text?("file1")
211 assert page.has_text?("file2")
212 assert page.has_text?("file3")
214 # Filter file list to a regex matching no files
215 page.find_field('file_regex').set('file9')
216 assert page.has_no_text?("file1")
217 assert page.has_no_text?("file2")
218 assert page.has_no_text?("file3")
219 # make sure that we actually are looking at the collections
220 # page and not e.g. a fiddlesticks
221 assert page.has_text?("multilevel_collection_1")
222 assert page.has_text?(col["name"] || col["uuid"])
224 # Set filename filter to a syntactically invalid regex
225 # Page loads, but stops filtering after the last valid regex parse
226 page.find_field('file_regex').set('file[2')
227 assert page.has_text?("multilevel_collection_1")
228 assert page.has_text?(col["name"] || col["uuid"])
229 assert page.has_text?("file1")
230 assert page.has_text?("file2")
231 assert page.has_text?("file3")
233 # Test the "Select all" button
235 # Note: calling .set('') on a Selenium element is not sufficient
236 # to reset the field for this test, as it does not send any key
237 # events to the browser. To clear the field, we must instead send
238 # a backspace character.
239 # See https://selenium.googlecode.com/svn/trunk/docs/api/rb/Selenium/WebDriver/Element.html#clear-instance_method
240 page.find_field('file_regex').set("\b") # backspace
241 find('button#select-all').click
242 assert_checkboxes_state('input[type=checkbox]', true, '"select all" should check all checkboxes')
244 # Test the "Unselect all" button
245 page.find_field('file_regex').set("\b") # backspace
246 find('button#unselect-all').click
247 assert_checkboxes_state('input[type=checkbox]', false, '"unselect all" should clear all checkboxes')
249 # Filter files, then "select all", then unfilter
250 page.find_field('file_regex').set("\b") # backspace
251 find('button#unselect-all').click
252 page.find_field('file_regex').set('file[12]')
253 find('button#select-all').click
254 page.find_field('file_regex').set("\b") # backspace
256 # all "file1" and "file2" checkboxes must be selected
257 # all "file3" checkboxes must be clear
258 assert_checkboxes_state('[value*="file1"]', true, 'checkboxes for file1 should be selected after filtering')
259 assert_checkboxes_state('[value*="file2"]', true, 'checkboxes for file2 should be selected after filtering')
260 assert_checkboxes_state('[value*="file3"]', false, 'checkboxes for file3 should be clear after filtering')
262 # Select all files, then filter, then "unselect all", then unfilter
263 page.find_field('file_regex').set("\b") # backspace
264 find('button#select-all').click
265 page.find_field('file_regex').set('file[12]')
266 find('button#unselect-all').click
267 page.find_field('file_regex').set("\b") # backspace
269 # all "file1" and "file2" checkboxes must be clear
270 # all "file3" checkboxes must be selected
271 assert_checkboxes_state('[value*="file1"]', false, 'checkboxes for file1 should be clear after filtering')
272 assert_checkboxes_state('[value*="file2"]', false, 'checkboxes for file2 should be clear after filtering')
273 assert_checkboxes_state('[value*="file3"]', true, 'checkboxes for file3 should be selected after filtering')
276 test "Creating collection from list of filtered files" do
277 col = api_fixture('collections', 'collection_with_files_in_subdir')
278 visit page_with_token('user1_with_load', "/collections/#{col['uuid']}")
279 assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not found'
280 assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not found'
281 assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not found'
282 assert page.has_text?('file1_in_subdir4'), 'expected file1_in_subdir4 not found'
283 assert page.has_text?('file2_in_subdir4'), 'expected file2_in_subdir4 not found'
285 # Select all files but then filter them to files in subdir1, subdir2 or subdir3
286 find('button#select-all').click
287 page.find_field('file_regex').set('_in_subdir[123]')
288 assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not in filtered files'
289 assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not in filtered files'
290 assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not in filtered files'
291 assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in filtered files'
292 assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in filtered files'
294 # Create a new collection
295 click_button 'Selection...'
296 within('.selection-action-container') do
297 click_link 'Create new collection with selected files'
300 # now in the newly created collection page
301 # must have files in subdir1 and subdir3 but not subdir4
302 assert page.has_text?('file_in_subdir1'), 'file_in_subdir1 missing from new collection'
303 assert page.has_text?('file1_in_subdir3'), 'file1_in_subdir3 missing from new collection'
304 assert page.has_text?('file2_in_subdir3'), 'file2_in_subdir3 missing from new collection'
305 assert page.has_no_text?('file1_in_subdir4'), 'file1_in_subdir4 found in new collection'
306 assert page.has_no_text?('file2_in_subdir4'), 'file2_in_subdir4 found in new collection'
308 # Make sure we're not still on the old collection page.
309 refute_match(%r{/collections/#{col['uuid']}}, page.current_url)
312 test "remove a file from collection using checkbox and dropdown option" do
313 need_selenium 'to confirm unlock'
315 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
316 assert(page.has_text?('file1'), 'file not found - file1')
321 input_files = page.all('input[type=checkbox]')
324 click_button 'Selection...'
325 within('.selection-action-container') do
326 click_link 'Remove selected files'
329 assert(page.has_no_text?('file1'), 'file found - file')
330 assert(page.has_text?('file2'), 'file not found - file2')
333 test "remove a file in collection using trash icon" do
334 need_selenium 'to confirm unlock'
336 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
337 assert(page.has_text?('file1'), 'file not found - file1')
341 first('.fa-trash-o').click
344 assert(page.has_no_text?('file1'), 'file found - file')
345 assert(page.has_text?('file2'), 'file not found - file2')
348 test "rename a file in collection" do
349 need_selenium 'to confirm unlock'
351 visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
355 within('.collection_files') do
356 first('.fa-pencil').click
357 find('.editable-input input').set('file1renamed')
358 find('.editable-submit').click
361 assert(page.has_text?('file1renamed'), 'file not found - file1renamed')
364 test "remove/rename file options not presented if user cannot update a collection" do
365 # visit a publicly accessible collection as 'spectator'
366 visit page_with_token('spectator', '/collections/zzzzz-4zz18-uukreo9rbgwsujr')
368 click_button 'Selection'
369 within('.selection-action-container') do
370 assert_selector 'li', text: 'Create new collection with selected files'
371 assert_no_selector 'li', text: 'Remove selected files'
374 within('.collection_files') do
375 assert(page.has_text?('GNU_General_Public_License'), 'file not found - GNU_General_Public_License')
376 assert_nil first('.fa-pencil')
377 assert_nil first('.fa-trash-o')
381 test "unlock collection to modify files" do
382 need_selenium 'to confirm remove'
384 collection = api_fixture('collections')['collection_owned_by_active']
386 # On load, collection is locked, and upload tab, rename and remove options are disabled
387 visit page_with_token('active', "/collections/#{collection['uuid']}")
389 assert_selector 'a[data-toggle="disabled"]', text: 'Upload'
391 within('.collection_files') do
392 file_ctrls = page.all('.btn-collection-file-control')
393 assert_equal 2, file_ctrls.size
394 assert_equal true, file_ctrls[0]['class'].include?('disabled')
395 assert_equal true, file_ctrls[1]['class'].include?('disabled')
396 find('input[type=checkbox]').click
399 click_button 'Selection'
400 within('.selection-action-container') do
401 assert_selector 'li.disabled', text: 'Remove selected files'
402 assert_selector 'li', text: 'Create new collection with selected files'
407 assert_no_selector 'a[data-toggle="disabled"]', text: 'Upload'
408 assert_selector 'a', text: 'Upload'
410 within('.collection_files') do
411 file_ctrls = page.all('.btn-collection-file-control')
412 assert_equal 2, file_ctrls.size
413 assert_equal false, file_ctrls[0]['class'].include?('disabled')
414 assert_equal false, file_ctrls[1]['class'].include?('disabled')
416 # previous checkbox selection won't result in firing a new event;
417 # undo and redo checkbox to fire the selection event again
418 find('input[type=checkbox]').click
419 find('input[type=checkbox]').click
422 click_button 'Selection'
423 within('.selection-action-container') do
424 assert_no_selector 'li.disabled', text: 'Remove selected files'
425 assert_selector 'li', text: 'Remove selected files'
429 def unlock_collection
430 first('.lock-collection-btn').click