foreman_rh_cloud 13.2.8 → 13.2.10

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/insights_cloud/candlepin_proxies_extensions.rb +23 -0
  3. data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +9 -0
  4. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +13 -10
  5. data/app/controllers/insights_cloud/ui_requests_controller.rb +3 -7
  6. data/app/models/concerns/rh_cloud_host.rb +4 -2
  7. data/app/models/insights_client_report_status.rb +9 -1
  8. data/app/models/inventory_sync/inventory_status.rb +16 -4
  9. data/lib/foreman_inventory_upload/generators/fact_helpers.rb +26 -4
  10. data/lib/foreman_inventory_upload.rb +8 -1
  11. data/lib/foreman_rh_cloud/engine.rb +1 -0
  12. data/lib/foreman_rh_cloud/version.rb +1 -1
  13. data/lib/foreman_rh_cloud.rb +36 -9
  14. data/lib/insights_cloud/async/insights_generate_notifications.rb +10 -1
  15. data/lib/inventory_sync/async/inventory_full_sync.rb +39 -3
  16. data/lib/inventory_sync/async/inventory_self_host_sync.rb +12 -2
  17. data/package.json +1 -1
  18. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +56 -2
  19. data/test/controllers/insights_cloud/candlepin_proxies_extensions_test.rb +70 -0
  20. data/test/controllers/insights_cloud/ui_requests_controller_test.rb +16 -2
  21. data/test/jobs/insights_client_status_aging_test.rb +40 -0
  22. data/test/jobs/insights_generate_notifications_test.rb +26 -0
  23. data/test/jobs/inventory_full_sync_test.rb +212 -0
  24. data/test/jobs/inventory_self_host_sync_test.rb +9 -0
  25. data/test/models/insights_client_report_status_test.rb +109 -0
  26. data/test/models/inventory_sync/inventory_status_test.rb +85 -0
  27. data/test/unit/foreman_rh_cloud_self_host_test.rb +50 -2
  28. data/test/unit/metadata_generator_test.rb +24 -1
  29. data/test/unit/rh_cloud_host_test.rb +60 -0
  30. data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +24 -2
  31. data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +73 -10
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js +8 -2
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +1 -0
  34. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +1 -0
  35. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js +43 -17
  36. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/__tests__/Toast.test.js +82 -0
  37. data/webpack/ForemanRhCloudHelpers.js +22 -0
  38. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +27 -1
  39. data/webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js +134 -22
  40. metadata +16 -2
@@ -177,8 +177,22 @@ module InsightsCloud
177
177
 
178
178
  get :forward_request, params: { "controller" => "vulnerabilities", "path" => "api/vulnerability/v1/cves" }, session: set_session
179
179
  assert_equal 500, @response.status
