foreman_remote_execution 14.1.0 → 14.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/job_template.rb +1 -0
  3. data/app/views/template_invocations/show.js.erb +1 -1
  4. data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
  5. data/lib/foreman_remote_execution/engine.rb +1 -1
  6. data/lib/foreman_remote_execution/version.rb +1 -1
  7. data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
  8. data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
  9. data/test/benchmark/targeting_benchmark.rb +31 -0
  10. data/test/factories/foreman_remote_execution_factories.rb +147 -0
  11. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
  12. data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
  13. data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
  14. data/test/functional/api/v2/registration_controller_test.rb +73 -0
  15. data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
  16. data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
  17. data/test/functional/cockpit_controller_test.rb +16 -0
  18. data/test/functional/job_invocations_controller_test.rb +132 -0
  19. data/test/functional/job_templates_controller_test.rb +31 -0
  20. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  21. data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
  22. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  23. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  24. data/test/helpers/remote_execution_helper_test.rb +46 -0
  25. data/test/support/remote_execution_helper.rb +5 -0
  26. data/test/test_plugin_helper.rb +9 -0
  27. data/test/unit/actions/run_host_job_test.rb +115 -0
  28. data/test/unit/actions/run_hosts_job_test.rb +214 -0
  29. data/test/unit/api_params_test.rb +25 -0
  30. data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
  31. data/test/unit/concerns/host_extensions_test.rb +219 -0
  32. data/test/unit/concerns/nic_extensions_test.rb +9 -0
  33. data/test/unit/execution_task_status_mapper_test.rb +92 -0
  34. data/test/unit/input_template_renderer_test.rb +503 -0
  35. data/test/unit/job_invocation_composer_test.rb +974 -0
  36. data/test/unit/job_invocation_report_template_test.rb +60 -0
  37. data/test/unit/job_invocation_test.rb +232 -0
  38. data/test/unit/job_template_effective_user_test.rb +37 -0
  39. data/test/unit/job_template_test.rb +316 -0
  40. data/test/unit/remote_execution_feature_test.rb +86 -0
  41. data/test/unit/remote_execution_provider_test.rb +298 -0
  42. data/test/unit/renderer_scope_input_test.rb +49 -0
  43. data/test/unit/targeting_test.rb +206 -0
  44. data/test/unit/template_invocation_input_value_test.rb +38 -0
  45. metadata +39 -2
