foreman_rh_cloud 14.0.3 → 14.1.1

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 (36) 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/foreman_inventory_upload/accounts_controller.rb +1 -1
  5. data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +2 -2
  6. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +9 -2
  7. data/app/models/concerns/rh_cloud_host.rb +35 -3
  8. data/app/models/insights_client_report_status.rb +9 -1
  9. data/app/models/inventory_sync/inventory_status.rb +16 -4
  10. data/lib/foreman_inventory_upload/async/create_missing_insights_facets.rb +8 -2
  11. data/lib/foreman_rh_cloud/engine.rb +1 -0
  12. data/lib/foreman_rh_cloud/version.rb +1 -1
  13. data/lib/insights_cloud/async/vmaas_reposcan_sync.rb +23 -8
  14. data/lib/inventory_sync/async/inventory_full_sync.rb +39 -3
  15. data/package.json +1 -1
  16. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +40 -0
  17. data/test/controllers/insights_cloud/candlepin_proxies_extensions_test.rb +70 -0
  18. data/test/jobs/insights_client_status_aging_test.rb +40 -0
  19. data/test/jobs/inventory_full_sync_test.rb +212 -0
  20. data/test/models/insights_client_report_status_test.rb +109 -0
  21. data/test/models/inventory_sync/inventory_status_test.rb +85 -0
  22. data/test/unit/lib/insights_cloud/async/vmaas_reposcan_sync_test.rb +80 -25
  23. data/test/unit/rh_cloud_host_test.rb +214 -0
  24. data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +30 -3
  25. data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +112 -10
  26. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js +8 -2
  27. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +1 -0
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +1 -0
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js +43 -17
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/__tests__/Toast.test.js +82 -0
  31. data/webpack/ForemanRhCloudHelpers.js +22 -0
  32. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +7 -4
  33. data/webpack/InsightsCloudSync/Components/InsightsTable/table.scss +9 -0
  34. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +53 -12
  35. data/webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js +134 -22
  36. metadata +7 -1
@@ -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')
@@ -310,4 +311,215 @@ class InventoryFullSyncTest < ActiveSupport::TestCase
310
311
 
311
312
  assert_nil InventorySync::InventoryStatus.where(host_id: @host3.id).first
312
313
  end