180
- assert_equal 'Cloud request failed', JSON.parse(@response.body)['message']
181
- assert_match(/#{@body}/, JSON.parse(@response.body)['response'])
180
+ assert_equal @body, @response.body
181
+ end
182
+
183
+ test "should forward JSON error responses without double-escaping" do
184
+ json_error = { errors: [{ detail: 'inventory_id must exist', status: '404' }] }.to_json
185
+ net_http_resp = Net::HTTPResponse.new(1.0, 404, "Not Found")
186
+ net_http_resp['content-type'] = 'application/json'
187
+ res = RestClient::Response.create(json_error, net_http_resp, @http_req)
188
+ ::ForemanRhCloud::InsightsApiForwarder.any_instance.stubs(:execute_cloud_request).raises(RestClient::NotFound.new(res))
189
+
190
+ get :forward_request, params: { "controller" => "vulnerabilities", "path" => "api/vulnerability/v1/systems/00000000-0000-0000-0000-000000000000" }, session: set_session
191
+ assert_equal 404, @response.status
192
+ assert_includes @response.content_type, 'application/json'
193
+ assert_equal json_error, @response.body
194
+ parsed = JSON.parse(@response.body)
195
+ assert_equal 'inventory_id must exist', parsed['errors'][0]['detail']
182
196
  end
183
197
 
184
198
  test "should allow forward_request with nil location (Any location)" do
@@ -31,4 +31,44 @@ class InsightsClientStatusAgingTest < ActiveSupport::TestCase
31
31
  assert_equal InsightsClientReportStatus::NO_REPORT, @host3.get_status(InsightsClientReportStatus).status
32
32
  assert_equal InsightsClientReportStatus::NO_REPORT, @host4.get_status(InsightsClientReportStatus).status
33
33
  end
34
+
35
+ test 'aging job does not affect USER_OMITTED hosts' do
36
+ # Host 1: USER_OMITTED with old reported_at (should stay USER_OMITTED)
37
+ InsightsClientReportStatus.find_or_initialize_by(host_id: @host1.id).update(
38
+ status: InsightsClientReportStatus::USER_OMITTED,
39
+ reported_at: Time.now - InsightsClientReportStatus::REPORT_INTERVAL - 1.day
40
+ )
41
+
42
+ # Host 2: REPORTING with old reported_at (should change to NO_REPORT)
43
+ InsightsClientReportStatus.find_or_initialize_by(host_id: @host2.id).update(
44
+ status: InsightsClientReportStatus::REPORTING,
45
+ reported_at: Time.now - InsightsClientReportStatus::REPORT_INTERVAL - 1.day
46
+ )
47
+
48
+ # Host 3: USER_OMITTED with recent reported_at (should stay USER_OMITTED)
49
+ InsightsClientReportStatus.find_or_initialize_by(host_id: @host3.id).update(
50
+ status: InsightsClientReportStatus::USER_OMITTED,
51
+ reported_at: Time.now - 1.day
52
+ )
53
+
54
+ # Host 4: REPORTING with recent reported_at (should stay REPORTING)
55
+ InsightsClientReportStatus.find_or_initialize_by(host_id: @host4.id).update(
56
+ status: InsightsClientReportStatus::REPORTING,
57
+ reported_at: Time.now - 1.day
58
+ )
59
+
60
+ action = create_and_plan_action(InsightsCloud::Async::InsightsClientStatusAging)
61
+ run_action(action)
62
+
63
+ @hosts.each(&:reload)
64
+
65
+ assert_equal InsightsClientReportStatus::USER_OMITTED, @host1.get_status(InsightsClientReportStatus).status,
66
+ 'USER_OMITTED host with old report should stay USER_OMITTED'
67
+ assert_equal InsightsClientReportStatus::NO_REPORT, @host2.get_status(InsightsClientReportStatus).status,
68
+ 'REPORTING host with old report should change to NO_REPORT'
69
+ assert_equal InsightsClientReportStatus::USER_OMITTED, @host3.get_status(InsightsClientReportStatus).status,
70
+ 'USER_OMITTED host with recent report should stay USER_OMITTED'
71
+ assert_equal InsightsClientReportStatus::REPORTING, @host4.get_status(InsightsClientReportStatus).status,
72
+ 'REPORTING host with recent report should stay REPORTING'
73
+ end
34
74
  end
@@ -0,0 +1,26 @@
1
+ require 'test_plugin_helper'
2
+ require 'foreman_tasks/test_helpers'
3
+
4
+ class InsightsGenerateNotificationsTest < ActiveSupport::TestCase
5
+ include Dynflow::Testing::Factories
6
+
7
+ setup do
8
+ User.current = User.find_by(login: 'secret_admin')
9
+ end
10
+
11
+ test 'skips notifications when foreman_host is nil' do
12
+ ForemanRhCloud.stubs(:foreman_host).returns(nil)
13
+
14
+ # Ensure blueprint exists or create it
15
+ NotificationBlueprint.find_or_create_by(name: 'insights_satellite_hits') do |bp|
16
+ bp.message = 'Test message'
17
+ bp.level = 'info'
18
+ bp.expires_in = 7.days
19
+ end
20
+
21
+ plan = ForemanTasks.sync_task(InsightsCloud::Async::InsightsGenerateNotifications)
22
+
23
+ # Should not raise an error, task completes successfully
24
+ assert_includes ['success', 'stopped'], plan.state, "Task should complete without error"
25
+ end
26
+ end
@@ -5,6 +5,7 @@ class InventoryFullSyncTest < ActiveSupport::TestCase
5
5
  include Dynflow::Testing::Factories
6
6
  include MockCerts
7
7
  include KatelloCVEHelper
8
+ include CandlepinIsolation
8
9
 
9
10
  setup do
10
11
  User.current = User.find_by(login: 'secret_admin')
@@ -313,4 +314,215 @@ class InventoryFullSyncTest < ActiveSupport::TestCase
313
314
 
314
315
  assert_nil InventorySync::InventoryStatus.where(host_id: @host3.id).first
315
316
  end
317
+
318
+ test 'user-omitted hosts get USER_OMITTED status' do
319
+ # Add parameter to exclude host1
320
+ @host1.host_parameters << HostParameter.create(
321
+ name: 'host_registration_insights_inventory',
322
+ value: 'false',
323
+ parameter_type: 'boolean'
324
+ )
325
+ @host1.save!
326
+
327
+ setup_certs_expectation do
328
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
329
+ end
330
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
331
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
332
+
333
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
334
+ run_action(action)
335
+
336
+ @host1.reload
337
+ @host2.reload
338
+
339
+ # Host1 should be USER_OMITTED
340
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
341
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
342
+ 'Host with host_registration_insights_inventory=false should have USER_OMITTED status'
343
+
344
+ # Host2 should be SYNC
345
+ assert_equal InventorySync::InventoryStatus::SYNC,
346
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
347
+ 'Normal host should have SYNC status'
348
+ end
349
+
350
+ test 'user-omitted hosts are not marked as disconnected' do
351
+ # Add parameter to exclude host1
352
+ @host1.host_parameters << HostParameter.create(
353
+ name: 'host_registration_insights_inventory',
354
+ value: 'false',
355
+ parameter_type: 'boolean'
356
+ )
357
+ @host1.save!
358
+
359
+ # Host1 is not in the cloud inventory response
360
+ setup_certs_expectation do
361
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
362
+ end
363
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
364
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id])
365
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
366
+
367
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
368
+ run_action(action)
369
+
370
+ @host1.reload
371
+
372
+ # Host1 should be USER_OMITTED, not DISCONNECT
373
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
374
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
375
+ 'User-omitted host should have USER_OMITTED status even if not in cloud inventory'
376
+ end
377
+
378
+ test 'host_statuses output includes all three counts' do
379
+ # Create a mix of hosts with different statuses
380
+ # Host1: will be user-omitted
381
+ @host1.host_parameters << HostParameter.create(
382
+ name: 'host_registration_insights_inventory',
383
+ value: 'false',
384
+ parameter_type: 'boolean'
385
+ )
386
+ @host1.save!
387
+
388
+ # Host2: will be synced (in cloud inventory)
389
+ # Host3: will be disconnected (not in cloud inventory)
390
+
391
+ # Create inventory response with only host2 (exclude host1 and host3)
392
+ inventory_with_host2_only = @inventory.dup
393
+ inventory_with_host2_only['results'] = [@inventory['results'][0]] # Only host2
394
+ inventory_with_host2_only['total'] = 1
395
+ inventory_with_host2_only['count'] = 1
396
+
397
+ setup_certs_expectation do
398
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
399
+ end
400
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(inventory_with_host2_only)
401
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id, @host3.id])
402
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
403
+
404
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
405
+ run_action(action)
406
+
407
+ # Verify the statuses were actually set correctly
408
+ @host1.reload
409
+ @host2.reload
410
+ @host3.reload
411
+
412
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
413
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
414
+ 'Host with host_registration_insights_inventory=false should have USER_OMITTED status'
415
+ assert_equal InventorySync::InventoryStatus::SYNC,
416
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
417
+ 'Host in cloud inventory should have SYNC status'
418
+ assert_equal InventorySync::InventoryStatus::DISCONNECT,
419
+ InventorySync::InventoryStatus.where(host_id: @host3.id).first.status,
420
+ 'Host not in cloud inventory and not user-omitted should have DISCONNECT status'
421
+ end
422
+
423
+ test 'user-omitted status respects parameter inheritance from hostgroup' do
424
+ # Create a hostgroup and assign it to host1
425
+ hostgroup = FactoryBot.create(:hostgroup)
426
+ hostgroup.organizations << @host1.organization
427
+ @host1.hostgroup = hostgroup
428
+ @host1.save!
429
+
430
+ # Set parameter at hostgroup level, not directly on host
431
+ # This verifies the fix that uses search_for instead of querying HostParameter directly
432
+ hostgroup.group_parameters << GroupParameter.create(
433
+ name: 'host_registration_insights_inventory',
434
+ value: 'false',
435
+ key_type: 'boolean'
436
+ )
437
+ hostgroup.save!
438
+
439
+ # Verify parameter is inherited (not set directly on host)
440
+ assert_nil @host1.parameters.find_by(name: 'host_registration_insights_inventory'),
441
+ 'Test setup: parameter should not be set directly on host'
442
+ refute ::Foreman::Cast.to_bool(@host1.host_param('host_registration_insights_inventory')),
443
+ 'Test setup: parameter should be inherited from hostgroup'
444
+
445
+ # Host2 remains normal (no parameter)
446
+ setup_certs_expectation do
447
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
448
+ end
449
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
450
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id])
451
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
452
+
453
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
454
+ run_action(action)
455
+
456
+ @host1.reload
457
+ @host2.reload
458
+
459
+ # Host1 should be USER_OMITTED (inherited parameter from hostgroup)
460
+ host1_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
461
+ assert_not_nil host1_status, 'Host1 should have an inventory status'
462
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, host1_status.status,
463
+ 'Host with inherited host_registration_insights_inventory=false should have USER_OMITTED status'
464
+
465
+ # Host2 should be SYNC
466
+ assert_equal InventorySync::InventoryStatus::SYNC,
467
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
468
+ 'Normal host should have SYNC status'
469
+ end
470
+
471
+ test 'user-omitted statuses are cleared before re-creating them to avoid silent create failures' do
472
+ # First sync: host1 is user-omitted
473
+ @host1.host_parameters << HostParameter.create(
474
+ name: 'host_registration_insights_inventory',
475
+ value: 'false',
476
+ parameter_type: 'boolean'
477
+ )
478
+ @host1.save!
479
+
480
+ setup_certs_expectation do
481
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
482
+ end
483
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory).twice
484
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id]).twice
485
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
486
+
487
+ # Run first sync
488
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
489
+ run_action(action)
490
+
491
+ @host1.reload
492
+ initial_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
493
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, initial_status.status,
494
+ 'Initial sync: host should be USER_OMITTED'
495
+ initial_status_id = initial_status.id
496
+
497
+ # Verify there's only one status record for host1
498
+ assert_equal 1, InventorySync::InventoryStatus.where(host_id: @host1.id).count,
499
+ 'Should have exactly one status record after first sync'
500
+
501
+ # Run second sync (parameter still false)
502
+ # Without clearing old user_omitted statuses, the .create would silently fail
503
+ # with 'Host has already been taken' due to uniqueness constraint
504
+ setup_certs_expectation do
505
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
506
+ end
507
+
508
+ action2 = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
509
+ run_action(action2)
510
+
511
+ @host1.reload
512
+ final_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
513
+
514
+ # Verify status is still USER_OMITTED (not stale from first sync)
515
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, final_status.status,
516
+ 'Status should be USER_OMITTED after second sync'
517
+
518
+ # Verify there's still only one status record (old one was deleted before creating new one)
519
+ assert_equal 1, InventorySync::InventoryStatus.where(host_id: @host1.id).count,
520
+ 'Should have exactly one status record (old cleared before new created)'
521
+
522
+ # Verify the old status record was actually deleted and replaced with a new one
523
+ refute InventorySync::InventoryStatus.exists?(initial_status_id),
524
+ 'Old status record should have been deleted before creating new one'
525
+ assert_not_equal initial_status_id, final_status.id,
526
+ 'New status record should have different ID, proving old was deleted'
527
+ end
316
528
  end
