Merge branch 'master' into 3654-combine-selections
[arvados.git] / apps / workbench / test / integration / projects_test.rb
1 require 'integration_helper'
2 require 'selenium-webdriver'
3 require 'headless'
4
5 class ProjectsTest < ActionDispatch::IntegrationTest
6   setup do
7     Capybara.current_driver = Capybara.javascript_driver
8   end
9
10   test 'Find a project and edit its description' do
11     visit page_with_token 'active', '/'
12     find('.arv-project-list a,button', text: 'A Project').
13       click
14     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
15       find('span', text: api_fixture('groups')['aproject']['name']).click
16       within('.arv-description-as-subtitle') do
17         find('.fa-pencil').click
18         find('.editable-input textarea').set('I just edited this.')
19         find('.editable-submit').click
20       end
21       wait_for_ajax
22     end
23     visit current_path
24     assert(find?('.container-fluid', text: 'I just edited this.'),
25            "Description update did not survive page refresh")
26   end
27
28   test 'Find a project and edit description to textile description' do
29     visit page_with_token 'active', '/'
30     find('.arv-project-list a,button', text: 'A Project').
31       click
32     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
33       find('span', text: api_fixture('groups')['aproject']['name']).click
34       within('.arv-description-as-subtitle') do
35         find('.fa-pencil').click
36         find('.editable-input textarea').set('<p>*Textile description for A project* - "take me home":/ </p><p>And a new paragraph in description.</p>')
37         find('.editable-submit').click
38       end
39       wait_for_ajax
40     end
41
42     # visit project page
43     visit current_path
44     assert(has_no_text?('.container-fluid', text: '*Textile description for A project*'),
45            "Description is not rendered properly")
46     assert(find?('.container-fluid', text: 'Textile description for A project'),
47            "Description update did not survive page refresh")
48     assert(find?('.container-fluid', text: 'And a new paragraph in description'),
49            "Description did not contain the expected new paragraph")
50     assert(page.has_link?("take me home"), "link not found in description")
51
52     click_link 'take me home'
53
54     # now in dashboard
55     assert(page.has_text?('My projects'), 'My projects - not found on dashboard')
56     assert(page.has_text?('Projects shared with me'), 'Projects shared with me - not found on dashboard')
57     assert(page.has_text?('Textile description for A project'), "Project description not found")
58     assert(page.has_no_text?('*Textile description for A project*'), "Project description is not rendered properly in dashboard")
59     assert(page.has_no_text?('And a new paragraph in description'), "Project description is not truncated after first paragraph")
60   end
61
62   test 'Find a project and edit description to html description' do
63     visit page_with_token 'active', '/'
64     find('.arv-project-list a,button', text: 'A Project').
65       click
66     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
67       find('span', text: api_fixture('groups')['aproject']['name']).click
68       within('.arv-description-as-subtitle') do
69         find('.fa-pencil').click
70         find('.editable-input textarea').set('<br>Textile description for A project</br> - <a href="/">take me home</a>')
71         find('.editable-submit').click
72       end
73       wait_for_ajax
74     end
75     visit current_path
76     assert(find?('.container-fluid', text: 'Textile description for A project'),
77            "Description update did not survive page refresh")
78     assert(!find?('.container-fluid', text: '<br>Textile description for A project</br>'),
79            "Textile description is displayed with uninterpreted formatting characters")
80     assert(page.has_link?("take me home"),"link not found in description")
81     click_link 'take me home'
82     assert page.has_text?('My projects')
83     assert page.has_text?('Projects shared with me')
84   end
85
86   test 'Find a project and edit description to textile description with link to object' do
87     visit page_with_token 'active', '/'
88     find('.arv-project-list a,button', text: 'A Project').
89       click
90     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
91       find('span', text: api_fixture('groups')['aproject']['name']).click
92       within('.arv-description-as-subtitle') do
93         find('.fa-pencil').click
94         find('.editable-input textarea').set('*Textile description for A project* - "go to sub-project":' + api_fixture('groups')['asubproject']['uuid'] + "'")
95         find('.editable-submit').click
96       end
97       wait_for_ajax
98     end
99     visit current_path
100     assert(find?('.container-fluid', text: 'Textile description for A project'),
101            "Description update did not survive page refresh")
102     assert(!find?('.container-fluid', text: '*Textile description for A project*'),
103            "Textile description is displayed with uninterpreted formatting characters")
104     assert(page.has_link?("go to sub-project"), "link not found in description")
105     click_link 'go to sub-project'
106     assert(page.has_text?(api_fixture('groups')['asubproject']['name']), 'sub-project name not found after clicking link')
107   end
108
109   test 'Add a new name, then edit it, without creating a duplicate' do
110     project_uuid = api_fixture('groups')['aproject']['uuid']
111     specimen_uuid = api_fixture('traits')['owned_by_aproject_with_no_name']['uuid']
112     visit page_with_token 'active', '/projects/' + project_uuid
113     click_link 'Other objects'
114     within '.selection-action-container' do
115       # Wait for the tab to load:
116       assert_selector 'tr[data-kind="arvados#trait"]'
117       within first('tr', text: 'Trait') do
118         find(".fa-pencil").click
119         find('.editable-input input').set('Now I have a name.')
120         find('.glyphicon-ok').click
121         assert_selector '.editable', text: 'Now I have a name.'
122         find(".fa-pencil").click
123         find('.editable-input input').set('Now I have a new name.')
124         find('.glyphicon-ok').click
125       end
126       wait_for_ajax
127       assert_selector '.editable', text: 'Now I have a new name.'
128     end
129     visit current_path
130     click_link 'Other objects'
131     within '.selection-action-container' do
132       find '.editable', text: 'Now I have a new name.'
133       page.assert_no_selector '.editable', text: 'Now I have a name.'
134     end
135   end
136
137   test 'Create a project and move it into a different project' do
138     visit page_with_token 'active', '/projects'
139     find('.btn', text: "Add new project").click
140
141     # within('.editable', text: 'New project') do
142     within('h2') do
143       find('.fa-pencil').click
144       find('.editable-input input').set('Project 1234')
145       find('.glyphicon-ok').click
146     end
147     wait_for_ajax
148
149     visit '/projects'
150     find('.btn', text: "Add new project").click
151     within('h2') do
152       find('.fa-pencil').click
153       find('.editable-input input').set('Project 5678')
154       find('.glyphicon-ok').click
155     end
156     wait_for_ajax
157
158     click_link 'Move project...'
159     find('.selectable', text: 'Project 1234').click
160     find('.modal-footer a,button', text: 'Move').click
161     wait_for_ajax
162
163     # Wait for the page to refresh and show the new parent in Sharing panel
164     click_link 'Sharing'
165     assert(page.has_link?("Project 1234"),
166            "Project 5678 should now be inside project 1234")
167   end
168
169   def show_project_using(auth_key, proj_key='aproject')
170     project_uuid = api_fixture('groups')[proj_key]['uuid']
171     visit(page_with_token(auth_key, "/projects/#{project_uuid}"))
172     assert(page.has_text?("A Project"), "not on expected project page")
173   end
174
175   def share_rows
176     find('#project_sharing').all('tr')
177   end
178
179   def add_share_and_check(share_type, name)
180     assert(page.has_no_text?(name), "project is already shared with #{name}")
181     start_share_count = share_rows.size
182     click_on("Share with #{share_type}")
183     within(".modal-container") do
184       # Order is important here: we should find something that appears in the
185       # modal before we make any assertions about what's not in the modal.
186       # Otherwise, the not-included assertions might falsely pass because
187       # the modal hasn't loaded yet.
188       find(".selectable", text: name).click
189       assert(has_no_selector?(".modal-dialog-preview-pane"),
190              "preview pane available in sharing dialog")
191       assert_raises(Capybara::ElementNotFound,
192                     "Projects pulldown available from sharing dialog") do
193         click_on "All projects"
194       end
195       click_on "Add"
196     end
197     using_wait_time(Capybara.default_wait_time * 3) do
198       assert(page.has_link?(name),
199              "new share was not added to sharing table")
200       assert_equal(start_share_count + 1, share_rows.size,
201                    "new share did not add row to sharing table")
202     end
203   end
204
205   def modify_share_and_check(name)
206     start_rows = share_rows
207     link_row = start_rows.select { |row| row.has_text?(name) }
208     assert_equal(1, link_row.size, "row with new permission not found")
209     within(link_row.first) do
210       click_on("Read")
211       select("Write", from: "share_change_level")
212       click_on("editable-submit")
213       assert(has_link?("Write"),
214              "failed to change access level on new share")
215       click_on "Revoke"
216     end
217     using_wait_time(Capybara.default_wait_time * 3) do
218       assert(page.has_no_text?(name),
219              "new share row still exists after being revoked")
220       assert_equal(start_rows.size - 1, share_rows.size,
221                    "revoking share did not remove row from sharing table")
222     end
223   end
224
225   test "project viewer can't see project sharing tab" do
226     show_project_using("project_viewer")
227     assert(page.has_no_link?("Sharing"),
228            "read-only project user sees sharing tab")
229   end
230
231   test "project owner can manage sharing for another user" do
232     add_user = api_fixture('users')['future_project_user']
233     new_name = ["first_name", "last_name"].map { |k| add_user[k] }.join(" ")
234
235     show_project_using("active")
236     click_on "Sharing"
237     add_share_and_check("users", new_name)
238     modify_share_and_check(new_name)
239   end
240
241   test "project owner can manage sharing for another group" do
242     new_name = api_fixture('groups')['future_project_viewing_group']['name']
243
244     show_project_using("active")
245     click_on "Sharing"
246     add_share_and_check("groups", new_name)
247     modify_share_and_check(new_name)
248   end
249
250   test "'share with group' listing does not offer projects" do
251     show_project_using("active")
252     click_on "Sharing"
253     click_on "Share with groups"
254     good_uuid = api_fixture("groups")["private"]["uuid"]
255     assert(page.has_selector?(".selectable[data-object-uuid=\"#{good_uuid}\"]"),
256            "'share with groups' listing missing owned user group")
257     bad_uuid = api_fixture("groups")["asubproject"]["uuid"]
258     assert(page.has_no_selector?(".selectable[data-object-uuid=\"#{bad_uuid}\"]"),
259            "'share with groups' listing includes project")
260   end
261
262   [
263     'Move',
264     'Remove',
265     'Copy',
266   ].each do |action|
267     test "selection #{action} for project" do
268       src = api_fixture('groups')['aproject']
269       dest = api_fixture('groups')['asubproject']
270       my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
271
272       perform_selection_action src, dest, my_collection, action
273
274       case action
275       when 'Copy'
276         assert page.has_text?(my_collection['name']), 'Collection not found in src project after copy'
277         visit page_with_token 'active', '/'
278         find('.arv-project-list a,button', text: dest['name']).click
279         assert page.has_text?(my_collection['name']), 'Collection not found in dest project after copy'
280
281         # now remove it from destination project to restore to original state
282         perform_selection_action dest, nil, my_collection, 'Remove'
283       when 'Move'
284         assert page.has_no_text?(my_collection['name']), 'Collection still found in src project after move'
285         visit page_with_token 'active', '/'
286         find('.arv-project-list a,button', text: dest['name']).click
287         assert page.has_text?(my_collection['name']), 'Collection not found in dest project after move'
288
289         # move it back to src project to restore to original state
290         perform_selection_action dest, src, my_collection, action
291       when 'Remove'
292         assert page.has_no_text?(my_collection['name']), 'Collection still found in src project after remove'
293         visit page_with_token 'active', '/'
294         find('.arv-project-list a,button', text: 'Home').click
295         assert page.has_text?(my_collection['name']), 'Collection not found in home project after remove'
296       end
297     end
298   end
299
300   def perform_selection_action src, dest, item, action
301     visit page_with_token 'active', '/'
302     find('.arv-project-list a,button', text: src['name']).click
303     assert page.has_text?(item['name']), 'Collection not found in src project'
304
305     within('tr', text: item['name']) do
306       find('input[type=checkbox]').click
307     end
308
309     click_button 'Selection...'
310
311     within('.selection-action-container') do
312       assert page.has_text?("Compare selected"), "Compare selected link text not found"
313       assert page.has_link?("Copy selected"), "Copy selected link not found"
314       assert page.has_link?("Move selected"), "Move selected link not found"
315       assert page.has_link?("Remove selected"), "Remove selected link not found"
316
317       click_link "#{action} selected"
318     end
319
320     # select the destination project if a Copy or Move action is being performed
321     if action == 'Copy' || action == 'Move'
322       within(".modal-container") do
323         find('.selectable', text: dest['name']).click
324         find('.modal-footer a,button', text: action).click
325         wait_for_ajax
326       end
327     end
328   end
329
330   # Test copy action state. It should not be available when a subproject is selected.
331   test "copy action is disabled when a subproject is selected" do
332     my_project = api_fixture('groups')['aproject']
333     my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
334     my_subproject = api_fixture('groups')['asubproject']
335
336     # verify that selection options are disabled on the project until an item is selected
337     visit page_with_token 'active', '/'
338     find('.arv-project-list a,button', text: my_project['name']).click
339
340     click_button 'Selection...'
341     within('.selection-action-container') do
342       page.assert_selector 'li.disabled', text: 'Combine selections into a new collection'
343       page.assert_selector 'li.disabled', text: 'Compare selected'
344       page.assert_selector 'li.disabled', text: 'Copy selected'
345       page.assert_selector 'li.disabled', text: 'Move selected'
346       page.assert_selector 'li.disabled', text: 'Remove selected'
347     end
348
349     # select collection and verify links are enabled
350     visit page_with_token 'active', '/'
351     find('.arv-project-list a,button', text: my_project['name']).click
352     assert page.has_text?(my_collection['name']), 'Collection not found in project'
353
354     within('tr', text: my_collection['name']) do
355       find('input[type=checkbox]').click
356     end
357
358     click_button 'Selection...'
359     within('.selection-action-container') do
360       page.assert_no_selector 'li.disabled', text: 'Combine selections into a new collection'
361       page.assert_selector 'li', text: 'Combine selections into a new collection'
362       page.assert_selector 'li.disabled', text: 'Compare selected'
363       page.assert_no_selector 'li.disabled', text: 'Copy selected'
364       page.assert_selector 'li', text: 'Copy selected'
365       page.assert_no_selector 'li.disabled', text: 'Move selected'
366       page.assert_selector 'li', text: 'Move selected'
367       page.assert_no_selector 'li.disabled', text: 'Remove selected'
368       page.assert_selector 'li', text: 'Remove selected'
369     end
370
371     # select subproject and verify that copy action is disabled
372     visit page_with_token 'active', '/'
373     find('.arv-project-list a,button', text: my_project['name']).click
374
375     click_link 'Subprojects'
376     assert page.has_text?(my_subproject['name']), 'Subproject not found in project'
377
378     within('tr', text: my_subproject['name']) do
379       find('input[type=checkbox]').click
380     end
381
382     click_button 'Selection...'
383     within('.selection-action-container') do
384       page.assert_selector 'li.disabled', text: 'Combine selections into a new collection'
385       page.assert_selector 'li.disabled', text: 'Compare selected'
386       page.assert_selector 'li.disabled', text: 'Copy selected'
387       page.assert_no_selector 'li.disabled', text: 'Move selected'
388       page.assert_selector 'li', text: 'Move selected'
389       page.assert_no_selector 'li.disabled', text: 'Remove selected'
390       page.assert_selector 'li', text: 'Remove selected'
391     end
392
393     # select subproject and a collection and verify that copy action is still disabled
394     visit page_with_token 'active', '/'
395     find('.arv-project-list a,button', text: my_project['name']).click
396
397     click_link 'Subprojects'
398     assert page.has_text?(my_subproject['name']), 'Subproject not found in project'
399
400     within('tr', text: my_subproject['name']) do
401       find('input[type=checkbox]').click
402     end
403
404     click_link 'Data collections'
405     assert page.has_text?(my_collection['name']), 'Collection not found in project'
406
407     within('tr', text: my_collection['name']) do
408       find('input[type=checkbox]').click
409     end
410
411     click_button 'Selection...'
412     within('.selection-action-container') do
413       page.assert_selector 'li.disabled', text: 'Combine selections into a new collection'
414       page.assert_selector 'li.disabled', text: 'Compare selected'
415       page.assert_selector 'li.disabled', text: 'Copy selected'
416       page.assert_no_selector 'li.disabled', text: 'Move selected'
417       page.assert_selector 'li', text: 'Move selected'
418       page.assert_no_selector 'li.disabled', text: 'Remove selected'
419       page.assert_selector 'li', text: 'Remove selected'
420     end
421   end
422
423   test "combine selections into new collection" do
424     my_project = api_fixture('groups')['aproject']
425     my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
426
427     visit page_with_token 'active', '/'
428     find('.arv-project-list a,button', text: my_project['name']).click
429     assert page.has_text?(my_collection['name']), 'Collection not found in project'
430
431     within('tr', text: my_collection['name']) do
432       find('input[type=checkbox]').click
433     end
434
435     click_button 'Selection...'
436     within('.selection-action-container') do
437       click_link 'Combine selections into a new collection'
438     end
439
440     # back in project page
441     assert page.has_text?(my_collection['name']), 'Collection not found in project'
442     assert page.has_link?('Jobs and pipelines'), 'Jobs and pipelines link not found in project'
443   end
444
445 end