314
+
315
+ test 'user-omitted hosts get USER_OMITTED status' do
316
+ # Add parameter to exclude host1
317
+ @host1.host_parameters << HostParameter.create(
318
+ name: 'host_registration_insights_inventory',
319
+ value: 'false',
320
+ parameter_type: 'boolean'
321
+ )
322
+ @host1.save!
323
+
324
+ setup_certs_expectation do
325
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
326
+ end
327
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
328
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
329
+
330
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
331
+ run_action(action)
332
+
333
+ @host1.reload
334
+ @host2.reload
335
+
336
+ # Host1 should be USER_OMITTED
337
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
338
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
339
+ 'Host with host_registration_insights_inventory=false should have USER_OMITTED status'
340
+
341
+ # Host2 should be SYNC
342
+ assert_equal InventorySync::InventoryStatus::SYNC,
343
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
344
+ 'Normal host should have SYNC status'
345
+ end
346
+
347
+ test 'user-omitted hosts are not marked as disconnected' do
348
+ # Add parameter to exclude host1
349
+ @host1.host_parameters << HostParameter.create(
350
+ name: 'host_registration_insights_inventory',
351
+ value: 'false',
352
+ parameter_type: 'boolean'
353
+ )
354
+ @host1.save!
355
+
356
+ # Host1 is not in the cloud inventory response
357
+ setup_certs_expectation do
358
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
359
+ end
360
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
361
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id])
362
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
363
+
364
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
365
+ run_action(action)
366
+
367
+ @host1.reload
368
+
369
+ # Host1 should be USER_OMITTED, not DISCONNECT
370
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
371
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
372
+ 'User-omitted host should have USER_OMITTED status even if not in cloud inventory'
373
+ end
374
+
375
+ test 'host_statuses output includes all three counts' do
376
+ # Create a mix of hosts with different statuses
377
+ # Host1: will be user-omitted
378
+ @host1.host_parameters << HostParameter.create(
379
+ name: 'host_registration_insights_inventory',
380
+ value: 'false',
381
+ parameter_type: 'boolean'
382
+ )
383
+ @host1.save!
384
+
385
+ # Host2: will be synced (in cloud inventory)
386
+ # Host3: will be disconnected (not in cloud inventory)
387
+
388
+ # Create inventory response with only host2 (exclude host1 and host3)
389
+ inventory_with_host2_only = @inventory.dup
390
+ inventory_with_host2_only['results'] = [@inventory['results'][0]] # Only host2
391
+ inventory_with_host2_only['total'] = 1
392
+ inventory_with_host2_only['count'] = 1
393
+
394
+ setup_certs_expectation do
395
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
396
+ end
397
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(inventory_with_host2_only)
398
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id, @host3.id])
399
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
400
+
401
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
402
+ run_action(action)
403
+
404
+ # Verify the statuses were actually set correctly
405
+ @host1.reload
406
+ @host2.reload
407
+ @host3.reload
408
+
409
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED,
410
+ InventorySync::InventoryStatus.where(host_id: @host1.id).first.status,
411
+ 'Host with host_registration_insights_inventory=false should have USER_OMITTED status'
412
+ assert_equal InventorySync::InventoryStatus::SYNC,
413
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
414
+ 'Host in cloud inventory should have SYNC status'
415
+ assert_equal InventorySync::InventoryStatus::DISCONNECT,
416
+ InventorySync::InventoryStatus.where(host_id: @host3.id).first.status,
417
+ 'Host not in cloud inventory and not user-omitted should have DISCONNECT status'
418
+ end
419
+
420
+ test 'user-omitted status respects parameter inheritance from hostgroup' do
421
+ # Create a hostgroup and assign it to host1
422
+ hostgroup = FactoryBot.create(:hostgroup)
423
+ hostgroup.organizations << @host1.organization
424
+ @host1.hostgroup = hostgroup
425
+ @host1.save!
426
+
427
+ # Set parameter at hostgroup level, not directly on host
428
+ # This verifies the fix that uses search_for instead of querying HostParameter directly
429
+ hostgroup.group_parameters << GroupParameter.create(
430
+ name: 'host_registration_insights_inventory',
431
+ value: 'false',
432
+ key_type: 'boolean'
433
+ )
434
+ hostgroup.save!
435
+
436
+ # Verify parameter is inherited (not set directly on host)
437
+ assert_nil @host1.parameters.find_by(name: 'host_registration_insights_inventory'),
438
+ 'Test setup: parameter should not be set directly on host'
439
+ refute ::Foreman::Cast.to_bool(@host1.host_param('host_registration_insights_inventory')),
440
+ 'Test setup: parameter should be inherited from hostgroup'
441
+
442
+ # Host2 remains normal (no parameter)
443
+ setup_certs_expectation do
444
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
445
+ end
446
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory)
447
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id])
448
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
449
+
450
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
451
+ run_action(action)
452
+
453
+ @host1.reload
454
+ @host2.reload
455
+
456
+ # Host1 should be USER_OMITTED (inherited parameter from hostgroup)
457
+ host1_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
458
+ assert_not_nil host1_status, 'Host1 should have an inventory status'
459
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, host1_status.status,
460
+ 'Host with inherited host_registration_insights_inventory=false should have USER_OMITTED status'
461
+
462
+ # Host2 should be SYNC
463
+ assert_equal InventorySync::InventoryStatus::SYNC,
464
+ InventorySync::InventoryStatus.where(host_id: @host2.id).first.status,
465
+ 'Normal host should have SYNC status'
466
+ end
467
+
468
+ test 'user-omitted statuses are cleared before re-creating them to avoid silent create failures' do
469
+ # First sync: host1 is user-omitted
470
+ @host1.host_parameters << HostParameter.create(
471
+ name: 'host_registration_insights_inventory',
472
+ value: 'false',
473
+ parameter_type: 'boolean'
474
+ )
475
+ @host1.save!
476
+
477
+ setup_certs_expectation do
478
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
479
+ end
480
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:query_inventory).returns(@inventory).twice
481
+ InventorySync::Async::InventoryFullSync.any_instance.expects(:affected_host_ids).returns([@host1.id, @host2.id]).twice
482
+ FactoryBot.create(:fact_value, fact_name: fact_names['virt::uuid'], value: '1234', host: @host2)
483
+
484
+ # Run first sync
485
+ action = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
486
+ run_action(action)
487
+
488
+ @host1.reload
489
+ initial_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
490
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, initial_status.status,
491
+ 'Initial sync: host should be USER_OMITTED'
492
+ initial_status_id = initial_status.id
493
+
494
+ # Verify there's only one status record for host1
495
+ assert_equal 1, InventorySync::InventoryStatus.where(host_id: @host1.id).count,
496
+ 'Should have exactly one status record after first sync'
497
+
498
+ # Run second sync (parameter still false)
499
+ # Without clearing old user_omitted statuses, the .create would silently fail
500
+ # with 'Host has already been taken' due to uniqueness constraint
501
+ setup_certs_expectation do
502
+ InventorySync::Async::InventoryFullSync.any_instance.stubs(:candlepin_id_cert)
503
+ end
504
+
505
+ action2 = create_and_plan_action(InventorySync::Async::InventoryFullSync, @host1.organization)
506
+ run_action(action2)
507
+
508
+ @host1.reload
509
+ final_status = InventorySync::InventoryStatus.where(host_id: @host1.id).first
510
+
511
+ # Verify status is still USER_OMITTED (not stale from first sync)
512
+ assert_equal InventorySync::InventoryStatus::USER_OMITTED, final_status.status,
513
+ 'Status should be USER_OMITTED after second sync'
514
+
515
+ # Verify there's still only one status record (old one was deleted before creating new one)
516
+ assert_equal 1, InventorySync::InventoryStatus.where(host_id: @host1.id).count,
517
+ 'Should have exactly one status record (old cleared before new created)'
518
+
519
+ # Verify the old status record was actually deleted and replaced with a new one
520
+ refute InventorySync::InventoryStatus.exists?(initial_status_id),
521
+ 'Old status record should have been deleted before creating new one'
522
+ assert_not_equal initial_status_id, final_status.id,
523
+ 'New status record should have different ID, proving old was deleted'
524
+ end
313
525
  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