@@ -106,4 +106,13 @@ class InventorySelfHostSyncTest < ActiveSupport::TestCase
106
106
 
107
107
  assert_equal @host1_inventory_id, @host1.insights.uuid
108
108
  end
109
+
110
+ test 'skips sync when foreman_host is nil' do
111
+ ForemanRhCloud.stubs(:foreman_host).returns(nil)
112
+
113
+ plan = ForemanTasks.sync_task(InventorySync::Async::InventorySelfHostSync)
114
+
115
+ # Task should be stopped (not executed) when host is nil, not failed
116
+ assert_equal 'stopped', plan.state
117
+ end
109
118
  end
@@ -72,4 +72,113 @@ class InsightsClientReportStatusTest < ActiveSupport::TestCase
72
72
 
73
73
  assert_equal HostStatus::Global::ERROR, @host.global_status
74
74
  end
75
+
76
+ test 'host with host_registration_insights parameter set to false gets USER_OMITTED status' do
77
+ @host.host_parameters << HostParameter.create(
78
+ name: 'host_registration_insights',
79
+ value: 'false',
80
+ parameter_type: 'boolean'
81
+ )
82
+ @host.save!
83
+
84
+ insights_status = @host.get_status(InsightsClientReportStatus)
85
+ insights_status.refresh!
86
+
87
+ assert_equal InsightsClientReportStatus::USER_OMITTED, insights_status.status
88
+ assert_equal HostStatus::Global::OK, insights_status.to_global
89
+ end
90
+
91
+ test 'USER_OMITTED status has correct label' do
92
+ insights_status = @host.get_status(InsightsClientReportStatus)
93
+ insights_status.status = InsightsClientReportStatus::USER_OMITTED
94
+ insights_status.save!
95
+
96
+ label = insights_status.to_label
97
+ assert_match(/host_registration_insights/, label)
98
+ assert_match(/false/, label)
99
+ end
100
+
101
+ test 'stale scope excludes USER_OMITTED hosts' do
102
+ host1 = FactoryBot.create(:host, :managed)
103
+ host2 = FactoryBot.create(:host, :managed)
104
+ host3 = FactoryBot.create(:host, :managed)
105
+
106
+ # Host 1: USER_OMITTED with old reported_at (should NOT be in stale scope)
107
+ status1 = host1.get_status(InsightsClientReportStatus)
108
+ status1.status = InsightsClientReportStatus::USER_OMITTED
109
+ status1.reported_at = Time.zone.now - InsightsClientReportStatus::REPORT_INTERVAL - 1.day
110
+ status1.save!
111
+
112
+ # Host 2: REPORTING with old reported_at (should be in stale scope)
113
+ status2 = host2.get_status(InsightsClientReportStatus)
114
+ status2.status = InsightsClientReportStatus::REPORTING
115
+ status2.reported_at = Time.zone.now - InsightsClientReportStatus::REPORT_INTERVAL - 1.day
116
+ status2.save!
117
+
118
+ # Host 3: NO_REPORT with old reported_at (should be in stale scope)
119
+ status3 = host3.get_status(InsightsClientReportStatus)
120
+ status3.status = InsightsClientReportStatus::NO_REPORT
121
+ status3.reported_at = Time.zone.now - InsightsClientReportStatus::REPORT_INTERVAL - 1.day
122
+ status3.save!
123
+
124
+ stale_statuses = InsightsClientReportStatus.stale
125
+ stale_host_ids = stale_statuses.pluck(:host_id)
126
+
127
+ assert_not_includes stale_host_ids, host1.id, 'USER_OMITTED host should not be in stale scope'
128
+ assert_includes stale_host_ids, host2.id, 'REPORTING host with old report should be in stale scope'
129
+ assert_includes stale_host_ids, host3.id, 'NO_REPORT host with old report should be in stale scope'
130
+ end
131
+
132
+ test 'USER_OMITTED status respects parameter inheritance from hostgroup' do
133
+ # Create a hostgroup with parameter = false
134
+ hostgroup = FactoryBot.create(:hostgroup)
135
+ hostgroup.group_parameters << GroupParameter.create(
136
+ name: 'host_registration_insights',
137
+ value: 'false',
138
+ key_type: 'boolean'
139
+ )
140
+ hostgroup.save!
141
+
142
+ @host.hostgroup = hostgroup
143
+ @host.save!
144
+
145
+ # Verify parameter is inherited (not set directly on host)
146
+ assert_nil @host.parameters.find_by(name: 'host_registration_insights'),
147
+ 'Test setup: parameter should not be set directly on host'
148
+
149
+ insights_status = @host.get_status(InsightsClientReportStatus)
150
+ insights_status.refresh!
151
+
152
+ assert_equal InsightsClientReportStatus::USER_OMITTED, insights_status.status,
153
+ 'Status should be USER_OMITTED when host_registration_insights=false is inherited from hostgroup'
154
+ assert_equal HostStatus::Global::OK, insights_status.to_global,
155
+ 'USER_OMITTED status should not affect global status'
156
+ end
157
+
158
+ test 'host parameter overrides inherited parameter from hostgroup' do
159
+ # Create a hostgroup with parameter = false
160
+ hostgroup = FactoryBot.create(:hostgroup)
161
+ hostgroup.group_parameters << GroupParameter.create(
162
+ name: 'host_registration_insights',
163
+ value: 'false',
164
+ key_type: 'boolean'
165
+ )
166
+ hostgroup.save!
167
+
168
+ @host.hostgroup = hostgroup
169
+
170
+ # Override with host parameter = true
171
+ @host.host_parameters << HostParameter.create(
172
+ name: 'host_registration_insights',
173
+ value: 'true',
174
+ parameter_type: 'boolean'
175
+ )
176
+ @host.save!
177
+
178
+ insights_status = @host.get_status(InsightsClientReportStatus)
179
+ insights_status.refresh!
180
+
181
+ assert_not_equal InsightsClientReportStatus::USER_OMITTED, insights_status.status,
182
+ 'Status should not be USER_OMITTED when host parameter overrides hostgroup parameter with true'
183
+ end
75
184
  end
