foreman_remote_execution 16.6.4 → 16.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9b021c23fae39af103f3ac3bdb0fd3e208b74fb8ad234f66f7770a5e4dfc78f
4
- data.tar.gz: 4fd6498051640caae5cf6e4580d01106883a0f33d486b672a0d77ed49205e52c
3
+ metadata.gz: 803af8d6f1617172c150d68158d14d6fa9df2e5cc431226c1c0522f679420c7b
4
+ data.tar.gz: 43b21d05688e374f58999c890d0ed3fedcf8e98d978be053bc52f83701e89292
5
5
  SHA512:
6
- metadata.gz: bf4e127076278dca8d3ec7d4b86a625a89bb95c4a4c5cd728e9207e8ff3151f983965061498bbfd813c6d9d08f49ec59f9ba2bc06be113f8e721e8f04dfc0285
7
- data.tar.gz: 0124e5ae5e0f484269adf05e9e9e3aa10e1262c4c6f635707dee035f217a5206565bc231fa65dee356609ce6e9db332d7c1faf96b8d1b7b3760483171997e5fd
6
+ metadata.gz: 9fed722946268e04461455cffe63c27add5a23a5a0e2bbdd698a2d7bd7c94017deb0342650ecaadf92426d64db8e40682f8871ab4a43a7e60e24c1096c230bb0
7
+ data.tar.gz: c11bfa8d9b1e72d2bb35d11829509923b6fb7babffd98a7782d5c8718adc9628def8739d388be1f5de07c239ba8b852c9de907c8e1b77383c573f460813ccff7
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '16.6.4'.freeze
2
+ VERSION = '16.6.5'.freeze
3
3
  end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../test_plugin_helper', __dir__)