@@ -5,19 +5,17 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
5
5
  include ForemanTasks::TestHelpers::WithInThreadExecutor
6
6
 
7
7
  setup do
8
- @root = FactoryBot.build(:katello_root_repository, :fedora_17_x86_64_dev_root)
9
- @root.save(validate: false)
10
- @repo = FactoryBot.create(
11
- :katello_repository,
12
- :with_product,
13
- distribution_family: 'Red Hat',
14
- distribution_version: '7.5',
15
- root: @root
16
- )
8
+ @organization = FactoryBot.create(:organization)
9
+ # Create a simple repository - we only need id and organization_id for the action
10
+ @repo = ::Katello::Repository.new(id: 1)
11
+ @repo.stubs(:organization_id).returns(@organization.id)
12
+ ::Katello::Repository.stubs(:find).with(1).returns(@repo)
13
+
17
14
  @repo_payload = { id: @repo.id }
18
15
  @expected_url = 'https://example.com/api/v1/vmaas/reposcan/sync'
19
16
  InsightsCloud.stubs(:vmaas_reposcan_sync_url).returns(@expected_url)
20
17
  ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
18
+ Organization.stubs(:find).with(@organization.id).returns(@organization)
21
19
  end
22
20
 
23
21
  teardown do
@@ -83,7 +81,7 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
83
81
  params[:url] == @expected_url &&
84
82
  params[:headers].is_a?(Hash) &&
85
83
  params[:headers]['Content-Type'] == 'application/json' &&
86
- params[:organization] == @repo.organization
84
+ params[:organization] == @organization
87
85
  end
88
86
  .returns(mock_response)
89
87
 
@@ -116,37 +114,94 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
116
114
  .stubs(:execute_cloud_request)
117
115
  .raises(exception)
118
116
 