@@ -0,0 +1,85 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module InventorySync
4
+ class InventoryStatusTest < ActiveSupport::TestCase
5
+ setup do
6
+ @host = FactoryBot.create(:host, :managed)
7
+ end
8
+
9
+ test 'status constants are defined correctly' do
10
+ assert_equal 0, InventorySync::InventoryStatus::DISCONNECT
11
+ assert_equal 1, InventorySync::InventoryStatus::SYNC
12
+ assert_equal 2, InventorySync::InventoryStatus::USER_OMITTED
13
+ end
14
+
15
+ test 'to_global returns OK for USER_OMITTED status' do
16
+ status = @host.get_status(InventorySync::InventoryStatus)
17
+ status.status = InventorySync::InventoryStatus::USER_OMITTED
18
+ status.save!
19
+
20
+ assert_equal HostStatus::Global::OK, status.to_global
21
+ end
22
+
23
+ test 'to_global returns WARN for DISCONNECT status' do
24
+ status = @host.get_status(InventorySync::InventoryStatus)
25
+ status.status = InventorySync::InventoryStatus::DISCONNECT
26
+ status.save!
27
+
28
+ assert_equal HostStatus::Global::WARN, status.to_global
29
+ end
30
+
31
+ test 'to_global returns OK for SYNC status' do
32
+ status = @host.get_status(InventorySync::InventoryStatus)
33
+ status.status = InventorySync::InventoryStatus::SYNC
34
+ status.save!
35
+
36
+ assert_equal HostStatus::Global::OK, status.to_global
37
+ end
38
+
39
+ test 'to_label returns appropriate messages for each status' do
40
+ status = @host.get_status(InventorySync::InventoryStatus)
41
+
42
+ # Test DISCONNECT label
43
+ status.status = InventorySync::InventoryStatus::DISCONNECT
44
+ status.save!
45
+ label = status.to_label
46
+ assert_match(/not present/, label.downcase)
47
+ assert_match(/console\.redhat\.com/, label)
48
+
49
+ # Test SYNC label
50
+ status.status = InventorySync::InventoryStatus::SYNC
51
+ status.save!
52
+ label = status.to_label
53
+ assert_match(/uploaded.*present/, label.downcase)
54
+ assert_match(/console\.redhat\.com/, label)
55
+
56
+ # Test USER_OMITTED label
57
+ status.status = InventorySync::InventoryStatus::USER_OMITTED
58
+ status.save!
59
+ label = status.to_label
60
+ assert_match(/excluded/, label.downcase)
61
+ assert_match(/host parameter/, label.downcase)
62
+ assert_match(/console\.redhat\.com/, label)
63
+ end
64
+
65
+ test 'relevant? returns true in regular (non-IoP) mode' do
66
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
67
+
68
+ status = @host.get_status(InventorySync::InventoryStatus)
69
+ status.status = InventorySync::InventoryStatus::SYNC
70
+ status.save!
71
+
72
+ assert status.relevant?, 'Inventory status should be relevant in regular mode'
73
+ end
74
+
75
+ test 'relevant? returns false in IoP mode' do
76
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
77
+
78
+ status = @host.get_status(InventorySync::InventoryStatus)
79
+ status.status = InventorySync::InventoryStatus::SYNC
80
+ status.save!
81
+
82
+ refute status.relevant?, 'Inventory status should NOT be relevant in IoP mode'
83
+ end
84
+ end
85
+ end
@@ -2,8 +2,9 @@ require 'test_plugin_helper'
2
2
 