@@ -0,0 +1,214 @@
1
+ require 'test_plugin_helper'
2
+ require 'securerandom'
3
+
4
+ module ForemanRemoteExecution
5
+ class RunHostsJobTest < ActiveSupport::TestCase
6
+ include Dynflow::Testing
7
+
8
+ # Adding run_step_id wich is needed in RunHostsJob as a quick fix
9
+ # it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
10
+ # rubocop:disable Style/ClassAndModuleChildren
11
+ class Dynflow::Testing::DummyPlannedAction
12
+ def run_step_id
13
+ Dynflow::Testing.get_id
14
+ end
15
+ end
16
+ # rubocop:enable Style/ClassAndModuleChildren
17
+
18
+ let(:host) { FactoryBot.create(:host, :with_execution) }
19
+ let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
20
+ let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
21
+ let(:job_invocation) do
22
+ FactoryBot.build(:job_invocation, :with_template).tap do |invocation|
23
+ invocation.targeting = targeting
24
+ invocation.description = 'Some short description'
25
+ invocation.password = 'changeme'
26
+ invocation.key_passphrase = 'changemetoo'
27
+ invocation.effective_user_password = 'sudopassword'
28
+ invocation.save
29
+ end
30
+ end
31
+
32
+ let(:uuid) { SecureRandom.uuid }
33
+ let(:task) do
34
+ OpenStruct.new(:id => uuid).tap do |o|
35
+ o.stubs(:add_missing_task_groups)
36
+ o.stubs(:task_groups).returns([])
37
+ o.stubs(:pending?).returns(true)
38
+ end
39
+ end
40
+ let(:action) do
41
+ create_action(Actions::RemoteExecution::RunHostsJob).tap do |action|
42
+ action.expects(:action_subject).with(job_invocation, job_features: [])
43
+ ForemanTasks::Task::DynflowTask.stubs(:where).returns(mock.tap { |m| m.stubs(:first! => task) })
44
+ end
45
+ end
46
+ let(:planned) do
47
+ plan_action action, job_invocation
48
+ end
49
+
50
+ let(:delayed) do
51
+ action.delay({ :start_at => Time.now.getlocal }, job_invocation)
52
+ action
53
+ end
54
+
55
+ before do
56
+ ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
57
+ User.current = users :admin
58
+ action
59
+ end
60
+
61
+ context 'targeting resolving' do
62
+ it 'resolves dynamic targeting in plan' do
63
+ targeting.targeting_type = 'dynamic_query'
64
+ assert_not targeting.resolved?
65
+ delayed
66
+ assert_not targeting.resolved?
67
+ planned
68
+ assert_includes targeting.hosts, host
69
+ end
70
+
71
+ it 'resolves the hosts on static targeting in delay' do
72
+ assert_not targeting.resolved?
73
+ delayed
74
+ assert_includes targeting.hosts, host
75
+ # Verify Targeting#resolve_hosts! won't be hit again
76
+ targeting.expects(:resolve_hosts!).never
77
+ planned
78
+ end
79
+
80
+ it 'resolves the hosts on static targeting in plan phase if not resolved yet' do
81
+ planned
82
+ assert_includes targeting.hosts, host
83
+ end
84
+ end
85
+
86
+ it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
87
+ planned.expects(:output).at_most(5).returns(:planned_count => 0)
88
+ planned.expects(:trigger).with { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
89
+ planned.create_sub_plans
90
+ end
91
+
92
+ it 'uses the BindJobInvocation middleware' do
93
+ planned
94
+ assert_equal job_invocation.task_id, uuid
95
+ end
96
+
97
+ # In plan phase this is handled by #action_subject
98
+ # which is expected in tests
99
+ it 'sets input in delay phase when delayed' do
100
+ job_invocation_hash = delayed.input[:job_invocation]
101
+ assert_equal job_invocation_hash['id'], job_invocation.id
102
+ assert_equal job_invocation_hash['name'], job_invocation.job_category
103
+ assert_equal job_invocation_hash['description'], job_invocation.description
104
+ planned # To make the expectations happy
105
+ end
106
+
107
+ describe '#proxy_batch_size' do
108
+ it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
109
+ Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
110
+ planned
111
+ assert_equal 14, planned.proxy_batch_size
112
+ end
113
+
114
+ it 'gets the provider value' do
115
+ provider = mock('provider')
116
+ provider.expects(:proxy_batch_size).returns(15)
117
+ JobTemplate.any_instance.expects(:provider).returns(provider)
118
+
119
+ assert_equal 15, planned.proxy_batch_size
120
+ end
121
+ end
122
+
123
+ describe 'concurrency control' do
124
+ let(:level) { 5 }
125
+
126
+ it 'can be disabled' do
127
+ assert_nil planned.concurrency_limit
128
+ end
129
+
130
+ it 'can limit concurrency level' do
131
+ job_invocation.expects(:concurrency_level).twice.returns(level)
132
+ assert_equal level, planned.concurrency_limit
133
+ end
134
+ end
135
+
136
+ describe 'notifications' do
137
+ it 'creates drawer notification on succeess' do
138
+ blueprint = planned.job_invocation.build_notification
139
+ blueprint.expects(:deliver!)
140
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
141
+ planned.notify_on_success(nil)
142
+ end
143
+
144
+ it 'creates drawer notification on failure' do
145
+ blueprint = planned.job_invocation.build_notification
146
+ blueprint.expects(:deliver!)
147
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
148
+ planned.notify_on_failure(nil)
149
+ end
150
+
151
+ describe 'ignoring drawer notification' do
152
+ before do
153
+ blueprint = planned.job_invocation.build_notification
154
+ blueprint.expects(:deliver!)
155
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
156
+ end
157
+
158
+ let(:mail) do
159
+ object = mock
160
+ object.stubs(:deliver_now)
161
+ object
162
+ end
163
+
164
+ describe 'for user subscribed to all' do
165
+ before do
166
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
167
+ end
168
+
169
+ it 'sends the mail notification on success' do
170
+ RexJobMailer.expects(:job_finished).returns(mail)
171
+ planned.notify_on_success(nil)
172
+ end
173
+
174
+ it 'sends the mail notification on failure' do
175
+ RexJobMailer.expects(:job_finished).returns(mail)
176
+ planned.notify_on_failure(nil)
177
+ end
178
+ end
179
+
180
+ describe 'for user subscribed to failures' do
181
+ before do
182
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
183
+ end
184
+
185
+ it 'it does not send the mail notification on success' do
186
+ RexJobMailer.expects(:job_finished).never
187
+ planned.notify_on_success(nil)
188
+ end
189
+
190
+ it 'sends the mail notification on failure' do
191
+ RexJobMailer.expects(:job_finished).returns(mail)
192
+ planned.notify_on_failure(nil)
193
+ end
194
+ end
195
+
196
+ describe 'for user subscribed to successful jobs' do
197
+ before do
198
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
199
+ end
200
+
201
+ it 'sends the mail notification on success' do
202
+ RexJobMailer.expects(:job_finished).returns(mail)
203
+ planned.notify_on_success(nil)
204
+ end
205
+
206
+ it 'does not send the mail notification on failure' do
207
+ RexJobMailer.expects(:job_finished).never
208
+ planned.notify_on_failure(nil)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class ApiParamsTest < ActiveSupport::TestCase
6
+ describe '#format_datetime' do
7
+ let(:params) { JobInvocationComposer::ApiParams.allocate }
8
+
9
+ it 'leaves empty string as is' do
10
+ assert_equal params.send(:format_datetime, ''), ''
11
+ end
12
+
13
+ it 'honors explicitly supplied time zone' do
14
+ Time.use_zone(ActiveSupport::TimeZone['America/New_York']) do
15
+ assert_equal '2022-07-08 08:53', params.send(:format_datetime, '2022-07-08 12:53:20 UTC')
16
+ end
17
+ end
18
+
19
+ it 'implicitly honors current user\'s time zone' do
20
+ Time.use_zone(ActiveSupport::TimeZone['America/New_York']) do
21
+ assert_equal '2022-07-08 12:53', params.send(:format_datetime, '2022-07-08 12:53:20')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ForemanRemoteExecutionForemanTasksCleanerExtensionsTest < ActiveSupport::TestCase
4
+ # Apply the same stubbing as in foreman-tasks
5
+ before do
6
+ # To stop dynflow from backing up actions, execution_plans and steps
7
+ ForemanTasks.dynflow.world.persistence.adapter.stubs(:backup_to_csv)
8
+ ForemanTasks::Cleaner.any_instance.stubs(:say) # Make the tests silent
9
+ # Hack to make the tests pass due to ActiveRecord shenanigans
10
+ ForemanTasks::Cleaner.any_instance.stubs(:delete_orphaned_dynflow_tasks)
11
+ end
12
+
13
+ it 'tries to delete associated job invocations' do
14
+ job = FactoryBot.create(:job_invocation, :with_task)
15
+ ForemanTasks::Cleaner.new(:filter => "id = #{job.task.id}").delete
16
+ assert_empty JobInvocation.where(:id => job.id)
17
+ end
18
+
19
+ it 'removes orphaned job invocations' do
20
+ job = FactoryBot.create(:job_invocation, :with_task)
21
+ assert_equal 1, JobInvocation.where(:id => job.id).count
22
+ job.task.delete
23
+ job.reload
24
+ assert_nil job.task
25
+ refute_nil job.task_id
26
+ ForemanTasks::Cleaner.new(:filter => '').delete
27
+ assert_empty JobInvocation.where(:id => job.id)
28
+ end
29
+ end
@@ -0,0 +1,219 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
4
+ let(:provider) { 'SSH' }
5
+
6
+ before { User.current = FactoryBot.build(:user, :admin) }
7
+ after { User.current = nil }
8
+
9
+ describe 'ssh specific params' do
10
+ let(:host) { FactoryBot.create(:host, :with_execution) }
11
+ let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
12
+
13
+ before do
14
+ SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
15
+ Setting[:remote_execution_ssh_user] = 'root'
16
+ Setting[:remote_execution_effective_user_method] = 'sudo'
17
+ end
18
+
19
+ it 'has ssh user in the parameters' do
20
+ assert_equal Setting[:remote_execution_ssh_user], host.host_param('remote_execution_ssh_user')
21
+ end
22
+
23
+ it 'can override ssh user' do
24
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'amy')
25
+ assert_equal 'amy', host.host_param('remote_execution_ssh_user')
26
+ end
27
+
28
+ it 'has effective user method in the parameters' do
29
+ assert_equal Setting[:remote_execution_effective_user_method], host.host_param('remote_execution_effective_user_method')
30
+ end
31
+
32
+ it 'can override effective user method' do
33
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'su')
34
+ assert_equal 'su', host.host_param('remote_execution_effective_user_method')
35
+ end
36
+
37
+ it 'has ssh keys in the parameters' do
38
+ assert_includes host.remote_execution_ssh_keys, sshkey
39
+ end
40
+
41
+ it 'merges ssh keys from host parameters and proxies' do
42
+ key = 'ssh-rsa not-even-a-key something@somewhere.com'
43
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => [key])
44
+ assert_includes host.host_param('remote_execution_ssh_keys'), key
45
+ assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
46
+ end
47
+
48
+ it 'merges ssh key as a string from host parameters and proxies' do
49
+ key = 'ssh-rsa not-even-a-key something@somewhere.com'
50
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => key)
51
+ assert_includes host.host_param('remote_execution_ssh_keys'), key
52
+ assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
53
+ end
54
+
55
+ it 'has ssh keys in the parameters even when no user specified' do
56
+ # this is a case, when using the helper in provisioning templates
57
+ FactoryBot.create(:smart_proxy, :ssh)
58
+ host.interfaces.first.subnet.remote_execution_proxies.clear
59
+ User.current = nil
60
+ assert_includes host.remote_execution_ssh_keys, sshkey
61
+ end
62
+ end
63
+
64
+ context 'host has multiple nics' do
65
+ let(:host) { FactoryBot.build(:host, :with_execution) }
66
+
67
+ it 'should only have one execution interface' do
68
+ host.interfaces << FactoryBot.build(:nic_managed)
69
+ host.interfaces.each { |interface| interface.execution = true }
70
+ assert_predicate host, :valid?
71
+ assert_equal 1, host.interfaces.count(&:execution?)
72
+ end
73
+
74
+ it 'returns the execution interface' do
75
+ assert_kind_of Nic::Managed, host.execution_interface
76
+ end
77
+ end
78
+
79
+ context 'scoped search' do
80
+ let(:job) do
81
+ job = FactoryBot.create(:job_invocation, :with_task)
82
+ job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
83
+ job
84
+ end
85
+
86
+ let(:job2) do
87
+ job = FactoryBot.create(:job_invocation, :with_task)
88
+ job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
89
+ job
90
+ end
91
+
92
+ it 'finds hosts for job_invocation.id' do
93
+ found_ids = Host.search_for("job_invocation.id = #{job.id}").map(&:id).sort
94
+ assert_equal job.template_invocations_host_ids.sort, found_ids
95
+ end
96
+
97
+ it 'finds hosts by job_invocation.result' do
98
+ success, failed = job.template_invocations
99
+ .partition { |template| template.run_host_job_task.result == 'success' }
100
+ found_ids = Host.search_for('job_invocation.result = success').map(&:id)
101
+ assert_equal success.map(&:host_id), found_ids
102
+ found_ids = Host.search_for('job_invocation.result = failed').map(&:id)
103
+ assert_equal failed.map(&:host_id), found_ids
104
+ end
105
+
106
+ it 'finds hosts by job_invocation.id and job_invocation.result' do
107
+ # Force evaluation of the jobs
108
+ job
109
+ job2
110
+
111
+ assert_equal 2, Host.search_for("job_invocation.id = #{job.id}").count
112
+ assert_equal 2, Host.search_for("job_invocation.id = #{job2.id}").count
113
+ assert_equal 2, Host.search_for('job_invocation.result = success').count
114
+ assert_equal 2, Host.search_for('job_invocation.result = failed').count
115
+
116
+ success, failed = job.template_invocations
117
+ .partition { |template| template.run_host_job_task.result == 'success' }
118
+ found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = success").map(&:id)
119
+ assert_equal success.map(&:host_id), found_ids
120
+ found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = failed").map(&:id)
121
+ assert_equal failed.map(&:host_id), found_ids
122
+ end
123
+ end
124
+
125
+ describe 'proxy determination strategies' do
126
+ context 'subnet strategy' do
127
+ let(:host) { FactoryBot.build(:host, :with_execution) }
128
+ it { assert_includes host.remote_execution_proxies(provider)[:subnet], host.subnet.remote_execution_proxies.first }
129
+ end
130
+
131
+ context 'fallback strategy' do
132
+ let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
133
+
134
+ context 'enabled' do
135
+ before do
136
+ Setting[:remote_execution_fallback_proxy] = true
137
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
138
+ end
139
+
140
+ it 'returns a fallback proxy' do
141
+ assert_includes host.remote_execution_proxies(provider)[:fallback], host.subnet.tftp
142
+ end
143
+ end
144
+
145
+ context 'disabled' do
146
+ before do
147
+ Setting[:remote_execution_fallback_proxy] = false
148
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
149
+ end
150
+
151
+ it 'returns no proxy' do
152
+ assert_empty host.remote_execution_proxies(provider)[:fallback]
153
+ end
154
+ end
155
+ end
156
+
157
+ context 'global strategy' do
158
+ let(:tax_organization) { FactoryBot.build(:organization) }
159
+ let(:tax_location) { FactoryBot.build(:location) }
160
+ let(:host) { FactoryBot.build(:host, :organization => tax_organization, :location => tax_location) }
161
+ let(:proxy_in_taxonomies) { FactoryBot.create(:smart_proxy, :ssh, :organizations => [tax_organization], :locations => [tax_location]) }
162
+ let(:proxy_no_taxonomies) { FactoryBot.create(:smart_proxy, :ssh) }
163
+
164
+ context 'enabled' do
165
+ before { Setting[:remote_execution_global_proxy] = true }
166
+
167
+ it 'returns correct proxies confined by taxonomies' do
168
+ proxy_in_taxonomies
169
+ proxy_no_taxonomies
170
+ assert_includes host.remote_execution_proxies(provider)[:global], proxy_in_taxonomies
171
+ refute_includes host.remote_execution_proxies(provider)[:global], proxy_no_taxonomies
172
+ end
173
+ end
174
+
175
+ context 'disabled' do
176
+ before { Setting[:remote_execution_global_proxy] = false }
177
+ it 'returns no proxy' do
178
+ assert_empty host.remote_execution_proxies(provider)[:global]
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#execution_scope' do
185
+ let(:host) { FactoryBot.create(:host) }
186
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
187
+
188
+ before do
189
+ host
190
+ infra_host
191
+ end
192
+
193
+ context 'without infrastructure host permission' do
194
+ it 'omits the infrastructure host' do
195
+ setup_user('view', 'hosts')
196
+
197
+ hosts = ::Host::Managed.execution_scope
198
+ assert_includes hosts, host
199
+ refute_includes hosts, infra_host
200
+ end
201
+ end
202
+
203
+ context 'with infrastructure host permission' do
204
+ it 'finds the host as admin' do
205
+ assert User.current.admin?
206
+ hosts = ::Host::Managed.execution_scope
207
+ assert_includes hosts, host
208
+ assert_includes hosts, infra_host
209
+ end
210
+
211
+ it 'finds the host as user with needed permissions' do
212
+ setup_user('execute_jobs_on', 'infrastructure_hosts')
213
+ hosts = ::Host::Managed.execution_scope
214
+ assert_includes hosts, host
215
+ assert_includes hosts, infra_host
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ForemanRemoteExecutionNicExtensionsTest < ActiveSupport::TestCase
4
+ let(:host) { FactoryBot.create(:host) }
5
+
6
+ it 'sets the first primary interface as the execution interface' do
7
+ assert_equal host.interfaces.first, host.execution_interface
8
+ end
9
+ end
@@ -0,0 +1,92 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ExecutionTaskStatusMapperTest < ActiveSupport::TestCase
4
+ describe '.sql_conditions_for(status)' do
5
+ let(:subject) { HostStatus::ExecutionStatus::ExecutionTaskStatusMapper }
6
+
7
+ it 'accepts status number as well as string representation' do
8
+ assert_equal subject.sql_conditions_for('failed'), subject.sql_conditions_for(HostStatus::ExecutionStatus::ERROR)
9
+ end
10
+
11
+ it 'does not find any task for unknown state' do
12
+ assert_equal [ '1 = 0' ], subject.sql_conditions_for(-1)
13
+ end
14
+ end
15
+
16
+ let(:task) { ForemanTasks::Task.new }
17
+ let(:mapper) { HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.new(task) }
18
+
19
+ describe '#status' do
20
+ let(:subject) { mapper }
21
+
22
+ describe 'is queued' do
23
+ context 'when there is no task' do
24
+ before { subject.task = nil }
25
+ specify { assert_equal HostStatus::ExecutionStatus::QUEUED, subject.status }
26
+ end
27
+
28
+ context 'when the task is scheduled in future' do
29
+ before { subject.task.state = 'scheduled' }
30
+ specify { assert_equal HostStatus::ExecutionStatus::QUEUED, subject.status }
31
+ end
32
+ end
33
+
34
+ context 'when task is stopped' do
35
+ before { subject.task.state = 'stopped' }
36
+
37
+ describe 'is succeeded' do
38
+ context 'without error' do
39
+ before { subject.task.result = 'success' }
40
+ specify { assert_equal HostStatus::ExecutionStatus::OK, subject.status }
41
+ end
42
+ end
43
+
44
+ describe 'is failed' do
45
+ context 'with error' do
46
+ before { subject.task.result = 'error' }
47
+ specify { assert_equal HostStatus::ExecutionStatus::ERROR, subject.status }
48
+ end
49
+
50
+ context 'without error but just with warning (sub task failed)' do
51
+ before { subject.task.result = 'warning' }
52
+ specify { assert_equal HostStatus::ExecutionStatus::ERROR, subject.status }
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'when task is pending' do
58
+ before { subject.task.state = 'running' }
59
+
60
+ describe 'is pending' do
61
+ specify { assert_equal HostStatus::ExecutionStatus::RUNNING, subject.status }
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#status_label' do
67
+ let(:subject) { mapper.status_label }
68
+
69
+ context 'status is OK' do
70
+ before do
71
+ mapper.task.state = 'stopped'
72
+ mapper.task.result = 'success'
73
+ end
74
+
75
+ it 'returns ok label' do
76
+ assert_equal HostStatus::ExecutionStatus::STATUS_NAMES[HostStatus::ExecutionStatus::OK], subject
77
+ end
78
+ end
79
+
80
+ context 'status is ERROR' do
81
+ before do
82
+ mapper.task.state = 'stopped'
83
+ mapper.task.result = 'error'
84
+ end
85
+
86
+ it 'returns failed label' do
87
+ assert_equal HostStatus::ExecutionStatus::STATUS_NAMES[HostStatus::ExecutionStatus::ERROR], subject
88
+ end
89
+ end
90
+ end
91
+
92
+ end