Merge branch '8784-dir-listings'
[arvados.git] / apps / workbench / test / integration / work_units_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'helpers/fake_websocket_helper'
6 require 'integration_helper'
7
8 class WorkUnitsTest < ActionDispatch::IntegrationTest
9   include FakeWebsocketHelper
10
11   setup do
12     need_javascript
13   end
14
15   test "scroll all_processes page" do
16       expected_min, expected_max, expected, not_expected = [
17         25, 100,
18         ['/pipeline_instances/zzzzz-d1hrv-1yfj61234abcdk3',
19          '/pipeline_instances/zzzzz-d1hrv-jobspeccomponts',
20          '/jobs/zzzzz-8i9sb-grx15v5mjnsyxk7',
21          '/jobs/zzzzz-8i9sb-n7omg50bvt0m1nf',
22          '/container_requests/zzzzz-xvhdp-cr4completedcr2',
23          '/container_requests/zzzzz-xvhdp-cr4requestercn2'],
24         ['/pipeline_instances/zzzzz-d1hrv-scarxiyajtshq3l',
25          '/container_requests/zzzzz-xvhdp-oneof60crs00001']
26       ]
27
28       visit page_with_token('active', "/all_processes")
29
30       page_scrolls = expected_max/20 + 2
31       within('.arv-recent-all-processes') do
32         (0..page_scrolls).each do |i|
33           page.driver.scroll_to 0, 999000
34           begin
35             wait_for_ajax
36           rescue
37           end
38         end
39       end
40
41       # Verify that expected number of processes are found
42       found_items = page.all('tr[data-object-uuid]')
43       found_count = found_items.count
44       if expected_min == expected_max
45         assert_equal(true, found_count == expected_min,
46           "Not found expected number of items. Expected #{expected_min} and found #{found_count}")
47         assert page.has_no_text? 'request failed'
48       else
49         assert_equal(true, found_count>=expected_min,
50           "Found too few items. Expected at least #{expected_min} and found #{found_count}")
51         assert_equal(true, found_count<=expected_max,
52           "Found too many items. Expected at most #{expected_max} and found #{found_count}")
53       end
54
55       # verify that all expected uuid links are found
56       expected.each do |link|
57         assert_selector "a[href=\"#{link}\"]"
58       end
59
60       # verify that none of the not_expected uuid links are found
61       not_expected.each do |link|
62         assert_no_selector "a[href=\"#{link}\"]"
63       end
64   end
65
66   [
67     ['jobs', 'running_job_with_components', true, true],
68     ['pipeline_instances', 'components_is_jobspec', true, true],
69     ['containers', 'running', false],
70     ['container_requests', 'running', true],
71   ].each do |type, fixture, cancelable, confirm_cancellation|
72     test "cancel button for #{type}/#{fixture}" do
73       if cancelable
74         need_selenium 'to cancel'
75       end
76
77       obj = api_fixture(type)[fixture]
78       visit page_with_token "active", "/#{type}/#{obj['uuid']}"
79
80       assert_text 'created_at'
81       if cancelable
82         assert_text 'priority: 1' if type.include?('container')
83         if type.include?('pipeline')
84           assert_selector 'a', text: 'Pause'
85           first('a,link', text: 'Pause').click
86         else
87           assert_selector 'button', text: 'Cancel'
88           first('a,button', text: 'Cancel').click
89         end
90         if confirm_cancellation
91           alert = page.driver.browser.switch_to.alert
92           alert.accept
93         end
94         wait_for_ajax
95       end
96
97       if type.include?('pipeline')
98         assert_selector 'a', text: 'Resume'
99         assert_no_selector 'a', text: 'Pause'
100       elsif type.include?('job')
101         assert_text 'Cancelled'
102         assert_text 'Paused'  # this job has a pipeline child which was also cancelled
103         assert_no_selector 'button', text: 'Cancel'
104       elsif cancelable
105         assert_text 'priority: 0'
106       end
107     end
108   end
109
110   [
111     ['jobs', 'running_job_with_components'],
112     ['pipeline_instances', 'has_component_with_completed_jobs'],
113     ['container_requests', 'running'],
114     ['container_requests', 'completed'],
115   ].each do |type, fixture|
116     test "edit description for #{type}/#{fixture}" do
117       obj = api_fixture(type)[fixture]
118       visit page_with_token "active", "/#{type}/#{obj['uuid']}"
119
120       within('.arv-description-as-subtitle') do
121         find('.fa-pencil').click
122         find('.editable-input textarea').set('*Textile description for object*')
123         find('.editable-submit').click
124       end
125       wait_for_ajax
126
127       # verify description
128       assert page.has_no_text? '*Textile description for object*'
129       assert page.has_text? 'Textile description for object'
130     end
131   end
132
133   [
134     ['Pipeline with default input specifications', 'part-one', 'Provide values for the following'],
135     ['Workflow with default input specifications', 'this workflow has inputs specified', 'Provide a value for the following'],
136   ].each do |template_name, preview_txt, process_txt|
137     test "run a process using template #{template_name} from dashboard" do
138       visit page_with_token('admin')
139       assert_text 'Recent pipelines and processes' # seeing dashboard now
140
141       within('.recent-processes-actions') do
142         assert page.has_link?('All processes')
143         find('a', text: 'Run a process').click
144       end
145
146       # in the chooser, verify preview and click Next button
147       within('.modal-dialog') do
148         find('.selectable', text: template_name).click
149         assert_text preview_txt
150         find('.btn', text: 'Next: choose inputs').click
151       end
152
153       # in the process page now
154       assert_text process_txt
155       assert_selector 'a', text: template_name
156
157       assert_equal "Set value for ex_string_def", find('div.form-group > div > p.form-control-static > a', text: "hello-testing-123")[:"data-title"]
158
159       page.assert_selector 'a.disabled,button.disabled', text: 'Run'
160     end
161   end
162
163   test 'display container state changes in Container Request live log' do
164     use_fake_websocket_driver
165     c = api_fixture('containers')['queued']
166     cr = api_fixture('container_requests')['queued']
167     visit page_with_token('active', '/container_requests/'+cr['uuid'])
168     click_link('Log')
169
170     # The attrs of the "terminal window" text div in the log tab
171     # indicates which objects' events are worth displaying. Events
172     # that arrive too early (before that div exists) are not
173     # shown. For the user's sake, these early logs should also be
174     # retrieved and shown one way or another -- but in this particular
175     # test, we are only interested in logs that arrive by
176     # websocket. Therefore, to avoid races, we wait for the log tab to
177     # display before sending any events.
178     assert_text 'Recent logs'
179
180     [[{
181         event_type: 'dispatch',
182         properties: {
183           text: "dispatch logged a fake message\n",
184         },
185       }, "dispatch logged"],
186      [{
187         event_type: 'update',
188         properties: {
189           old_attributes: {state: 'Locked'},
190           new_attributes: {state: 'Queued'},
191         },
192       }, "Container #{c['uuid']} was returned to the queue"],
193      [{
194         event_type: 'update',
195         properties: {
196           old_attributes: {state: 'Queued'},
197           new_attributes: {state: 'Locked'},
198         },
199       }, "Container #{c['uuid']} was taken from the queue by a dispatch process"],
200      [{
201         event_type: 'crunch-run',
202         properties: {
203           text: "according to fake crunch-run,\nsome setup stuff happened on the compute node\n",
204         },
205       }, "setup stuff happened"],
206      [{
207         event_type: 'update',
208         properties: {
209           old_attributes: {state: 'Locked'},
210           new_attributes: {state: 'Running'},
211         },
212       }, "Container #{c['uuid']} started"],
213      [{
214         event_type: 'update',
215         properties: {
216           old_attributes: {state: 'Running'},
217           new_attributes: {state: 'Complete', exit_code: 1},
218         },
219       }, "Container #{c['uuid']} finished with exit code 1 (failure)"],
220      # It's unrealistic for state to change again once it's Complete,
221      # but the logging code doesn't care, so we do it to keep the test
222      # simple.
223      [{
224         event_type: 'update',
225         properties: {
226           old_attributes: {state: 'Running'},
227           new_attributes: {state: 'Cancelled'},
228         },
229       }, "Container #{c['uuid']} was cancelled"],
230     ].each do |send_event, expect_log_text|
231       assert_no_text(expect_log_text)
232       fake_websocket_event(send_event.merge(object_uuid: c['uuid']))
233       assert_text(expect_log_text)
234     end
235   end
236
237   [
238     ['jobs', 'active', 'running_job_with_components', 'component1', '/jobs/zzzzz-8i9sb-jyq01m7in1jlofj#Log'],
239     ['pipeline_instances', 'active', 'pipeline_in_running_state', 'foo', '/jobs/zzzzz-8i9sb-pshmckwoma9plh7#Log'],
240     ['pipeline_instances', nil, 'pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', 'foo', 'Log unavailable'],
241   ].each do |type, token, fixture, child, log_link|
242     test "link_to_log for #{fixture} for #{token}" do
243       obj = api_fixture(type)[fixture]
244       if token
245         visit page_with_token token, "/#{type}/#{obj['uuid']}"
246       else
247         Rails.configuration.anonymous_user_token =
248           api_fixture("api_client_authorizations", "anonymous", "api_token")
249         visit "/#{type}/#{obj['uuid']}"
250       end
251
252       click_link(child)
253
254       if token
255         assert_selector "a[href=\"#{log_link}\"]"
256       else
257         assert_text log_link
258       end
259     end
260   end
261
262   test 'Run from workflows index page' do
263     visit page_with_token('active', '/workflows')
264
265     wf_count = page.all('a[data-original-title="show workflow"]').count
266     assert_equal true, wf_count>0
267
268     # Run one of the workflows
269     wf_name = 'Workflow with input specifications'
270     within('tr', text: wf_name) do
271       find('a,button', text: 'Run').click
272     end
273
274     # Choose project for the container_request being created
275     within('.modal-dialog') do
276       find('.selectable', text: 'A Project').click
277       find('button', text: 'Choose').click
278     end
279
280     # In newly created container_request page now
281     assert_text 'A Project' # CR created in "A Project"
282     assert_text "This container request was created from the workflow #{wf_name}"
283     assert_match /Provide a value for .* then click the \"Run\" button to start the workflow/, page.text
284   end
285
286   test 'Run workflow from show page' do
287     visit page_with_token('active', '/workflows/zzzzz-7fd4e-validwithinputs')
288
289     find('a,button', text: 'Run this workflow').click
290
291     # Choose project for the container_request being created
292     within('.modal-dialog') do
293       find('.selectable', text: 'A Project').click
294       find('button', text: 'Choose').click
295     end
296
297     # In newly created container_request page now
298     assert_text 'A Project' # CR created in "A Project"
299     assert_text "This container request was created from the workflow"
300     assert_match /Provide a value for .* then click the \"Run\" button to start the workflow/, page.text
301   end
302 end