3
3
  class ForemanRhCloudSelfHostTest < ActiveSupport::TestCase
4
4
  setup do
5
- # reset cached value
6
- ForemanRhCloud.instance_variable_set(:@foreman_host, nil)
5
+ # reset cached value - must remove the variable entirely, not just set to nil
6
+ ForemanRhCloud.remove_instance_variable(:@foreman_host) if ForemanRhCloud.instance_variable_defined?(:@foreman_host)
7
+ ENV.delete('SATELLITE_RH_CLOUD_FOREMAN_HOST')
7
8
  end
8
9
 
9
10
  test 'finds host by fullname' do
@@ -32,4 +33,51 @@ class ForemanRhCloudSelfHostTest < ActiveSupport::TestCase
32
33
 
33
34
  assert_equal @host, actual
34
35
  end
36
+
37
+ test 'returns nil when host does not exist' do
38
+ ForemanRhCloud.expects(:foreman_host_name).returns('nonexistent.example.com')
39
+
40
+ actual = ForemanRhCloud.foreman_host
41
+
42
+ assert_nil actual
43
+ end
44
+
45
+ test 'returns nil and does not query Host when foreman_host_name is nil' do
46
+ ForemanRhCloud.stubs(:foreman_host_name).returns(nil)
47
+ ::Host.unscoped.friendly.expects(:where).never
48
+
49
+ assert_nil ForemanRhCloud.foreman_host
50
+ end
51
+
52
+ test 'caches nil value to avoid repeated lookups' do
53
+ ForemanRhCloud.expects(:foreman_host_name).once.returns('nonexistent.example.com')
54
+
55
+ 2.times { ForemanRhCloud.foreman_host }
56
+ end
57
+
58
+ test 'extracts hostname from foreman_url setting' do
59
+ Setting[:foreman_url] = 'https://satellite.example.com'
60
+
61
+ actual = ForemanRhCloud.foreman_url_hostname
62
+
63
+ assert_equal 'satellite.example.com', actual
64
+ end
65
+
66
+ test 'handles invalid foreman_url gracefully' do
67
+ # Stub Setting to return invalid URL without validation
68
+ Setting.stubs(:[]).with(:foreman_url).returns('not a valid url')
69
+
70
+ actual = ForemanRhCloud.foreman_url_hostname
71
+
72
+ assert_nil actual
73
+ end
74
+
75
+ test 'foreman_host_name uses foreman_url when marked_foreman_host is nil' do
76
+ ForemanRhCloud.expects(:marked_foreman_host).returns(nil)
77
+ ForemanRhCloud.expects(:foreman_url_hostname).returns('satellite.example.com')
78
+
79
+ actual = ForemanRhCloud.foreman_host_name
80
+
81
+ assert_equal 'satellite.example.com', actual
82
+ end
35
83
  end
