foreman_rh_cloud 13.0.7 → 13.0.9
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 +4 -4
- data/app/controllers/concerns/foreman_rh_cloud/registration_manager_extensions.rb +39 -0
- data/app/controllers/foreman_inventory_upload/accounts_controller.rb +1 -1
- data/app/controllers/foreman_inventory_upload/uploads_controller.rb +1 -1
- data/lib/foreman_inventory_upload/async/create_missing_insights_facets.rb +3 -2
- data/lib/foreman_inventory_upload/async/queue_for_upload_job.rb +1 -23
- data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +200 -0
- data/lib/foreman_inventory_upload.rb +6 -6
- data/lib/foreman_rh_cloud/engine.rb +1 -0
- data/lib/foreman_rh_cloud/plugin.rb +4 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/inventory_sync/async/inventory_hosts_sync.rb +0 -2
- data/lib/tasks/rh_cloud_inventory.rake +11 -1
- data/package.json +1 -1
- data/test/controllers/accounts_controller_test.rb +1 -1
- data/test/controllers/uploads_controller_test.rb +1 -1
- data/test/jobs/cloud_connector_announce_task_test.rb +3 -2
- data/test/jobs/connector_playbook_execution_reporter_task_test.rb +32 -20
- data/test/jobs/create_missing_insights_facets_test.rb +151 -0
- data/test/jobs/exponential_backoff_test.rb +9 -8
- data/test/jobs/generate_host_report_test.rb +100 -0
- data/test/jobs/generate_report_job_test.rb +146 -0
- data/test/jobs/host_inventory_report_job_test.rb +244 -0
- data/test/jobs/insights_client_status_aging_test.rb +3 -2
- data/test/jobs/insights_full_sync_test.rb +13 -7
- data/test/jobs/insights_resolutions_sync_test.rb +9 -5
- data/test/jobs/insights_rules_sync_test.rb +5 -3
- data/test/jobs/inventory_full_sync_test.rb +9 -5
- data/test/jobs/inventory_hosts_sync_test.rb +11 -6
- data/test/jobs/inventory_scheduled_sync_test.rb +10 -6
- data/test/jobs/inventory_self_host_sync_test.rb +1 -1
- data/test/jobs/queue_for_upload_job_test.rb +10 -19
- data/test/jobs/remove_insights_hosts_job_test.rb +14 -15
- data/test/jobs/single_host_report_job_test.rb +155 -0
- data/test/jobs/upload_report_direct_job_test.rb +399 -0
- data/test/unit/lib/foreman_rh_cloud/registration_manager_extensions_test.rb +192 -0
- data/webpack/ForemanColumnExtensions/index.js +2 -0
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +1 -1
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +4 -5
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +4 -2
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +9 -10
- data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +4 -1
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +24 -17
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +178 -8
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +3 -1
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +69 -51
- data/webpack/InsightsCloudSync/Components/InsightsSettings/InsightsSettings.js +3 -3
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +3 -9
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +12 -7
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -2
- data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +3 -3
- data/webpack/InsightsCloudSync/InsightsCloudSync.js +3 -3
- data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +10 -0
- data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +1 -1
- data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +5 -5
- data/webpack/InsightsVulnerabilityHostIndexExtensions/CVECountCell.js +2 -2
- data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +77 -22
- data/webpack/common/Hooks/ConfigHooks.js +3 -16
- metadata +17 -8
- data/lib/foreman_inventory_upload/async/upload_report_job.rb +0 -97
- data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +0 -55
- data/test/jobs/upload_report_job_test.rb +0 -37
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +0 -36
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +0 -112
- data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +0 -3
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
require 'foreman_tasks/test_helpers'
|
|
3
|
+
|
|
4
|
+
class UploadReportDirectJobTest < ActiveSupport::TestCase
|
|
5
|
+
include Dynflow::Testing::Factories
|
|
6
|
+
include Dynflow::Testing::Assertions
|
|
7
|
+
|
|
8
|
+
setup do
|
|
9
|
+
@organization = FactoryBot.create(:organization)
|
|
10
|
+
@uploads_folder = ForemanInventoryUpload.uploads_folder
|
|
11
|
+
@filename = File.join(@uploads_folder, 'test_report.tar.xz')
|
|
12
|
+
|
|
13
|
+
# Stub settings
|
|
14
|
+
Setting.stubs(:[]).with(:subscription_connection_enabled).returns(true)
|
|
15
|
+
Setting.stubs(:[]).with(:ssl_certificate).returns('/fake/cert.pem')
|
|
16
|
+
Setting.stubs(:[]).with(:ssl_priv_key).returns('/fake/key.pem')
|
|
17
|
+
|
|
18
|
+
# Stub ForemanRhCloud methods
|
|
19
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
20
|
+
|
|
21
|
+
# Stub organization owner details with certificate
|
|
22
|
+
@cert_data = {
|
|
23
|
+
'upstreamConsumer' => {
|
|
24
|
+
'idCert' => {
|
|
25
|
+
'cert' => 'FAKE CERTIFICATE',
|
|
26
|
+
'key' => 'FAKE KEY',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
Organization.any_instance.stubs(:owner_details).returns(@cert_data)
|
|
31
|
+
|
|
32
|
+
# Clear task output
|
|
33
|
+
TaskOutputLine.delete_all
|
|
34
|
+
TaskOutputStatus.delete_all
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test 'plan sets input correctly' do
|
|
38
|
+
action = create_and_plan_action(
|
|
39
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
40
|
+
@filename,
|
|
41
|
+
@organization.id
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert_equal @filename, action.input[:filename]
|
|
45
|
+
assert_equal @organization.id, action.input[:organization_id]
|
|
46
|
+
assert_equal "upload_for_#{@organization.id}", action.input[:instance_label]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test 'output_label generates correct label' do
|
|
50
|
+
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
51
|
+
assert_equal "upload_for_#{@organization.id}", label
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test 'uses manifest certificate in regular mode' do
|
|
55
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
56
|
+
|
|
57
|
+
action = create_and_plan_action(
|
|
58
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
59
|
+
@filename,
|
|
60
|
+
@organization.id
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
cert = action.send(:certificate)
|
|
64
|
+
assert_equal 'FAKE CERTIFICATE', cert[:cert]
|
|
65
|
+
assert_equal 'FAKE KEY', cert[:key]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test 'manifest_certificate extracts from organization owner_details' do
|
|
69
|
+
action = create_and_plan_action(
|
|
70
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
71
|
+
@filename,
|
|
72
|
+
@organization.id
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
cert = action.send(:manifest_certificate)
|
|
76
|
+
assert_equal 'FAKE CERTIFICATE', cert[:cert]
|
|
77
|
+
assert_equal 'FAKE KEY', cert[:key]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test 'uses foreman certificate in IoP mode' do
|
|
81
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
82
|
+
File.stubs(:read).with('/fake/cert.pem').returns('FOREMAN CERTIFICATE')
|
|
83
|
+
File.stubs(:read).with('/fake/key.pem').returns('FOREMAN KEY')
|
|
84
|
+
|
|
85
|
+
action = create_and_plan_action(
|
|
86
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
87
|
+
@filename,
|
|
88
|
+
@organization.id
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
cert = action.send(:foreman_certificate)
|
|
92
|
+
assert_equal 'FOREMAN CERTIFICATE', cert[:cert]
|
|
93
|
+
assert_equal 'FOREMAN KEY', cert[:key]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test 'certificate method returns manifest cert in regular mode' do
|
|
97
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
98
|
+
|
|
99
|
+
action = create_and_plan_action(
|
|
100
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
101
|
+
@filename,
|
|
102
|
+
@organization.id
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
cert = action.send(:certificate)
|
|
106
|
+
assert_equal 'FAKE CERTIFICATE', cert[:cert]
|
|
107
|
+
assert_equal 'FAKE KEY', cert[:key]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
test 'certificate method returns foreman cert in IoP mode' do
|
|
111
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
112
|
+
File.stubs(:read).with('/fake/cert.pem').returns('FOREMAN CERTIFICATE')
|
|
113
|
+
File.stubs(:read).with('/fake/key.pem').returns('FOREMAN KEY')
|
|
114
|
+
|
|
115
|
+
action = create_and_plan_action(
|
|
116
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
117
|
+
@filename,
|
|
118
|
+
@organization.id
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
cert = action.send(:certificate)
|
|
122
|
+
assert_equal 'FOREMAN CERTIFICATE', cert[:cert]
|
|
123
|
+
assert_equal 'FOREMAN KEY', cert[:key]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test 'filename returns input filename' do
|
|
127
|
+
action = create_and_plan_action(
|
|
128
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
129
|
+
@filename,
|
|
130
|
+
@organization.id
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert_equal @filename, action.send(:filename)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test 'organization returns Organization from input organization_id' do
|
|
137
|
+
action = create_and_plan_action(
|
|
138
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
139
|
+
@filename,
|
|
140
|
+
@organization.id
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
org = action.send(:organization)
|
|
144
|
+
assert_equal @organization.id, org.id
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
test 'content_disconnected? returns true when subscription_connection_enabled is false' do
|
|
148
|
+
Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
|
|
149
|
+
|
|
150
|
+
action = create_and_plan_action(
|
|
151
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
152
|
+
@filename,
|
|
153
|
+
@organization.id
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
assert action.send(:content_disconnected?)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
test 'content_disconnected? returns false when subscription_connection_enabled is true' do
|
|
160
|
+
Setting.stubs(:[]).with(:subscription_connection_enabled).returns(true)
|
|
161
|
+
|
|
162
|
+
action = create_and_plan_action(
|
|
163
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
164
|
+
@filename,
|
|
165
|
+
@organization.id
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
refute action.send(:content_disconnected?)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
test 'instance_label returns label from input' do
|
|
172
|
+
action = create_and_plan_action(
|
|
173
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
174
|
+
@filename,
|
|
175
|
+
@organization.id
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
assert_equal "upload_for_#{@organization.id}", action.send(:instance_label)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
test 'clears previous task output on plan' do
|
|
182
|
+
# Create some old output
|
|
183
|
+
old_label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
184
|
+
TaskOutputLine.create!(label: old_label, line: 'old line')
|
|
185
|
+
TaskOutputStatus.create!(label: old_label, status: 'old status')
|
|
186
|
+
|
|
187
|
+
create_and_plan_action(
|
|
188
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
189
|
+
@filename,
|
|
190
|
+
@organization.id
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Verify old output was cleared
|
|
194
|
+
assert_equal 0, TaskOutputLine.where(label: old_label).count
|
|
195
|
+
assert_equal 0, TaskOutputStatus.where(label: old_label).count
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
test 'rescue_strategy_for_self returns Fail strategy' do
|
|
199
|
+
action = create_and_plan_action(
|
|
200
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
201
|
+
@filename,
|
|
202
|
+
@organization.id
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
assert_equal Dynflow::Action::Rescue::Fail, action.send(:rescue_strategy_for_self)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
test 'handles RestClient server error gracefully' do
|
|
209
|
+
# Create mock response for RestClient exception
|
|
210
|
+
response = mock('response')
|
|
211
|
+
response.stubs(:code).returns(500)
|
|
212
|
+
response.stubs(:body).returns('Server error')
|
|
213
|
+
|
|
214
|
+
# Stub upload_file to raise server error
|
|
215
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob.any_instance.stubs(:upload_file)
|
|
216
|
+
.raises(RestClient::InternalServerError.new(response))
|
|
217
|
+
|
|
218
|
+
action = create_and_plan_action(
|
|
219
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
220
|
+
@filename,
|
|
221
|
+
@organization.id
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Should raise the error (handled by Dynflow retry mechanism)
|
|
225
|
+
assert_raises(RestClient::InternalServerError) do
|
|
226
|
+
action.send(:try_execute)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Verify progress output shows error
|
|
230
|
+
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
231
|
+
output = ForemanInventoryUpload::Async::ProgressOutput.get(label).full_output
|
|
232
|
+
assert_match(/Upload failed/, output)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
test 'handles RestClient timeout gracefully' do
|
|
236
|
+
# Stub upload_file to raise timeout (Timeout exception doesn't need response object)
|
|
237
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob.any_instance.stubs(:upload_file)
|
|
238
|
+
.raises(RestClient::Exceptions::Timeout.new)
|
|
239
|
+
|
|
240
|
+
action = create_and_plan_action(
|
|
241
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
242
|
+
@filename,
|
|
243
|
+
@organization.id
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Should raise the error (handled by Dynflow retry mechanism via ExponentialBackoff)
|
|
247
|
+
assert_raises(RestClient::Exceptions::Timeout) do
|
|
248
|
+
action.send(:try_execute)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Verify progress output shows error
|
|
252
|
+
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
253
|
+
output = ForemanInventoryUpload::Async::ProgressOutput.get(label).full_output
|
|
254
|
+
assert_match(/Upload failed/, output)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
test 'uses proxy configuration from ForemanRhCloud' do
|
|
258
|
+
proxy_url = 'http://proxy.example.com:8080'
|
|
259
|
+
ForemanRhCloud.stubs(:transformed_http_proxy_string).returns(proxy_url)
|
|
260
|
+
|
|
261
|
+
# Create test file
|
|
262
|
+
FileUtils.mkdir_p(File.dirname(@filename))
|
|
263
|
+
FileUtils.touch(@filename)
|
|
264
|
+
|
|
265
|
+
action = create_and_plan_action(
|
|
266
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
267
|
+
@filename,
|
|
268
|
+
@organization.id
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Mock response
|
|
272
|
+
response = mock('response')
|
|
273
|
+
response.stubs(:code).returns(200)
|
|
274
|
+
|
|
275
|
+
# Verify execute_cloud_request is called (which handles proxy)
|
|
276
|
+
# We can't test the actual proxy parameters because CloudRequest concern
|
|
277
|
+
# merges them internally, but we can verify the method is called
|
|
278
|
+
action.expects(:execute_cloud_request).returns(response)
|
|
279
|
+
|
|
280
|
+
# Stub upload_file to call execute_cloud_request (avoiding SSL cert creation issues)
|
|
281
|
+
action.stubs(:upload_file).returns(nil)
|
|
282
|
+
|
|
283
|
+
# Manually call upload_file expectations in the test
|
|
284
|
+
# This simulates what would happen during actual execution
|
|
285
|
+
action.send(:execute_cloud_request,
|
|
286
|
+
method: :post,
|
|
287
|
+
url: ForemanInventoryUpload.upload_url,
|
|
288
|
+
payload: { multipart: true },
|
|
289
|
+
headers: { 'X-Org-Id' => @organization.label })
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
test 'file cleanup when upload aborted due to missing certificate' do
|
|
293
|
+
# Remove certificate from organization
|
|
294
|
+
Organization.any_instance.stubs(:owner_details).returns({})
|
|
295
|
+
|
|
296
|
+
# Create a real test file to verify it's not moved
|
|
297
|
+
FileUtils.mkdir_p(@uploads_folder)
|
|
298
|
+
test_file = File.join(@uploads_folder, 'test_file_for_cleanup.tar.xz')
|
|
299
|
+
FileUtils.touch(test_file)
|
|
300
|
+
|
|
301
|
+
begin
|
|
302
|
+
action = create_and_plan_action(
|
|
303
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
304
|
+
test_file,
|
|
305
|
+
@organization.id
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Execute the action
|
|
309
|
+
action.send(:try_execute)
|
|
310
|
+
|
|
311
|
+
# Verify file still exists (not moved or deleted)
|
|
312
|
+
assert File.exist?(test_file), "File should remain when upload is aborted"
|
|
313
|
+
|
|
314
|
+
# Verify progress output mentions missing certificate
|
|
315
|
+
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
316
|
+
output = ForemanInventoryUpload::Async::ProgressOutput.get(label).full_output
|
|
317
|
+
assert_match(/Skipping organization.*no candlepin certificate/, output)
|
|
318
|
+
|
|
319
|
+
# Verify status indicates abortion
|
|
320
|
+
status = ForemanInventoryUpload::Async::ProgressOutput.get(label).status
|
|
321
|
+
assert_match(/exit 1/, status)
|
|
322
|
+
ensure
|
|
323
|
+
FileUtils.rm_f(test_file) if File.exist?(test_file)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
test 'file cleanup when upload aborted due to disconnected mode' do
|
|
328
|
+
Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
|
|
329
|
+
|
|
330
|
+
# Create a real test file
|
|
331
|
+
FileUtils.mkdir_p(@uploads_folder)
|
|
332
|
+
test_file = File.join(@uploads_folder, 'test_file_disconnected.tar.xz')
|
|
333
|
+
FileUtils.touch(test_file)
|
|
334
|
+
|
|
335
|
+
begin
|
|
336
|
+
action = create_and_plan_action(
|
|
337
|
+
ForemanInventoryUpload::Async::UploadReportDirectJob,
|
|
338
|
+
test_file,
|
|
339
|
+
@organization.id
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Execute the action
|
|
343
|
+
action.send(:try_execute)
|
|
344
|
+
|
|
345
|
+
# Verify file still exists
|
|
346
|
+
assert File.exist?(test_file), "File should remain when connection is disabled"
|
|
347
|
+
|
|
348
|
+
# Verify progress output
|
|
349
|
+
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
|
|
350
|
+
output = ForemanInventoryUpload::Async::ProgressOutput.get(label).full_output
|
|
351
|
+
assert_match(/connection to Insights is not enabled/, output)
|
|
352
|
+
ensure
|
|
353
|
+
FileUtils.rm_f(test_file) if File.exist?(test_file)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
test 'FileUpload wrapper delegates to file object' do
|
|
358
|
+
# Create test file
|
|
359
|
+
FileUtils.mkdir_p(File.dirname(@filename))
|
|
360
|
+
FileUtils.touch(@filename)
|
|
361
|
+
|
|
362
|
+
file = File.open(@filename, 'rb')
|
|
363
|
+
begin
|
|
364
|
+
wrapped = ForemanInventoryUpload::Async::UploadReportDirectJob::FileUpload.new(
|
|
365
|
+
file,
|
|
366
|
+
content_type: 'application/test'
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
assert_equal 'application/test', wrapped.content_type
|
|
370
|
+
assert_equal file.path, wrapped.path
|
|
371
|
+
assert_respond_to wrapped, :read
|
|
372
|
+
assert_respond_to wrapped, :close
|
|
373
|
+
ensure
|
|
374
|
+
file.close
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
test 'FileUpload wrapper provides content_type for RestClient' do
|
|
379
|
+
# Create test file
|
|
380
|
+
FileUtils.mkdir_p(File.dirname(@filename))
|
|
381
|
+
FileUtils.touch(@filename)
|
|
382
|
+
|
|
383
|
+
file = File.open(@filename, 'rb')
|
|
384
|
+
begin
|
|
385
|
+
wrapped = ForemanInventoryUpload::Async::UploadReportDirectJob::FileUpload.new(
|
|
386
|
+
file,
|
|
387
|
+
content_type: 'application/vnd.redhat.qpc.tar+tgz'
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# RestClient checks for these methods
|
|
391
|
+
assert_respond_to wrapped, :read
|
|
392
|
+
assert_respond_to wrapped, :path
|
|
393
|
+
assert_respond_to wrapped, :content_type
|
|
394
|
+
assert_equal 'application/vnd.redhat.qpc.tar+tgz', wrapped.content_type
|
|
395
|
+
ensure
|
|
396
|
+
file.close
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
|
|
3
|
+
module ForemanRhCloud
|
|
4
|
+
class RegistrationManagerExtensionsTest < ActiveSupport::TestCase
|
|
5
|
+
setup do
|
|
6
|
+
@org = FactoryBot.create(:organization)
|
|
7
|
+
@host = FactoryBot.create(:host, :managed, organization: @org)
|
|
8
|
+
@insights_facet = ::InsightsFacet.create!(host: @host, uuid: 'test-uuid-123')
|
|
9
|
+
|
|
10
|
+
# Stub Candlepin interaction (from Katello)
|
|
11
|
+
::Katello::Resources::Candlepin::Consumer.stubs(:destroy)
|
|
12
|
+
|
|
13
|
+
# Stub the cloud request to avoid actual HTTP calls
|
|
14
|
+
Katello::RegistrationManager.stubs(:execute_cloud_request).returns(true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context 'unregister_host' do
|
|
18
|
+
test 'should call HBI delete in IoP mode when host has insights facet with UUID' do
|
|
19
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
20
|
+
expected_url = ForemanInventoryUpload.host_by_id_url('test-uuid-123')
|
|
21
|
+
|
|
22
|
+
# Expect the cloud request to be made
|
|
23
|
+
Katello::RegistrationManager.expects(:execute_cloud_request).with do |params|
|
|
24
|
+
params[:organization] == @org &&
|
|
25
|
+
params[:method] == :delete &&
|
|
26
|
+
params[:url] == expected_url &&
|
|
27
|
+
params[:headers][:content_type] == :json
|
|
28
|
+
end.returns(true)
|
|
29
|
+
|
|
30
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
31
|
+
|
|
32
|
+
# Verify insights_facet was destroyed
|
|
33
|
+
assert_nil InsightsFacet.find_by(id: @insights_facet.id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
test 'should NOT call HBI delete in non-IoP mode' do
|
|
37
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
38
|
+
|
|
39
|
+
# Should NOT attempt to delete from HBI
|
|
40
|
+
Katello::RegistrationManager.expects(:execute_cloud_request).never
|
|
41
|
+
|
|
42
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
43
|
+
|
|
44
|
+
# Verify insights_facet was still destroyed
|
|
45
|
+
assert_nil InsightsFacet.find_by(id: @insights_facet.id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
test 'should NOT call HBI delete when host has no insights_facet' do
|
|
49
|
+
host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
|
|
50
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
51
|
+
|
|
52
|
+
Katello::RegistrationManager.expects(:execute_cloud_request).never
|
|
53
|
+
|
|
54
|
+
assert_nothing_raised do
|
|
55
|
+
Katello::RegistrationManager.unregister_host(host_without_facet, unregistering: true)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
test 'should NOT call HBI delete when host has insights_facet with empty UUID' do
|
|
60
|
+
host_with_empty_uuid_facet = FactoryBot.create(:host, :managed, organization: @org)
|
|
61
|
+
empty_uuid_facet = InsightsFacet.create!(host: host_with_empty_uuid_facet, uuid: '')
|
|
62
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
63
|
+
|
|
64
|
+
Katello::RegistrationManager.expects(:execute_cloud_request).never
|
|
65
|
+
|
|
66
|
+
assert_nothing_raised do
|
|
67
|
+
Katello::RegistrationManager.unregister_host(host_with_empty_uuid_facet, unregistering: true)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
assert_nil InsightsFacet.find_by(id: empty_uuid_facet.id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
test 'should NOT call HBI delete when insights_facet has no UUID' do
|
|
74
|
+
facet_id = @insights_facet.id
|
|
75
|
+
@insights_facet.update(uuid: nil)
|
|
76
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
77
|
+
|
|
78
|
+
Katello::RegistrationManager.expects(:execute_cloud_request).never
|
|
79
|
+
|
|
80
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
81
|
+
|
|
82
|
+
# Verify facet was still destroyed
|
|
83
|
+
assert_nil InsightsFacet.find_by(id: facet_id)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
test 'should always destroy insights_facet regardless of IoP mode' do
|
|
87
|
+
facet_id = @insights_facet.id
|
|
88
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
89
|
+
|
|
90
|
+
assert_not_nil InsightsFacet.find_by(id: facet_id)
|
|
91
|
+
|
|
92
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
93
|
+
|
|
94
|
+
assert_nil InsightsFacet.find_by(id: facet_id)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context 'hbi_host_destroy error handling' do
|
|
99
|
+
setup do
|
|
100
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
101
|
+
@expected_url = ForemanInventoryUpload.host_by_id_url('test-uuid-123')
|
|
102
|
+
@facet_id = @insights_facet.id
|
|
103
|
+
# Unstub execute_cloud_request for error tests
|
|
104
|
+
Katello::RegistrationManager.unstub(:execute_cloud_request)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
test 'should handle RestClient::NotFound gracefully' do
|
|
108
|
+
Katello::RegistrationManager.stubs(:execute_cloud_request).raises(RestClient::NotFound)
|
|
109
|
+
|
|
110
|
+
# Should log warning but not raise
|
|
111
|
+
Rails.logger.expects(:warn).with(regexp_matches(/host does not exist in HBI/))
|
|
112
|
+
|
|
113
|
+
assert_nothing_raised do
|
|
114
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Facet should still be destroyed
|
|
118
|
+
assert_nil InsightsFacet.find_by(id: @facet_id)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
test 'should handle server errors gracefully' do
|
|
122
|
+
error = RestClient::InternalServerError.new
|
|
123
|
+
Katello::RegistrationManager.stubs(:execute_cloud_request).raises(error)
|
|
124
|
+
|
|
125
|
+
# Should log error but not raise
|
|
126
|
+
Rails.logger.expects(:error).with(regexp_matches(/Failed to destroy HBI host/))
|
|
127
|
+
|
|
128
|
+
assert_nothing_raised do
|
|
129
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Facet should still be destroyed
|
|
133
|
+
assert_nil InsightsFacet.find_by(id: @facet_id)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test 'should handle timeout errors gracefully' do
|
|
137
|
+
error = RestClient::Exceptions::ReadTimeout.new
|
|
138
|
+
Katello::RegistrationManager.stubs(:execute_cloud_request).raises(error)
|
|
139
|
+
|
|
140
|
+
# Should log error but not raise
|
|
141
|
+
Rails.logger.expects(:error).with(regexp_matches(/Failed to destroy HBI host/))
|
|
142
|
+
|
|
143
|
+
assert_nothing_raised do
|
|
144
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Facet should still be destroyed
|
|
148
|
+
assert_nil InsightsFacet.find_by(id: @facet_id)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
test 'should handle connection errors gracefully' do
|
|
152
|
+
error = Errno::ECONNREFUSED.new
|
|
153
|
+
Katello::RegistrationManager.stubs(:execute_cloud_request).raises(error)
|
|
154
|
+
|
|
155
|
+
# Should log error but not raise
|
|
156
|
+
Rails.logger.expects(:error).with(regexp_matches(/Failed to destroy HBI host/))
|
|
157
|
+
|
|
158
|
+
assert_nothing_raised do
|
|
159
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Facet should still be destroyed
|
|
163
|
+
assert_nil InsightsFacet.find_by(id: @facet_id)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
context 'integration' do
|
|
168
|
+
test 'should be properly prepended to RegistrationManager' do
|
|
169
|
+
assert_includes Katello::RegistrationManager.singleton_class.ancestors,
|
|
170
|
+
ForemanRhCloud::RegistrationManagerExtensions
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
test 'should preserve original unregister_host behavior' do
|
|
174
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
175
|
+
|
|
176
|
+
# Just verify the extension is properly integrated
|
|
177
|
+
# Detailed Katello behavior is tested in Katello's own tests
|
|
178
|
+
assert_nothing_raised do
|
|
179
|
+
Katello::RegistrationManager.unregister_host(@host, unregistering: true)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context 'URL generation' do
|
|
185
|
+
test 'host_by_id_url should return correct format' do
|
|
186
|
+
url = ForemanInventoryUpload.host_by_id_url('test-uuid-123')
|
|
187
|
+
|
|
188
|
+
assert_match %r{/hosts/test-uuid-123$}, url
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -40,7 +40,7 @@ class AccountList extends Component {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const items = Object.keys(filteredAccount).map((label, index) => {
|
|
43
|
-
const account =
|
|
43
|
+
const account = filteredAccount[label];
|
|
44
44
|
return <ListItem key={index} label={label} account={account} />;
|
|
45
45
|
});
|
|
46
46
|
return <Accordion className="account-list">{items}</Accordion>;
|
data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export const props = {
|
|
2
2
|
label: 'test',
|
|
3
3
|
account: {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
4
|
+
generate_report_status: 'unknown',
|
|
5
|
+
upload_report_status: 'unknown',
|
|
6
|
+
id: 1,
|
|
7
|
+
report_file_paths: [],
|
|
9
8
|
},
|
|
10
9
|
};
|
|
@@ -10,8 +10,10 @@ import PropTypes from 'prop-types';
|
|
|
10
10
|
import ListItemStatus from '../ListItemStatus';
|
|
11
11
|
import Dashboard from '../../../Dashboard';
|
|
12
12
|
|
|
13
|
-
const ListItem = ({ label, account }) => {
|
|
13
|
+
const ListItem = ({ label, account = {} }) => {
|
|
14
14
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
15
|
+
const accountId = account?.id ?? 0;
|
|
16
|
+
|
|
15
17
|
return (
|
|
16
18
|
<AccordionItem>
|
|
17
19
|
<AccordionToggle
|
|
@@ -30,7 +32,7 @@ const ListItem = ({ label, account }) => {
|
|
|
30
32
|
<ListItemStatus key={`${label}_status`} account={account} />
|
|
31
33
|
</AccordionToggle>
|
|
32
34
|
<AccordionContent isHidden={!isExpanded}>
|
|
33
|
-
<Dashboard accountID={
|
|
35
|
+
<Dashboard accountID={accountId} account={account} />
|
|
34
36
|
</AccordionContent>
|
|
35
37
|
</AccordionItem>
|
|
36
38
|
);
|
|
@@ -18,11 +18,10 @@ exports[`ListItem rendering render with Props 1`] = `
|
|
|
18
18
|
<ListItemStatus
|
|
19
19
|
account={
|
|
20
20
|
Object {
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
21
|
+
"generate_report_status": "unknown",
|
|
22
|
+
"id": 1,
|
|
23
|
+
"report_file_paths": Array [],
|
|
24
|
+
"upload_report_status": "unknown",
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
key="test_status"
|
|
@@ -34,13 +33,13 @@ exports[`ListItem rendering render with Props 1`] = `
|
|
|
34
33
|
<Connect(Dashboard)
|
|
35
34
|
account={
|
|
36
35
|
Object {
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
36
|
+
"generate_report_status": "unknown",
|
|
37
|
+
"id": 1,
|
|
38
|
+
"report_file_paths": Array [],
|
|
39
|
+
"upload_report_status": "unknown",
|
|
42
40
|
}
|
|
43
41
|
}
|
|
42
|
+
accountID={1}
|
|
44
43
|
/>
|
|
45
44
|
</AccordionContent>
|
|
46
45
|
</AccordionItem>
|