3889: Clean up counter checks. For now, warn instead of failing.
[arvados.git] / services / api / test / unit / job_test.rb
1 require 'test_helper'
2 require 'helpers/git_test_helper'
3
4 class JobTest < ActiveSupport::TestCase
5   include GitTestHelper
6
7   BAD_COLLECTION = "#{'f' * 32}+0"
8
9   setup do
10     set_user_from_auth :active
11   end
12
13   def job_attrs merge_me={}
14     # Default (valid) set of attributes, with given overrides
15     {
16       script: "hash",
17       script_version: "master",
18       repository: "foo",
19     }.merge(merge_me)
20   end
21
22   test "Job without Docker image doesn't get locator" do
23     job = Job.new job_attrs
24     assert job.valid?, job.errors.full_messages.to_s
25     assert_nil job.docker_image_locator
26   end
27
28   { 'name' => [:links, :docker_image_collection_tag, :name],
29     'hash' => [:links, :docker_image_collection_hash, :name],
30     'locator' => [:collections, :docker_image, :portable_data_hash],
31   }.each_pair do |spec_type, (fixture_type, fixture_name, fixture_attr)|
32     test "Job initialized with Docker image #{spec_type} gets locator" do
33       image_spec = send(fixture_type, fixture_name).send(fixture_attr)
34       job = Job.new job_attrs(runtime_constraints:
35                               {'docker_image' => image_spec})
36       assert job.valid?, job.errors.full_messages.to_s
37       assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
38     end
39
40     test "Job modified with Docker image #{spec_type} gets locator" do
41       job = Job.new job_attrs
42       assert job.valid?, job.errors.full_messages.to_s
43       assert_nil job.docker_image_locator
44       image_spec = send(fixture_type, fixture_name).send(fixture_attr)
45       job.runtime_constraints['docker_image'] = image_spec
46       assert job.valid?, job.errors.full_messages.to_s
47       assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
48     end
49   end
50
51   test "removing a Docker runtime constraint removes the locator" do
52     image_locator = collections(:docker_image).portable_data_hash
53     job = Job.new job_attrs(runtime_constraints:
54                             {'docker_image' => image_locator})
55     assert job.valid?, job.errors.full_messages.to_s
56     assert_equal(image_locator, job.docker_image_locator)
57     job.runtime_constraints = {}
58     assert job.valid?, job.errors.full_messages.to_s + "after clearing runtime constraints"
59     assert_nil job.docker_image_locator
60   end
61
62   test "locate a Docker image with a repository + tag" do
63     image_repo, image_tag =
64       links(:docker_image_collection_tag2).name.split(':', 2)
65     job = Job.new job_attrs(runtime_constraints:
66                             {'docker_image' => image_repo,
67                               'docker_image_tag' => image_tag})
68     assert job.valid?, job.errors.full_messages.to_s
69     assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
70   end
71
72   test "can't locate a Docker image with a nonexistent tag" do
73     image_repo = links(:docker_image_collection_tag).name
74     image_tag = '__nonexistent tag__'
75     job = Job.new job_attrs(runtime_constraints:
76                             {'docker_image' => image_repo,
77                               'docker_image_tag' => image_tag})
78     assert(job.invalid?, "Job with bad Docker tag valid")
79   end
80
81   test "locate a Docker image with a partial hash" do
82     image_hash = links(:docker_image_collection_hash).name[0..24]
83     job = Job.new job_attrs(runtime_constraints:
84                             {'docker_image' => image_hash})
85     assert job.valid?, job.errors.full_messages.to_s + " with partial hash #{image_hash}"
86     assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
87   end
88
89   { 'name' => 'arvados_test_nonexistent',
90     'hash' => 'f' * 64,
91     'locator' => BAD_COLLECTION,
92   }.each_pair do |spec_type, image_spec|
93     test "Job validation fails with nonexistent Docker image #{spec_type}" do
94       job = Job.new job_attrs(runtime_constraints:
95                               {'docker_image' => image_spec})
96       assert(job.invalid?, "nonexistent Docker image #{spec_type} was valid")
97     end
98   end
99
100   test "Job validation fails with non-Docker Collection constraint" do
101     job = Job.new job_attrs(runtime_constraints:
102                             {'docker_image' => collections(:foo_file).uuid})
103     assert(job.invalid?, "non-Docker Collection constraint was valid")
104   end
105
106   test "can create Job with Docker image Collection without Docker links" do
107     image_uuid = collections(:unlinked_docker_image).portable_data_hash
108     job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
109     assert(job.valid?, "Job created with unlinked Docker image was invalid")
110     assert_equal(image_uuid, job.docker_image_locator)
111   end
112
113   test "can't create Job with Docker image locator" do
114     begin
115       job = Job.new job_attrs(docker_image_locator: BAD_COLLECTION)
116     rescue ActiveModel::MassAssignmentSecurity::Error
117       # Test passes - expected attribute protection
118     else
119       assert_nil job.docker_image_locator
120     end
121   end
122
123   test "can't assign Docker image locator to Job" do
124     job = Job.new job_attrs
125     begin
126       Job.docker_image_locator = BAD_COLLECTION
127     rescue NoMethodError
128       # Test passes - expected attribute protection
129     end
130     assert_nil job.docker_image_locator
131   end
132
133   [
134    {script_parameters: ""},
135    {script_parameters: []},
136    {script_parameters: {symbols: :are_not_allowed_here}},
137    {runtime_constraints: ""},
138    {runtime_constraints: []},
139    {tasks_summary: ""},
140    {tasks_summary: []},
141    {script_version: "no/branch/could/ever/possibly/have/this/name"},
142   ].each do |invalid_attrs|
143     test "validation failures set error messages: #{invalid_attrs.to_json}" do
144       # Ensure valid_attrs doesn't produce errors -- otherwise we will
145       # not know whether errors reported below are actually caused by
146       # invalid_attrs.
147       dummy = Job.create! job_attrs
148
149       job = Job.create job_attrs(invalid_attrs)
150       assert_raises(ActiveRecord::RecordInvalid, ArgumentError,
151                     "save! did not raise the expected exception") do
152         job.save!
153       end
154       assert_not_empty job.errors, "validation failure did not provide errors"
155     end
156   end
157
158   [
159     # Each test case is of the following format
160     # Array of parameters where each parameter is of the format:
161     #     attr name to be changed, attr value, and array of expectations (where each expectation is an array) OR the string "error"
162     [['running', false, [['state', 'Queued']]]],
163     [['state', 'Running', 'error']],  # is_locked_by_uuid is not set
164     [['is_locked_by_uuid', 'use_current_user_uuid', [['state', 'Queued']]], ['state', 'Running', [['running', true], ['started_at', 'not_nil'], ['success', 'nil']]]],
165     [['running', false, [['state', 'Queued']]], ['state', 'Complete', [['success', true]]]],
166     [['running', true, [['state', 'Running']]], ['cancelled_at', Time.now, [['state', 'Cancelled'],['running', false],['started_at', 'not_nil']]]],
167     [['running', true, [['state', 'Running']]], ['state', 'Cancelled', [['running', false],['cancelled_at', 'not_nil'],['started_at', 'not_nil']]]],
168     [['running', true, [['state', 'Running']]], ['success', true, [['state', 'Complete'],['running', false],['finished_at', 'not_nil']]]],
169     [['running', true, [['state', 'Running']]], ['success', false, [['state', 'Failed'],['running', false],['finished_at', 'not_nil']]]],
170     [['running', true, [['state', 'Running']]], ['state', 'Complete', [['success', true],['running', false],['finished_at', 'not_nil']]]],
171     [['running', true, [['state', 'Running']]], ['state', 'Failed', [['success', false],['running', false],['finished_at', 'not_nil']]]],
172     [['running', true, [['state', 'Running'], ['started_at', 'not_nil']]], ['running', false, [['state', 'Queued']]]],
173     [['cancelled_at', Time.now, [['state', 'Cancelled'],['running', false]]], ['success', false, [['state', 'Cancelled'],['running', false],['finished_at', 'nil'], ['cancelled_at', 'not_nil']]]],
174     [['cancelled_at', Time.now, [['state', 'Cancelled'],['running', false]]], ['success', true, [['state', 'Cancelled'],['running', false],['finished_at', 'nil'],['cancelled_at', 'not_nil']]]],
175     # potential migration cases
176     [['state', nil, [['state', 'Queued']]]],
177     [['state', nil, [['state', 'Queued']]], ['cancelled_at', Time.now, [['state', 'Cancelled']]]],
178     [['running', true, [['state', 'Running'], ['started_at', 'not_nil']]], ['state', nil, [['state', 'Running']]]],
179     # bogus initial status (started_at but not running), to produce error while setting state
180     [['started_at', Time.now, [['state', 'Queued']]], ['state', nil, 'error']],
181   ].each do |parameters|
182     test "verify job status #{parameters}" do
183       job = Job.create! job_attrs
184       assert job.valid?, job.errors.full_messages.to_s
185       assert_equal job.state, 'Queued'
186
187       parameters.each do |parameter|
188         expectations = parameter[2]
189         if parameter[1] == 'use_current_user_uuid'
190           parameter[1] = Thread.current[:user].uuid
191         end
192
193         if expectations.instance_of? Array
194           job[parameter[0]] = parameter[1]
195           job.save!
196           expectations.each do |expectation|
197             if expectation[1] == 'not_nil'
198               assert_not_nil job[expectation[0]]
199             elsif expectation[1] == 'nil'
200               assert_nil job[expectation[0]]
201             else
202               assert_equal expectation[1], job[expectation[0]]
203             end
204           end
205         else # String expectation, looking for error
206           if expectations == 'error'
207             rescued = false
208             begin
209               job[parameter[0]] = parameter[1]
210               job.save!
211             rescue
212               rescued = true
213             end
214             assert rescued, 'Expected error'
215           else
216             raise 'I do not know how to handle this expectation'
217           end
218         end
219       end
220     end
221   end
222
223 end