@@ -2,7 +2,8 @@ require 'test_plugin_helper'
2
2
 
3
3
  class MetadataGeneratorTest < ActiveSupport::TestCase
4
4
  setup do
5
- ForemanRhCloud.instance_variable_set(:@foreman_host, nil)
5
+ # reset cached value - must remove the variable entirely, not just set to nil
6
+ ForemanRhCloud.remove_instance_variable(:@foreman_host) if ForemanRhCloud.instance_variable_defined?(:@foreman_host)
6
7
  end
7
8
 
8
9
  test 'generates an empty report' do
@@ -64,4 +65,26 @@ class MetadataGeneratorTest < ActiveSupport::TestCase
64
65
  assert_not_nil(slice = slices['test_12345'])
65
66
  assert_equal 3, slice['number_hosts']
66
67
  end
68
+
69
+ test 'generates metadata when foreman_host is nil' do
70
+ ForemanRhCloud.stubs(:foreman_host).returns(nil)
71
+ ForemanRhCloud.stubs(:foreman_host_name).returns('satellite.example.com')
72
+
73
+ generator = ForemanInventoryUpload::Generators::Metadata.new
74
+
75
+ # Should not raise an error
76
+ json_str = nil
77
+ assert_nothing_raised do
78
+ json_str = generator.render do
79
+ end
80
+ end
81
+
82
+ # Verify hostname is from foreman_host_name
83
+ actual = JSON.parse(json_str.join("\n"))
84
+ assert_equal 'satellite.example.com', actual['reporting_host_name']
85
+ # Verify IP and BIOS UUID fields are nil when host is nil
86
+ # This is acceptable per SAT-25889 - cloud services don't rely on these fields
87
+ assert_nil actual['reporting_host_ips']
88
+ assert_nil actual['reporting_host_bios_uuid']
89
+ end
67
90
  end