4
+ require 'integration_test_helper'
5
+ require 'securerandom'
6
+
7
+ class JobWizardCategoryJsTest < IntegrationTestWithJavascript
8
+ WIZARD_INPUT_NAME_BASE = 'wizard_integration_input'
9
+ WIZARD_FEATURE_INPUT_NAME_BASE = 'wizard_integration_feature_input'
10
+
11
+ setup do
12
+ unique_suffix = SecureRandom.hex(6)
13
+ @wizard_input_name = "#{WIZARD_INPUT_NAME_BASE}_#{unique_suffix}"
14
+ @wizard_feature_input_name = "#{WIZARD_FEATURE_INPUT_NAME_BASE}_#{unique_suffix}"
15
+ default_template_name = "Integration Default REX Form Template #{unique_suffix}"
16
+ feature_template_name = "Integration Feature REX Template #{unique_suffix}"
17
+
18
+ # Prevent real Dynflow proxy calls in CI while planning jobs.
19
+ ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
20
+
21
+ @original_rex_form_template = Setting[:remote_execution_form_job_template]
22
+ as_admin do
23
+ @default_job_template = FactoryBot.create(
24
+ :job_template,
25
+ :name => default_template_name,
26
+ :job_category => 'Default Form Category'
27
+ )
28
+ @feature_job_template = FactoryBot.create(
29
+ :job_template,
30
+ :name => feature_template_name,
31
+ :job_category => 'Feature Linked Category'
32
+ )
33
+ add_plain_template_input!(@default_job_template, :name => @wizard_input_name)
34
+ add_plain_template_input!(@feature_job_template, :name => @wizard_feature_input_name)
35
+ Setting[:remote_execution_form_job_template] = @default_job_template.name
36
+ @remote_execution_feature = FactoryBot.create(
37
+ :remote_execution_feature,
38
+ :label => "integration_rex_category_feature_#{unique_suffix}",
39
+ :name => "Integration REX Category Feature #{unique_suffix}",
40
+ :job_template => @feature_job_template
41
+ )
42
+ @rex_host = FactoryBot.create(:host, :with_execution)
43
+ end
44
+ end
45
+
46
+ teardown do
47
+ Setting[:remote_execution_form_job_template] = @original_rex_form_template
48
+ end
49
+
50
+ test 'job wizard shows feature template job category when feature query param is present' do
51
+ visit new_job_invocation_path(:feature => @remote_execution_feature.label)
52
+ assert_text 'Category and template', :wait => 30
53
+ wait_for_ajax
54
+ assert_selector(:ouia_component_id, 'job_category', :text => 'Feature Linked Category', :wait => 30)
55
+ end
56
+
57
+ test 'job wizard shows default form category when no feature query param' do
58
+ visit new_job_invocation_path
59
+ assert_text 'Category and template', :wait => 30
60
+ wait_for_ajax
61
+ assert_selector(:ouia_component_id, 'job_category', :text => 'Default Form Category', :wait => 30)
62
+ end
63
+
64
+ test 'job wizard prefills template input from inputs query param' do
65
+ visit new_job_invocation_path(
66
+ :inputs => { @wizard_input_name => 'from_query_string' },
67
+ :search => ''
68
+ )
69
+ go_to_target_hosts_and_inputs_step!
70
+ assert_selector "textarea##{@wizard_input_name}", :wait => 30
71
+ assert_equal 'from_query_string', find("textarea##{@wizard_input_name}").value
72
+ end
73
+
74
+ test 'job wizard does not prefill template input when inputs query param is omitted' do
75
+ visit new_job_invocation_path(:search => '')
76
+ go_to_target_hosts_and_inputs_step!
77
+ assert_selector "textarea##{@wizard_input_name}", :wait => 30
78
+ assert_equal '', find("textarea##{@wizard_input_name}").value
79
+ end
80
+
81
+ test 'job wizard prefills template input when feature and inputs query params are present' do
82
+ visit new_job_invocation_path(
83
+ :feature => @remote_execution_feature.label,
84
+ :inputs => { @wizard_feature_input_name => 'feature_and_inputs' },
85
+ :search => ''
86
+ )
87
+ go_to_target_hosts_and_inputs_step!
88
+ assert_selector "textarea##{@wizard_feature_input_name}", :wait => 30
89
+ assert_equal 'feature_and_inputs', find("textarea##{@wizard_feature_input_name}").value
90
+ end
91
+
92
+ test 'job wizard run on selected hosts is enabled on review and posts to job invocations API' do
93
+ posted_to_job_invocations_create = false
94
+ subscriber = ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |_n, _s, _f, _i, payload|
95
+ next unless payload[:controller] == 'Api::V2::JobInvocationsController' && payload[:action].to_s == 'create'
96
+ root = payload[:params]
97
+ root = root.to_unsafe_h if root.respond_to?(:to_unsafe_h)
98
+ job_inv = (root[:job_invocation] || root['job_invocation'])
99
+ next unless job_inv
100
+ job_inv = job_inv.to_unsafe_h if job_inv.respond_to?(:to_unsafe_h)
101
+ next unless job_inv['job_template_id'].to_i == @default_job_template.id
102
+ posted_to_job_invocations_create = true
103
+ end
104
+
105
+ begin
106
+ visit new_job_invocation_path(:search => "id = #{@rex_host.id}")
107
+ navigate_job_wizard_to_review_step!
108
+
109
+ assert_selector(:ouia_component_id, 'run-on-selected-hosts-footer', :wait => 30)
110
+ run_on_hosts = find(:ouia_component_id, 'run-on-selected-hosts-footer')
111
+ assert_not_equal 'true', run_on_hosts[:'aria-disabled'], 'expected run-on-selected-hosts button to be enabled'
112
+ assert_text 'Run on selected hosts'
113
+ find(:ouia_component_id, 'run-on-selected-hosts-footer').click
114
+ wait_for_ajax
115
+ assert posted_to_job_invocations_create, 'expected Api::V2::JobInvocationsController#create to run'
116
+ assert_match(%r{\A/job_invocations/\d+\z}, page.current_path)
117
+ ensure
118
+ ActiveSupport::Notifications.unsubscribe(subscriber)
119
+ end
120
+ end
121
+
122
+ test 'job wizard create API request sends job_template_id and inputs for default template' do
123
+ input_value = 'api_payload_default_template'
124
+ job_inv_params = capture_api_job_invocation_params_during(expected_template_id: @default_job_template.id) do
125
+ visit new_job_invocation_path(
126
+ :search => "id = #{@rex_host.id}",
127
+ :inputs => { @wizard_input_name => input_value }
128
+ )
129
+ navigate_job_wizard_to_review_step!
130
+ find(:ouia_component_id, 'run-on-selected-hosts-footer').click
131
+ wait_for_ajax
132
+ end
133
+
134
+ assert job_inv_params, 'expected job_invocation key in POST /api/job_invocations'
135
+ assert job_inv_params[:job_template_id].present?, 'expected job_template_id to be present'
136
+ assert_equal @default_job_template.id, job_inv_params[:job_template_id].to_i
137
+ assert job_inv_params[:feature].blank?
138
+ assert_equal input_value, hash_from_params_object(job_inv_params[:inputs])[@wizard_input_name]
139
+ job_invocation_id = page.current_path.split('/').last.to_i
140
+ assert_equal @default_job_template.job_category, JobInvocation.find(job_invocation_id).job_category, 'expected job category to be the default template category'
141
+ end
142
+
143
+ test 'job wizard create API request sends feature label and inputs without job_template_id' do
144
+ input_value = 'api_payload_feature_template'
145
+ job_inv_params = capture_api_job_invocation_params_during(expected_feature: @remote_execution_feature.label) do
146
+ visit new_job_invocation_path(
147
+ :feature => @remote_execution_feature.label,
148
+ :search => "id = #{@rex_host.id}",
149
+ :inputs => { @wizard_feature_input_name => input_value }
150
+ )
151
+ navigate_job_wizard_to_review_step!
152
+ find(:ouia_component_id, 'run-on-selected-hosts-footer').click
153
+ wait_for_ajax
154
+ end
155
+
156
+ assert job_inv_params, 'expected job_invocation key in POST /api/job_invocations'
157
+ assert_not job_inv_params[:job_template_id].present?
158
+ assert_equal @remote_execution_feature.label, job_inv_params[:feature]
159
+ assert_equal input_value, hash_from_params_object(job_inv_params[:inputs])[@wizard_feature_input_name]
160
+ job_invocation_id = page.current_path.split('/').last.to_i
161
+ assert_equal @feature_job_template.job_category, JobInvocation.find(job_invocation_id).job_category, 'expected job category to be the feature template category'
162
+ end
163
+
164
+ private
165
+
166
+ def capture_api_job_invocation_params_during(expected_template_id: nil, expected_feature: nil)
167
+ captured = nil
168
+ subscriber = ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |_n, _s, _f, _i, payload|
169
+ next unless payload[:controller] == 'Api::V2::JobInvocationsController' && payload[:action].to_s == 'create'
170
+ root = payload[:params]
171
+ root = root.to_unsafe_h if root.respond_to?(:to_unsafe_h)
172
+ job_inv_params = root[:job_invocation] || root['job_invocation']
173
+ next unless job_inv_params
174
+ job_inv_params = job_inv_params.to_unsafe_h if job_inv_params.respond_to?(:to_unsafe_h)
175
+ job_inv_params = job_inv_params.with_indifferent_access
176
+ next if expected_template_id && job_inv_params[:job_template_id].to_i != expected_template_id
177
+ next if expected_feature && job_inv_params[:feature] != expected_feature
178
+ captured = job_inv_params
179
+ end
180
+ begin
181
+ yield
182
+ ensure
183
+ ActiveSupport::Notifications.unsubscribe(subscriber)
184
+ end
185
+ captured
186
+ end
187
+
188
+ def hash_from_params_object(obj)
189
+ return {}.with_indifferent_access if obj.blank?
190
+ h = obj.respond_to?(:to_unsafe_h) ? obj.to_unsafe_h : obj.to_h
191
+ h.with_indifferent_access
192
+ end
193
+
194
+ def add_plain_template_input!(job_template, name:)
195
+ job_template.template_inputs << FactoryBot.build(
196
+ :template_input,
197
+ :name => name,
198
+ :input_type => 'user',
199
+ :value_type => 'plain',
200
+ :required => false
201
+ )
202
+ job_template.save!
203
+ end
204
+
205
+ def job_wizard_click_next!
206
+ wait_for_ajax
207
+ button = find(:ouia_component_id, 'next-footer')
208
+ page.execute_script('arguments[0].scrollIntoView({block: "center"})', button.native)
209
+ button.click
210
+ wait_for_ajax
211
+ end
212
+
213
+ def go_to_target_hosts_and_inputs_step!
214
+ assert_text 'Category and template', :wait => 30
215
+ wait_for_ajax
216
+ job_wizard_click_next!
217
+ assert_text 'Target hosts and inputs', :wait => 30
218
+ wait_for_ajax
219
+ end
220
+
221
+ def navigate_job_wizard_to_review_step!
222
+ assert_text 'Category and template', :wait => 30
223
+ wait_for_ajax
224
+ job_wizard_click_next!
225
+ assert_text 'Target hosts and inputs', :wait => 30
226
+ job_wizard_click_next!
227
+ assert_text 'Advanced fields', :wait => 30
228
+ job_wizard_click_next!
229
+ assert_text 'Immediate execution', :wait => 30
230
+ job_wizard_click_next!
231
+ assert_text 'Review details', :wait => 30
232
+ wait_for_ajax
233
+ end
234
+ end
@@ -61,6 +61,11 @@ section.job-additional-info {
61
61
  }
62
62
 
63
63
  .job-details-table-section {
64
+ &.table-section {
65
+ padding-left: 0;
66
+ padding-right: 0;
67
+ }
68
+
64
69
  section:nth-child(1) {
65
70
  padding: 0;
66
71
  }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.6.4
4
+ version: 16.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-06-02 00:00:00.000000000 Z
10
+ date: 2026-06-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: deface
@@ -354,6 +354,7 @@ files:
354
354
  - test/graphql/queries/job_invocation_query_test.rb
355
355
  - test/graphql/queries/job_invocations_query_test.rb
356
356
  - test/helpers/remote_execution_helper_test.rb
357
+ - test/integration/job_wizard_category_js_test.rb
357
358
  - test/support/remote_execution_helper.rb
358
359
  - test/test_plugin_helper.rb
359
360
  - test/unit/actions/run_host_job_test.rb