Merge branch 'master' into 3382-always-show-inputs
[arvados.git] / services / api / test / functional / arvados / v1 / job_reuse_controller_test.rb
1 require 'test_helper'
2 require 'helpers/git_test_helper'
3
4 class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
5   fixtures :repositories, :users, :jobs, :links, :collections
6
7   # See git_setup.rb for the commit log for test.git.tar
8   include GitTestHelper
9
10   setup do
11     @controller = Arvados::V1::JobsController.new
12     authorize_with :active
13   end
14
15   test "reuse job with no_reuse=false" do
16     post :create, job: {
17       no_reuse: false,
18       script: "hash",
19       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
20       repository: "foo",
21       script_parameters: {
22         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
23         an_integer: '1'
24       }
25     }
26     assert_response :success
27     assert_not_nil assigns(:object)
28     new_job = JSON.parse(@response.body)
29     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
30     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
31   end
32
33   test "reuse job with find_or_create=true" do
34     post :create, {
35       job: {
36         script: "hash",
37         script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
38         repository: "foo",
39         script_parameters: {
40           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
41           an_integer: '1'
42         }
43       },
44       find_or_create: true
45     }
46     assert_response :success
47     assert_not_nil assigns(:object)
48     new_job = JSON.parse(@response.body)
49     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
50     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
51   end
52
53   test "reuse job with symbolic script_version" do
54     post :create, {
55       job: {
56         script: "hash",
57         script_version: "tag1",
58         repository: "foo",
59         script_parameters: {
60           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
61           an_integer: '1'
62         }
63       },
64       find_or_create: true
65     }
66     assert_response :success
67     assert_not_nil assigns(:object)
68     new_job = JSON.parse(@response.body)
69     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
70     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
71   end
72
73   test "do not reuse job because no_reuse=true" do
74     post :create, {
75       job: {
76         no_reuse: true,
77         script: "hash",
78         script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
79         repository: "foo",
80         script_parameters: {
81           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
82           an_integer: '1'
83         }
84       }
85     }
86     assert_response :success
87     assert_not_nil assigns(:object)
88     new_job = JSON.parse(@response.body)
89     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
90     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
91   end
92
93   test "do not reuse job because find_or_create=false" do
94     post :create, {
95       job: {
96         script: "hash",
97         script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
98         repository: "foo",
99         script_parameters: {
100           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
101           an_integer: '1'
102         }
103       },
104       find_or_create: false
105     }
106     assert_response :success
107     assert_not_nil assigns(:object)
108     new_job = JSON.parse(@response.body)
109     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
110     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
111   end
112
113   test "do not reuse job because output is not readable by user" do
114     authorize_with :job_reader
115     post :create, {
116       job: {
117         script: "hash",
118         script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
119         repository: "foo",
120         script_parameters: {
121           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
122           an_integer: '1'
123         }
124       },
125       find_or_create: true
126     }
127     assert_response :success
128     assert_not_nil assigns(:object)
129     new_job = JSON.parse(@response.body)
130     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
131     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
132   end
133
134   test "test_cannot_reuse_job_no_output" do
135     post :create, job: {
136       no_reuse: false,
137       script: "hash",
138       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
139       repository: "foo",
140       script_parameters: {
141         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
142         an_integer: '2'
143       }
144     }
145     assert_response :success
146     assert_not_nil assigns(:object)
147     new_job = JSON.parse(@response.body)
148     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykppp', new_job['uuid']
149   end
150
151   test "test_reuse_job_range" do
152     post :create, job: {
153       no_reuse: false,
154       script: "hash",
155       minimum_script_version: "tag1",
156       script_version: "master",
157       repository: "foo",
158       script_parameters: {
159         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
160         an_integer: '1'
161       }
162     }
163     assert_response :success
164     assert_not_nil assigns(:object)
165     new_job = JSON.parse(@response.body)
166     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
167     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
168   end
169
170   test "cannot_reuse_job_no_minimum_given_so_must_use_specified_commit" do
171     post :create, job: {
172       no_reuse: false,
173       script: "hash",
174       script_version: "master",
175       repository: "foo",
176       script_parameters: {
177         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
178         an_integer: '1'
179       }
180     }
181     assert_response :success
182     assert_not_nil assigns(:object)
183     new_job = JSON.parse(@response.body)
184     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
185     assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
186   end
187
188   test "test_cannot_reuse_job_different_input" do
189     post :create, job: {
190       no_reuse: false,
191       script: "hash",
192       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
193       repository: "foo",
194       script_parameters: {
195         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
196         an_integer: '2'
197       }
198     }
199     assert_response :success
200     assert_not_nil assigns(:object)
201     new_job = JSON.parse(@response.body)
202     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
203     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
204   end
205
206   test "test_cannot_reuse_job_different_version" do
207     post :create, job: {
208       no_reuse: false,
209       script: "hash",
210       script_version: "master",
211       repository: "foo",
212       script_parameters: {
213         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
214         an_integer: '2'
215       }
216     }
217     assert_response :success
218     assert_not_nil assigns(:object)
219     new_job = JSON.parse(@response.body)
220     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
221     assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
222   end
223
224   test "test_can_reuse_job_submitted_nondeterministic" do
225     post :create, job: {
226       no_reuse: false,
227       script: "hash",
228       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
229       repository: "foo",
230       script_parameters: {
231         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
232         an_integer: '1'
233       },
234       nondeterministic: true
235     }
236     assert_response :success
237     assert_not_nil assigns(:object)
238     new_job = JSON.parse(@response.body)
239     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
240     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
241   end
242
243   test "test_cannot_reuse_job_past_nondeterministic" do
244     post :create, job: {
245       no_reuse: false,
246       script: "hash2",
247       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
248       repository: "foo",
249       script_parameters: {
250         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
251         an_integer: '1'
252       }
253     }
254     assert_response :success
255     assert_not_nil assigns(:object)
256     new_job = JSON.parse(@response.body)
257     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykyyy', new_job['uuid']
258     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
259   end
260
261   test "test_cannot_reuse_job_no_permission" do
262     authorize_with :spectator
263     post :create, job: {
264       no_reuse: false,
265       script: "hash",
266       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
267       repository: "foo",
268       script_parameters: {
269         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
270         an_integer: '1'
271       }
272     }
273     assert_response :success
274     assert_not_nil assigns(:object)
275     new_job = JSON.parse(@response.body)
276     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
277     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
278   end
279
280   test "test_cannot_reuse_job_excluded" do
281     post :create, job: {
282       no_reuse: false,
283       script: "hash",
284       minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
285       script_version: "master",
286       repository: "foo",
287       exclude_script_versions: ["tag1"],
288       script_parameters: {
289         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
290         an_integer: '1'
291       }
292     }
293     assert_response :success
294     assert_not_nil assigns(:object)
295     new_job = JSON.parse(@response.body)
296     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
297     assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
298                      new_job['script_version'])
299   end
300
301   test "cannot reuse job with find_or_create but excluded version" do
302     post :create, {
303       job: {
304         script: "hash",
305         script_version: "master",
306         repository: "foo",
307         script_parameters: {
308           input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
309           an_integer: '1'
310         }
311       },
312       find_or_create: true,
313       minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
314       exclude_script_versions: ["tag1"],
315     }
316     assert_response :success
317     assert_not_nil assigns(:object)
318     new_job = JSON.parse(@response.body)
319     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
320     assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
321                      new_job['script_version'])
322   end
323
324   BASE_FILTERS = {
325     'repository' => ['=', 'foo'],
326     'script' => ['=', 'hash'],
327     'script_version' => ['in git', 'master'],
328     'docker_image_locator' => ['=', nil],
329     'arvados_sdk_version' => ['=', nil],
330   }
331
332   def filters_from_hash(hash)
333     hash.each_pair.map { |name, filter| [name] + filter }
334   end
335
336   test "can reuse a Job based on filters" do
337     filters_hash = BASE_FILTERS.
338       merge('script_version' => ['in git', 'tag1'])
339     post(:create, {
340            job: {
341              script: "hash",
342              script_version: "master",
343              repository: "foo",
344              script_parameters: {
345                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
346                an_integer: '1'
347              }
348            },
349            filters: filters_from_hash(filters_hash),
350            find_or_create: true,
351          })
352     assert_response :success
353     assert_not_nil assigns(:object)
354     new_job = JSON.parse(@response.body)
355     assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
356     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
357   end
358
359   test "can not reuse a Job based on filters" do
360     filters = filters_from_hash(BASE_FILTERS
361                                   .reject { |k| k == 'script_version' })
362     filters += [["script_version", "in git",
363                  "31ce37fe365b3dc204300a3e4c396ad333ed0556"],
364                 ["script_version", "not in git", ["tag1"]]]
365     post(:create, {
366            job: {
367              script: "hash",
368              script_version: "master",
369              repository: "foo",
370              script_parameters: {
371                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
372                an_integer: '1'
373              }
374            },
375            filters: filters,
376            find_or_create: true,
377          })
378     assert_response :success
379     assert_not_nil assigns(:object)
380     new_job = JSON.parse(@response.body)
381     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
382     assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
383   end
384
385   test "can not reuse a Job based on arbitrary filters" do
386     filters_hash = BASE_FILTERS.
387       merge("created_at" => ["<", "2010-01-01T00:00:00Z"])
388     post(:create, {
389            job: {
390              script: "hash",
391              script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
392              repository: "foo",
393              script_parameters: {
394                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
395                an_integer: '1'
396              }
397            },
398            filters: filters_from_hash(filters_hash),
399            find_or_create: true,
400          })
401     assert_response :success
402     assert_not_nil assigns(:object)
403     new_job = JSON.parse(@response.body)
404     assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
405     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
406   end
407
408   test "can reuse a Job with a Docker image" do
409     post(:create, {
410            job: {
411              script: "hash",
412              script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
413              repository: "foo",
414              script_parameters: {
415                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
416                an_integer: '1'
417              },
418              runtime_constraints: {
419                docker_image: 'arvados/apitestfixture',
420              }
421            },
422            find_or_create: true,
423          })
424     assert_response :success
425     new_job = assigns(:object)
426     assert_not_nil new_job
427     target_job = jobs(:previous_docker_job_run)
428     [:uuid, :script_version, :docker_image_locator].each do |attr|
429       assert_equal(target_job.send(attr), new_job.send(attr))
430     end
431   end
432
433   test "can reuse a Job with a Docker image hash filter" do
434     filters_hash = BASE_FILTERS.
435       merge("script_version" =>
436               ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
437             "docker_image_locator" =>
438               ["in docker", links(:docker_image_collection_hash).name])
439     post(:create, {
440            job: {
441              script: "hash",
442              script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
443              repository: "foo",
444              script_parameters: {
445                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
446                an_integer: '1'
447              },
448            },
449            filters: filters_from_hash(filters_hash),
450            find_or_create: true,
451          })
452     assert_response :success
453     new_job = assigns(:object)
454     assert_not_nil new_job
455     target_job = jobs(:previous_docker_job_run)
456     [:uuid, :script_version, :docker_image_locator].each do |attr|
457       assert_equal(target_job.send(attr), new_job.send(attr))
458     end
459   end
460
461   test "reuse Job with Docker image repo+tag" do
462     filters_hash = BASE_FILTERS.
463       merge("script_version" =>
464               ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
465             "docker_image_locator" =>
466               ["in docker", links(:docker_image_collection_tag2).name])
467     post(:create, {
468            job: {
469              script: "hash",
470              script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
471              repository: "foo",
472              script_parameters: {
473                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
474                an_integer: '1'
475              },
476            },
477            filters: filters_from_hash(filters_hash),
478            find_or_create: true,
479          })
480     assert_response :success
481     new_job = assigns(:object)
482     assert_not_nil new_job
483     target_job = jobs(:previous_docker_job_run)
484     [:uuid, :script_version, :docker_image_locator].each do |attr|
485       assert_equal(target_job.send(attr), new_job.send(attr))
486     end
487   end
488
489   test "new job with unknown Docker image filter" do
490     filters_hash = BASE_FILTERS.
491       merge("docker_image_locator" => ["in docker", "_nonesuchname_"])
492     post(:create, {
493            job: {
494              script: "hash",
495              script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
496              repository: "foo",
497              script_parameters: {
498                input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
499                an_integer: '1'
500              },
501            },
502            filters: filters_from_hash(filters_hash),
503            find_or_create: true,
504          })
505     assert_response :success
506     new_job = assigns(:object)
507     assert_not_nil new_job
508     assert_not_equal(jobs(:previous_docker_job_run).uuid, new_job.uuid)
509   end
510
511   ["repository", "script"].each do |skip_key|
512     test "missing #{skip_key} filter raises an error" do
513       filters = filters_from_hash(BASE_FILTERS.reject { |k| k == skip_key })
514       post(:create, {
515              job: {
516                script: "hash",
517                script_version: "master",
518                repository: "foo",
519                script_parameters: {
520                  input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
521                  an_integer: '1'
522                }
523              },
524              filters: filters,
525              find_or_create: true,
526            })
527       assert_includes(405..599, @response.code.to_i,
528                       "bad status code with missing #{skip_key} filter")
529     end
530   end
531
532   test "find Job with script version range" do
533     get :index, filters: [["repository", "=", "foo"],
534                           ["script", "=", "hash"],
535                           ["script_version", "in git", "tag1"]]
536     assert_response :success
537     assert_not_nil assigns(:objects)
538     assert_includes(assigns(:objects).map { |job| job.uuid },
539                     jobs(:previous_job_run).uuid)
540   end
541
542   test "find Job with script version range exclusions" do
543     get :index, filters: [["repository", "=", "foo"],
544                           ["script", "=", "hash"],
545                           ["script_version", "not in git", "tag1"]]
546     assert_response :success
547     assert_not_nil assigns(:objects)
548     refute_includes(assigns(:objects).map { |job| job.uuid },
549                     jobs(:previous_job_run).uuid)
550   end
551
552   test "find Job with Docker image range" do
553     get :index, filters: [["docker_image_locator", "in docker",
554                            "arvados/apitestfixture"]]
555     assert_response :success
556     assert_not_nil assigns(:objects)
557     assert_includes(assigns(:objects).map { |job| job.uuid },
558                     jobs(:previous_docker_job_run).uuid)
559     refute_includes(assigns(:objects).map { |job| job.uuid },
560                     jobs(:previous_job_run).uuid)
561   end
562
563   test "find Job with Docker image using reader tokens" do
564     authorize_with :inactive
565     get(:index, {
566           filters: [["docker_image_locator", "in docker",
567                      "arvados/apitestfixture"]],
568           reader_tokens: [api_token(:active)],
569         })
570     assert_response :success
571     assert_not_nil assigns(:objects)
572     assert_includes(assigns(:objects).map { |job| job.uuid },
573                     jobs(:previous_docker_job_run).uuid)
574     refute_includes(assigns(:objects).map { |job| job.uuid },
575                     jobs(:previous_job_run).uuid)
576   end
577
578   test "'in docker' filter accepts arrays" do
579     get :index, filters: [["docker_image_locator", "in docker",
580                            ["_nonesuchname_", "arvados/apitestfixture"]]]
581     assert_response :success
582     assert_not_nil assigns(:objects)
583     assert_includes(assigns(:objects).map { |job| job.uuid },
584                     jobs(:previous_docker_job_run).uuid)
585     refute_includes(assigns(:objects).map { |job| job.uuid },
586                     jobs(:previous_job_run).uuid)
587   end
588
589   test "'not in docker' filter accepts arrays" do
590     get :index, filters: [["docker_image_locator", "not in docker",
591                            ["_nonesuchname_", "arvados/apitestfixture"]]]
592     assert_response :success
593     assert_not_nil assigns(:objects)
594     assert_includes(assigns(:objects).map { |job| job.uuid },
595                     jobs(:previous_job_run).uuid)
596     refute_includes(assigns(:objects).map { |job| job.uuid },
597                     jobs(:previous_docker_job_run).uuid)
598   end
599
600   def create_foo_hash_job_params(params)
601     if not params.has_key?(:find_or_create)
602       params[:find_or_create] = true
603     end
604     job_attrs = params.delete(:job) || {}
605     params[:job] = {
606       script: "hash",
607       script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
608       repository: "foo",
609       script_parameters: {
610         input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
611         an_integer: '1',
612       },
613     }.merge(job_attrs)
614     params
615   end
616
617   def check_new_job_created_from(params)
618     start_time = Time.now
619     post(:create, create_foo_hash_job_params(params))
620     assert_response :success
621     new_job = assigns(:object)
622     assert_not_nil new_job
623     assert_operator(start_time, :<=, new_job.created_at)
624     new_job
625   end
626
627   def check_errors_from(params)
628     post(:create, create_foo_hash_job_params(params))
629     assert_includes(405..499, @response.code.to_i)
630     errors = json_response.fetch("errors", [])
631     assert(errors.any?, "no errors assigned from #{params}")
632     refute(errors.any? { |msg| msg =~ /^#<[A-Za-z]+: / },
633            "errors include raw exception")
634     errors
635   end
636
637   # 1de84a8 is on the b1 branch, after master's tip.
638   test "new job created from unsatisfiable minimum version filter" do
639     filters_hash = BASE_FILTERS.merge("script_version" => ["in git", "1de84a8"])
640     check_new_job_created_from(filters: filters_from_hash(filters_hash))
641   end
642
643   test "new job created from unsatisfiable minimum version parameter" do
644     check_new_job_created_from(minimum_script_version: "1de84a8")
645   end
646
647   test "new job created from unsatisfiable minimum version attribute" do
648     check_new_job_created_from(job: {minimum_script_version: "1de84a8"})
649   end
650
651   test "graceful error from nonexistent minimum version filter" do
652     filters_hash = BASE_FILTERS.merge("script_version" =>
653                                       ["in git", "__nosuchbranch__"])
654     errors = check_errors_from(filters: filters_from_hash(filters_hash))
655     assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
656            "bad refspec not mentioned in error message")
657   end
658
659   test "graceful error from nonexistent minimum version parameter" do
660     errors = check_errors_from(minimum_script_version: "__nosuchbranch__")
661     assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
662            "bad refspec not mentioned in error message")
663   end
664
665   test "graceful error from nonexistent minimum version attribute" do
666     errors = check_errors_from(job: {minimum_script_version: "__nosuchbranch__"})
667     assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
668            "bad refspec not mentioned in error message")
669   end
670
671   test "can't reuse job with older Arvados SDK version" do
672     params = {
673       script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
674       runtime_constraints: {"arvados_sdk_version" => "master"},
675     }
676     check_new_job_created_from(job: params)
677   end
678
679   test "reuse job from arvados_sdk_version git filters" do
680     filters_hash = BASE_FILTERS.
681       merge("arvados_sdk_version" => ["in git", "commit2"])
682     filters_hash.delete("script_version")
683     params = create_foo_hash_job_params(filters:
684                                         filters_from_hash(filters_hash))
685     post(:create, params)
686     assert_response :success
687     assert_equal(jobs(:previous_job_run_with_arvados_sdk_version).uuid,
688                  assigns(:object).uuid)
689   end
690
691   test "create new job because of arvados_sdk_version 'not in git' filters" do
692     filters_hash = BASE_FILTERS.reject { |k| k == "script_version" }
693     filters = filters_from_hash(filters_hash)
694     # Allow anything from the root commit, but before commit 2.
695     filters += [["arvados_sdk_version", "in git", "436637c8"],
696                 ["arvados_sdk_version", "not in git", "00634b2b"]]
697     check_new_job_created_from(filters: filters)
698   end
699 end