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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/foreman_rh_cloud/registration_manager_extensions.rb +39 -0
  3. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +1 -1
  4. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +1 -1
  5. data/lib/foreman_inventory_upload/async/create_missing_insights_facets.rb +3 -2
  6. data/lib/foreman_inventory_upload/async/queue_for_upload_job.rb +1 -23
  7. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +200 -0
  8. data/lib/foreman_inventory_upload.rb +6 -6
  9. data/lib/foreman_rh_cloud/engine.rb +1 -0
  10. data/lib/foreman_rh_cloud/plugin.rb +4 -0
  11. data/lib/foreman_rh_cloud/version.rb +1 -1
  12. data/lib/inventory_sync/async/inventory_hosts_sync.rb +0 -2
  13. data/lib/tasks/rh_cloud_inventory.rake +11 -1
  14. data/package.json +1 -1
  15. data/test/controllers/accounts_controller_test.rb +1 -1
  16. data/test/controllers/uploads_controller_test.rb +1 -1
  17. data/test/jobs/cloud_connector_announce_task_test.rb +3 -2
  18. data/test/jobs/connector_playbook_execution_reporter_task_test.rb +32 -20
  19. data/test/jobs/create_missing_insights_facets_test.rb +151 -0
  20. data/test/jobs/exponential_backoff_test.rb +9 -8
  21. data/test/jobs/generate_host_report_test.rb +100 -0
  22. data/test/jobs/generate_report_job_test.rb +146 -0
  23. data/test/jobs/host_inventory_report_job_test.rb +244 -0
  24. data/test/jobs/insights_client_status_aging_test.rb +3 -2
  25. data/test/jobs/insights_full_sync_test.rb +13 -7
  26. data/test/jobs/insights_resolutions_sync_test.rb +9 -5
  27. data/test/jobs/insights_rules_sync_test.rb +5 -3
  28. data/test/jobs/inventory_full_sync_test.rb +9 -5
  29. data/test/jobs/inventory_hosts_sync_test.rb +11 -6
  30. data/test/jobs/inventory_scheduled_sync_test.rb +10 -6
  31. data/test/jobs/inventory_self_host_sync_test.rb +1 -1
  32. data/test/jobs/queue_for_upload_job_test.rb +10 -19
  33. data/test/jobs/remove_insights_hosts_job_test.rb +14 -15
  34. data/test/jobs/single_host_report_job_test.rb +155 -0
  35. data/test/jobs/upload_report_direct_job_test.rb +399 -0
  36. data/test/unit/lib/foreman_rh_cloud/registration_manager_extensions_test.rb +192 -0
  37. data/webpack/ForemanColumnExtensions/index.js +2 -0
  38. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +1 -1
  39. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +4 -5
  40. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +4 -2
  41. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +9 -10
  42. data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +4 -1
  43. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +24 -17
  44. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +178 -8
  45. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +3 -1
  46. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +69 -51
  47. data/webpack/InsightsCloudSync/Components/InsightsSettings/InsightsSettings.js +3 -3
  48. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +3 -9
  49. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +12 -7
  50. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -2
  51. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +3 -3
  52. data/webpack/InsightsCloudSync/InsightsCloudSync.js +3 -3
  53. data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +10 -0
  54. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +1 -1
  55. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +5 -5
  56. data/webpack/InsightsVulnerabilityHostIndexExtensions/CVECountCell.js +2 -2
  57. data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +77 -22
  58. data/webpack/common/Hooks/ConfigHooks.js +3 -16
  59. metadata +17 -8
  60. data/lib/foreman_inventory_upload/async/upload_report_job.rb +0 -97
  61. data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +0 -55
  62. data/test/jobs/upload_report_job_test.rb +0 -37
  63. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +0 -36
  64. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +0 -112
  65. 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
@@ -79,6 +79,8 @@ const hostsIndexColumnExtensions = [
79
79
  categoryName: insightsCategoryName,
80
80
  categoryKey: 'insights',
81
81
  isSorted: false,
82
+ // eslint-disable-next-line camelcase
83
+ isRelevant: contextData => contextData?.metadata?.foreman_rh_cloud?.iop,
82
84
  },
83
85
  ];
84
86
 
@@ -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 = accounts[label];
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>;
@@ -1,10 +1,9 @@
1
1
  export const props = {
2
2
  label: 'test',
3
3
  account: {
4
- test: {
5
- generate_report_status: 'unknown',
6
- upload_report_status: 'unknown',
7
- id: 1,
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={account.id} account={account} />
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
- "test": Object {
22
- "generate_report_status": "unknown",
23
- "id": 1,
24
- "upload_report_status": "unknown",
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
- "test": Object {
38
- "generate_report_status": "unknown",
39
- "id": 1,
40
- "upload_report_status": "unknown",
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>