119
- error = assert_raises(ForemanTasks::TaskError) do
120
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
121
- end
117
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
122
118
 
123
- assert_equal 'VMaaS reposcan sync failed: 500 - Server Error', error.task.output[:message]
119
+ assert_equal 'VMaaS reposcan sync failed: 500 - Server Error', task.output[:message]
124
120
  end
125
121
 
126
122
  test 'run sets error message in task output for StandardError exception' do
123
+ mock_logger = mock('logger')
124
+ mock_logger.expects(:error).with('Error triggering VMaaS reposcan sync: Network timeout')
125
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
126
+
127
127
  InsightsCloud::Async::VmaasReposcanSync.any_instance
128
128
  .stubs(:execute_cloud_request)
129
129
  .raises(StandardError.new('Network timeout'))
130
130
 
131
- error = assert_raises(ForemanTasks::TaskError) do
132
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
133
- end
131
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
134
132
 
135
- # The task is available via main_action
136
- assert_match(/Error triggering VMaaS reposcan sync: Network timeout, response: /,
137
- error.task.main_action.output[:message])
133
+ assert_equal 'Error triggering VMaaS reposcan sync: Network timeout', task.output[:message]
138
134
  end
139
135
 
140
- test 'run logs and re-raises when cloud request returns error response' do
141
- error_response = mock('error_response', code: 500, body: 'error')
136
+ test 'run logs and handles error response without raising' do
137
+ error_response = mock('error_response')
138
+ error_response.stubs(:code).returns(500)
139
+ error_response.stubs(:body).returns('error')
142
140
  exception = RestClient::ExceptionWithResponse.new(error_response)
143
141
 
144
142
  InsightsCloud::Async::VmaasReposcanSync.any_instance
145
143
  .stubs(:execute_cloud_request)
146
144
  .raises(exception)
147
145
 
148
- assert_raises(ForemanTasks::TaskError) do
149
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
150
- end
146
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
147
+
148
+ assert_equal 'VMaaS reposcan sync failed: 500 - error', task.output[:message]
149
+ end
150
+
151
+ test 'run handles 429 error with warning log level' do
152
+ error_response = mock('error_response')
153
+ error_response.stubs(:code).returns(429)
154
+ error_response.stubs(:body).returns('{"msg": "Another task already in progress"}')
155
+ exception = RestClient::ExceptionWithResponse.new(error_response)
156
+
157
+ mock_logger = mock('logger')
158
+ mock_logger.expects(:warn).with('VMaaS reposcan sync skipped: another sync already in progress (429)')
159
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
160
+
161
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
162
+ .stubs(:execute_cloud_request)
163
+ .raises(exception)
164
+
165
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
166
+
167
+ assert_equal 'VMaaS reposcan sync skipped: another sync already in progress (429)',
168
+ task.output[:message]
169
+ end
170
+
171
+ test 'run handles non-429 errors with error log level' do
172
+ error_response = mock('error_response')
173
+ error_response.stubs(:code).returns(500)
174
+ error_response.stubs(:body).returns('Internal Server Error')
175
+ exception = RestClient::ExceptionWithResponse.new(error_response)
176
+
177
+ mock_logger = mock('logger')
178
+ mock_logger.expects(:error).with('VMaaS reposcan sync failed: 500 - Internal Server Error')
179
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
180
+
181
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
182
+ .stubs(:execute_cloud_request)
183
+ .raises(exception)
184
+
185
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
186
+
187
+ assert_equal 'VMaaS reposcan sync failed: 500 - Internal Server Error',
188
+ task.output[:message]
189
+ end
190
+
191
+ test 'run handles RestClient::ExceptionWithResponse with nil response' do
192
+ exception = RestClient::ExceptionWithResponse.new(nil)
193
+
194
+ mock_logger = mock('logger')
195
+ mock_logger.expects(:error).with('VMaaS reposcan sync failed: - ')
196
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
197
+
198
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
199
+ .stubs(:execute_cloud_request)
200
+ .raises(exception)
201
+
202
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
203
+
204
+ refute_nil task.output[:message]
205
+ assert_equal 'VMaaS reposcan sync failed: - ', task.output[:message]
151
206
  end
152
207
  end