Merge branch '14196-api-v2-tokens' refs #14196
[arvados.git] / apps / workbench / test / controllers / pipeline_instances_controller_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class PipelineInstancesControllerTest < ActionController::TestCase
8   include PipelineInstancesHelper
9
10   def create_instance_long_enough_to(instance_attrs={})
11     # create 'two_part' pipeline with the given instance attributes
12     pt_fixture = api_fixture('pipeline_templates')['two_part']
13     post :create, {
14       pipeline_instance: instance_attrs.merge({
15         pipeline_template_uuid: pt_fixture['uuid']
16       }),
17       format: :json
18     }, session_for(:active)
19     assert_response :success
20     pi_uuid = assigns(:object).uuid
21     assert_not_nil assigns(:object)
22
23     # yield
24     yield pi_uuid, pt_fixture
25
26     # delete the pipeline instance
27     use_token :active
28     PipelineInstance.where(uuid: pi_uuid).first.destroy
29   end
30
31   test "pipeline instance components populated after create" do
32     create_instance_long_enough_to do |new_instance_uuid, template_fixture|
33       assert_equal(template_fixture['components'].to_json,
34                    assigns(:object).components.to_json)
35     end
36   end
37
38   test "can render pipeline instance with tagged collections" do
39     # Make sure to pass in a tagged collection to test that part of the rendering behavior.
40     get(:show,
41         {id: api_fixture("pipeline_instances")["pipeline_with_tagged_collection_input"]["uuid"]},
42         session_for(:active))
43     assert_response :success
44   end
45
46   test "update script_parameters one at a time using merge param" do
47       template_fixture = api_fixture('pipeline_templates')['two_part']
48       post :update, {
49         id: api_fixture("pipeline_instances")["pipeline_to_merge_params"]["uuid"],
50         pipeline_instance: {
51           components: {
52             "part-two" => {
53               script_parameters: {
54                 integer_with_value: {
55                   value: 9
56                 },
57                 plain_string: {
58                   value: 'quux'
59                 },
60               }
61             }
62           }
63         },
64         merge: true,
65         format: :json
66       }, session_for(:active)
67       assert_response :success
68       assert_not_nil assigns(:object)
69       orig_params = template_fixture['components']['part-two']['script_parameters']
70       new_params = assigns(:object).components[:'part-two'][:script_parameters]
71       orig_params.keys.each do |k|
72         unless %w(integer_with_value plain_string).index(k)
73           assert_equal orig_params[k].to_json, new_params[k.to_sym].to_json
74         end
75       end
76   end
77
78   test "component rendering copes with unexpected components format" do
79     get(:show,
80         {id: api_fixture("pipeline_instances")["components_is_jobspec"]["uuid"]},
81         session_for(:active))
82     assert_response :success
83   end
84
85   test "dates in JSON components are parsed" do
86     get(:show,
87         {id: api_fixture('pipeline_instances')['has_component_with_completed_jobs']['uuid']},
88         session_for(:active))
89     assert_response :success
90     assert_not_nil assigns(:object)
91     assert_not_nil assigns(:object).components[:foo][:job]
92     start_at = assigns(:object).components[:foo][:job][:started_at]
93     start_at = Time.parse(start_at) if (start_at.andand.class == String)
94     assert start_at.is_a? Time
95     finished_at = assigns(:object).components[:foo][:job][:started_at]
96     finished_at = Time.parse(finished_at) if (finished_at.andand.class == String)
97     assert finished_at.is_a? Time
98   end
99
100   # The next two tests ensure that a pipeline instance can be copied
101   # when the template has components that do not exist in the
102   # instance (ticket #4000).
103
104   test "copy pipeline instance with components=use_latest" do
105     post(:copy,
106          {
107            id: api_fixture('pipeline_instances')['pipeline_with_newer_template']['uuid'],
108            components: 'use_latest',
109            script: 'use_latest',
110            pipeline_instance: {
111              state: 'RunningOnServer'
112            }
113          },
114          session_for(:active))
115     assert_response 302
116     assert_not_nil assigns(:object)
117
118     # Component 'foo' has script parameters only in the pipeline instance.
119     # Component 'bar' is present only in the pipeline_template.
120     # Test that the copied pipeline instance includes parameters for
121     # component 'foo' from the source instance, and parameters for
122     # component 'bar' from the source template.
123     #
124     assert_not_nil assigns(:object).components[:foo]
125     foo = assigns(:object).components[:foo]
126     assert_not_nil foo[:script_parameters]
127     assert_not_nil foo[:script_parameters][:input]
128     assert_equal 'foo instance input', foo[:script_parameters][:input][:title]
129
130     assert_not_nil assigns(:object).components[:bar]
131     bar = assigns(:object).components[:bar]
132     assert_not_nil bar[:script_parameters]
133     assert_not_nil bar[:script_parameters][:input]
134     assert_equal 'bar template input', bar[:script_parameters][:input][:title]
135   end
136
137   test "copy pipeline instance on newer template works with script=use_same" do
138     post(:copy,
139          {
140            id: api_fixture('pipeline_instances')['pipeline_with_newer_template']['uuid'],
141            components: 'use_latest',
142            script: 'use_same',
143            pipeline_instance: {
144              state: 'RunningOnServer'
145            }
146          },
147          session_for(:active))
148     assert_response 302
149     assert_not_nil assigns(:object)
150
151     # Test that relevant component parameters were copied from both
152     # the source instance and source template, respectively (see
153     # previous test)
154     #
155     assert_not_nil assigns(:object).components[:foo]
156     foo = assigns(:object).components[:foo]
157     assert_not_nil foo[:script_parameters]
158     assert_not_nil foo[:script_parameters][:input]
159     assert_equal 'foo instance input', foo[:script_parameters][:input][:title]
160
161     assert_not_nil assigns(:object).components[:bar]
162     bar = assigns(:object).components[:bar]
163     assert_not_nil bar[:script_parameters]
164     assert_not_nil bar[:script_parameters][:input]
165     assert_equal 'bar template input', bar[:script_parameters][:input][:title]
166   end
167
168   test "generate graph" do
169
170     use_token 'admin'
171
172     pipeline_for_graph = {
173       state: 'Complete',
174       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc9',
175       components: {
176         stage1: {
177           repository: 'foo',
178           script: 'hash',
179           script_version: 'master',
180           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
181           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
182         },
183         stage2: {
184           repository: 'foo',
185           script: 'hash2',
186           script_version: 'master',
187           script_parameters: {
188             input: 'fa7aeb5140e2848d39b416daeef4ffc5+45'
189           },
190           job: {uuid: 'zzzzz-8i9sb-graphstage20000'},
191           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujx'
192         }
193       }
194     }
195
196     @controller.params['tab_pane'] = "Graph"
197     provenance, pips = @controller.graph([pipeline_for_graph])
198
199     graph_test_collection1 = find_fixture Collection, "graph_test_collection1"
200     stage1 = find_fixture Job, "graph_stage1"
201     stage2 = find_fixture Job, "graph_stage2"
202
203     ['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage1',
204      'component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage2',
205      stage1.uuid,
206      stage2.uuid,
207      stage1.output,
208      stage2.output,
209      pipeline_for_graph[:components][:stage1][:output_uuid],
210      pipeline_for_graph[:components][:stage2][:output_uuid]
211     ].each do |k|
212
213       assert_not_nil provenance[k], "Expected key #{k} in provenance set"
214       assert_equal 1, pips[k], "Expected key #{k} in pips set" if !k.start_with? "component_"
215     end
216
217     prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
218         :request => RequestDuck,
219         :all_script_parameters => true,
220         :combine_jobs => :script_and_version,
221         :pips => pips,
222         :only_components => true }
223
224     stage1_id = "#{stage1[:script]}_#{stage1[:script_version]}_#{Digest::MD5.hexdigest(stage1[:script_parameters].to_json)}"
225     stage2_id = "#{stage2[:script]}_#{stage2[:script_version]}_#{Digest::MD5.hexdigest(stage2[:script_parameters].to_json)}"
226
227     stage1_out = stage1[:output].gsub('+','\\\+')
228
229     assert_match /#{stage1_id}&#45;&gt;#{stage1_out}/, prov_svg
230
231     assert_match /#{stage1_out}&#45;&gt;#{stage2_id}/, prov_svg
232
233   end
234
235   test "generate graph compare" do
236
237     use_token 'admin'
238
239     pipeline_for_graph1 = {
240       state: 'Complete',
241       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc9',
242       components: {
243         stage1: {
244           repository: 'foo',
245           script: 'hash',
246           script_version: 'master',
247           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
248           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
249         },
250         stage2: {
251           repository: 'foo',
252           script: 'hash2',
253           script_version: 'master',
254           script_parameters: {
255             input: 'fa7aeb5140e2848d39b416daeef4ffc5+45'
256           },
257           job: {uuid: 'zzzzz-8i9sb-graphstage20000'},
258           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujx'
259         }
260       }
261     }
262
263     pipeline_for_graph2 = {
264       state: 'Complete',
265       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc0',
266       components: {
267         stage1: {
268           repository: 'foo',
269           script: 'hash',
270           script_version: 'master',
271           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
272           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
273         },
274         stage2: {
275           repository: 'foo',
276           script: 'hash2',
277           script_version: 'master',
278           script_parameters: {
279           },
280           job: {uuid: 'zzzzz-8i9sb-graphstage30000'},
281           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujj'
282         }
283       }
284     }
285
286     @controller.params['tab_pane'] = "Graph"
287     provenance, pips = @controller.graph([pipeline_for_graph1, pipeline_for_graph2])
288
289     collection1 = find_fixture Collection, "graph_test_collection1"
290
291     stage1 = find_fixture Job, "graph_stage1"
292     stage2 = find_fixture Job, "graph_stage2"
293     stage3 = find_fixture Job, "graph_stage3"
294
295     [['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage1', nil],
296      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage2', nil],
297      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc0_stage1', nil],
298      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc0_stage2', nil],
299      [stage1.uuid, 3],
300      [stage2.uuid, 1],
301      [stage3.uuid, 2],
302      [stage1.output, 3],
303      [stage2.output, 1],
304      [stage3.output, 2],
305      [pipeline_for_graph1[:components][:stage1][:output_uuid], 3],
306      [pipeline_for_graph1[:components][:stage2][:output_uuid], 1],
307      [pipeline_for_graph2[:components][:stage2][:output_uuid], 2]
308     ].each do |k|
309       assert_not_nil provenance[k[0]], "Expected key #{k[0]} in provenance set"
310       assert_equal k[1], pips[k[0]], "Expected key #{k} in pips" if !k[0].start_with? "component_"
311     end
312
313     prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
314         :request => RequestDuck,
315         :all_script_parameters => true,
316         :combine_jobs => :script_and_version,
317         :pips => pips,
318         :only_components => true }
319
320     collection1_id = collection1.portable_data_hash.gsub('+','\\\+')
321
322     stage2_id = "#{stage2[:script]}_#{stage2[:script_version]}_#{Digest::MD5.hexdigest(stage2[:script_parameters].to_json)}"
323     stage3_id = "#{stage3[:script]}_#{stage3[:script_version]}_#{Digest::MD5.hexdigest(stage3[:script_parameters].to_json)}"
324
325     stage2_out = stage2[:output].gsub('+','\\\+')
326     stage3_out = stage3[:output].gsub('+','\\\+')
327
328     assert_match /#{collection1_id}&#45;&gt;#{stage2_id}/, prov_svg
329     assert_match /#{collection1_id}&#45;&gt;#{stage3_id}/, prov_svg
330
331     assert_match /#{stage2_id}&#45;&gt;#{stage2_out}/, prov_svg
332     assert_match /#{stage3_id}&#45;&gt;#{stage3_out}/, prov_svg
333
334   end
335
336 end