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