foreman_rh_cloud 14.1.2 → 14.2.0

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +157 -5
  3. data/lib/foreman_inventory_upload/generators/fact_helpers.rb +26 -4
  4. data/lib/foreman_inventory_upload.rb +8 -1
  5. data/lib/foreman_rh_cloud/plugin.rb +16 -5
  6. data/lib/foreman_rh_cloud/version.rb +1 -1
  7. data/lib/foreman_rh_cloud.rb +36 -9
  8. data/lib/insights_cloud/async/insights_generate_notifications.rb +10 -1
  9. data/lib/inventory_sync/async/inventory_self_host_sync.rb +12 -2
  10. data/package.json +1 -1
  11. data/test/jobs/insights_generate_notifications_test.rb +26 -0
  12. data/test/jobs/inventory_self_host_sync_test.rb +9 -0
  13. data/test/unit/foreman_rh_cloud_self_host_test.rb +50 -2
  14. data/test/unit/metadata_generator_test.rb +24 -1
  15. data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +240 -4
  16. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +0 -6
  17. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListReducer.js +0 -2
  18. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/EmptyResults.test.js +10 -9
  19. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +13 -9
  20. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/ErrorState.test.js +20 -9
  21. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +31 -8
  22. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/ListItemStatus.test.js +26 -10
  23. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +33 -9
  24. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListReducer.test.js +53 -35
  25. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +13 -9
  26. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilter.test.js +12 -15
  27. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +32 -12
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +0 -2
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +1 -2
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +13 -7
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +0 -5
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +47 -7
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +23 -9
  34. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButtonSelectors.test.js +19 -17
  35. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +25 -37
  36. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +0 -4
  37. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +18 -47
  38. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +28 -8
  39. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +25 -8
  40. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +11 -9
  41. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +11 -9
  42. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
  43. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +33 -12
  44. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +36 -8
  45. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/InsightsSettingsActions.test.js +61 -47
  46. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +48 -4
  47. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +126 -35
  48. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableSelectors.test.js +90 -24
  49. data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +79 -21
  50. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +31 -6
  51. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.test.js +42 -9
  52. data/webpack/__tests__/ForemanRhCloudHelpers.test.js +91 -53
  53. data/webpack/common/Switcher/__tests__/HelpLabel.test.js +25 -10
  54. data/webpack/common/Switcher/__tests__/SwitcherPF4.test.js +41 -10
  55. metadata +4 -77
  56. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/__snapshots__/EmptyResults.test.js.snap +0 -18
  57. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/__snapshots__/EmptyState.test.js.snap +0 -25
  58. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/__snapshots__/ErrorState.test.js.snap +0 -20
  59. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +0 -47
  60. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +0 -59
  61. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListActions.test.js +0 -34
  62. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListIntegration.test.js +0 -14
  63. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListSelectors.test.js +0 -25
  64. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +0 -49
  65. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +0 -86
  66. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +0 -75
  67. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +0 -46
  68. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/__snapshots__/FileDownload.test.js.snap +0 -26
  69. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterActions.test.js +0 -14
  70. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterReducer.test.js +0 -28
  71. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterSelectors.test.js +0 -21
  72. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilter.test.js.snap +0 -21
  73. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterActions.test.js.snap +0 -17
  74. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterReducer.test.js.snap +0 -19
  75. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterSelectors.test.js.snap +0 -9
  76. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -43
  77. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/AdvancedSettingActions.test.js +0 -9
  78. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/__snapshots__/AdvancedSettingActions.test.js.snap +0 -18
  79. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/InventorySettingsActions.test.js +0 -14
  80. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/__snapshots__/InventorySettingsActions.test.js.snap +0 -26
  81. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +0 -68
  82. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorActions.js +0 -21
  83. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorButton.js +0 -59
  84. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorConstants.js +0 -6
  85. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorSelectors.js +0 -19
  86. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorActions.test.js +0 -9
  87. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +0 -22
  88. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorActions.test.js.snap +0 -11
  89. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorButton.test.js.snap +0 -59
  90. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/index.js +0 -34
  91. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +0 -66
  92. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +0 -18
  93. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/__snapshots__/SettingsWarning.test.js.snap +0 -32
  94. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/index.js +0 -25
  95. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButton.test.js.snap +0 -15
  96. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButtonSelectors.test.js.snap +0 -3
  97. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +0 -58
  98. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/__snapshots__/ScheduledRun.test.js.snap +0 -23
  99. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/__snapshots__/StatusChart.test.js.snap +0 -74
  100. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/__snapshots__/TabContainer.test.js.snap +0 -18
  101. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/__snapshots__/TabFooter.test.js.snap +0 -12
  102. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/__snapshots__/InventoryAutoUpload.test.js.snap +0 -96
  103. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryUpload.test.js +0 -10
  104. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +0 -5
  105. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryUpload.test.js.snap +0 -14
  106. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/InsightsSettingsReducer.test.js +0 -33
  107. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/InsightsSettingsSelectors.test.js +0 -21
  108. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/__snapshots__/InsightsSettingsActions.test.js.snap +0 -65
  109. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/__snapshots__/InsightsSettingsReducer.test.js.snap +0 -19
  110. data/webpack/InsightsCloudSync/Components/InsightsSettings/__tests__/__snapshots__/InsightsSettingsSelectors.test.js.snap +0 -9
  111. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTableActions.test.js.snap +0 -131
  112. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTableSelectors.test.js.snap +0 -87
  113. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +0 -10
  114. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncHelpers.test.js +0 -9
  115. data/webpack/InsightsCloudSync/__tests__/__snapshots__/InsightsCloudSyncActions.test.js.snap +0 -15
  116. data/webpack/InsightsCloudSync/__tests__/__snapshots__/InsightsCloudSyncHelpers.test.js.snap +0 -3
  117. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTabActions.test.js +0 -19
  118. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTabReducer.test.js +0 -26
  119. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTabSelectors.test.js +0 -13
  120. data/webpack/InsightsHostDetailsTab/__tests__/__snapshots__/InsightsTab.test.js.snap +0 -34
  121. data/webpack/InsightsHostDetailsTab/__tests__/__snapshots__/InsightsTabActions.test.js.snap +0 -56
  122. data/webpack/InsightsHostDetailsTab/__tests__/__snapshots__/InsightsTabReducer.test.js.snap +0 -32
  123. data/webpack/InsightsHostDetailsTab/__tests__/__snapshots__/InsightsTabSelectors.test.js.snap +0 -18
  124. data/webpack/__tests__/ForemanRhCloudSelectors.test.js +0 -22
  125. data/webpack/__tests__/ForemanRhCloudTestHelpers.test.js +0 -20
  126. data/webpack/__tests__/__snapshots__/ForemanRhCloudHelpers.test.js.snap +0 -19
  127. data/webpack/__tests__/__snapshots__/ForemanRhCloudSelectors.test.js.snap +0 -25
  128. data/webpack/__tests__/__snapshots__/ForemanRhCloudTestHelpers.test.js.snap +0 -39
  129. data/webpack/common/Switcher/__tests__/__snapshots__/HelpLabel.test.js.snap +0 -16
  130. data/webpack/common/Switcher/__tests__/__snapshots__/SwitcherPF4.test.js.snap +0 -24
@@ -178,6 +178,61 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
178
178
  assert_nil result
179
179
  end
180
180
 
181
+ test 'scope_request? should return tag_name for compliance systems endpoint' do
182
+ get_req = ActionDispatch::Request.new(
183
+ 'REQUEST_URI' => '/api/compliance/v2/systems',
184
+ 'REQUEST_METHOD' => 'GET',
185
+ 'rack.input' => ::Puma::NullIO.new
186
+ )
187
+
188
+ result = @forwarder.send(:scope_request?, get_req, 'api/compliance/v2/systems')
189
+ assert_equal :tags, result
190
+ end
191
+
192
+ test 'scope_request? should return tag_name for compliance report systems endpoint' do
193
+ get_req = ActionDispatch::Request.new(
194
+ 'REQUEST_URI' => '/api/compliance/v2/reports/report-123/systems',
195
+ 'REQUEST_METHOD' => 'GET',
196
+ 'rack.input' => ::Puma::NullIO.new
197
+ )
198
+
199
+ result = @forwarder.send(:scope_request?, get_req, 'api/compliance/v2/reports/report-123/systems')
200
+ assert_equal :tags, result
201
+ end
202
+
203
+ test 'scope_request? should return tag_name for individual compliance system endpoint' do
204
+ get_req = ActionDispatch::Request.new(
205
+ 'REQUEST_URI' => '/api/compliance/v2/systems/system-456',
206
+ 'REQUEST_METHOD' => 'GET',
207
+ 'rack.input' => ::Puma::NullIO.new
208
+ )
209
+
210
+ result = @forwarder.send(:scope_request?, get_req, 'api/compliance/v2/systems/system-456')
211
+ assert_equal :tags, result
212
+ end
213
+
214
+ test 'scope_request? should return tag_name for system policies endpoint' do
215
+ get_req = ActionDispatch::Request.new(
216
+ 'REQUEST_URI' => '/api/compliance/v2/systems/system-456/policies',
217
+ 'REQUEST_METHOD' => 'GET',
218
+ 'rack.input' => ::Puma::NullIO.new
219
+ )
220
+
221
+ result = @forwarder.send(:scope_request?, get_req, 'api/compliance/v2/systems/system-456/policies')
222
+ assert_equal :tags, result
223
+ end
224
+
225
+ test 'scope_request? should return tag_name for system reports endpoint' do
226
+ get_req = ActionDispatch::Request.new(
227
+ 'REQUEST_URI' => '/api/compliance/v2/systems/system-456/reports',
228
+ 'REQUEST_METHOD' => 'GET',
229
+ 'rack.input' => ::Puma::NullIO.new
230
+ )
231
+
232
+ result = @forwarder.send(:scope_request?, get_req, 'api/compliance/v2/systems/system-456/reports')
233
+ assert_equal :tags, result
234
+ end
235
+
181
236
  test 'prepare_tags should use provided tag_name' do
182
237
  result = @forwarder.send(:prepare_tags, @user, @organization, @location, :custom_tag)
183
238
 
@@ -215,7 +270,7 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
215
270
 
216
271
  # Permission enforcement tests
217
272
 
218
- # GET /api/inventory/v1/hosts requires view_vulnerability
273
+ # GET /api/inventory/v1/hosts is a shared dependency - view_compliance OR view_vulnerability grants access
219
274
  test 'should allow GET request to inventory hosts when user has view_vulnerability permission' do
220
275
  user_agent = { :foo => :bar }
221
276
  params = {}
@@ -228,6 +283,7 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
228
283
  'action_dispatch.request.query_parameters' => params
229
284
  )
230
285
 
286
+ @user.stubs(:can?).with(:view_compliance).returns(false)
231
287
  @user.stubs(:can?).with(:view_vulnerability).returns(true)
232
288
  ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag)
233
289
  @forwarder.expects(:execute_cloud_request).returns(true)
@@ -235,7 +291,18 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
235
291
  @forwarder.forward_request(req, 'api/inventory/v1/hosts', 'test_controller', @user, @organization, @location)
236
292
  end
237
293
 
238
- test 'should deny GET request to inventory hosts when user lacks view_vulnerability permission' do
294
+ test 'should allow GET request to inventory hosts when user has view_compliance permission' do
295
+ req = build_request(method: 'GET', uri: '/api/inventory/v1/hosts')
296
+
297
+ @user.stubs(:can?).with(:view_compliance).returns(true)
298
+ @user.stubs(:can?).with(:view_vulnerability).returns(false)
299
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag)
300
+ @forwarder.expects(:execute_cloud_request).returns(true)
301
+
302
+ @forwarder.forward_request(req, 'api/inventory/v1/hosts', 'test_controller', @user, @organization, @location)
303
+ end
304
+
305
+ test 'should deny GET request to inventory hosts when user lacks both view_compliance and view_vulnerability' do
239
306
  user_agent = { :foo => :bar }
240
307
  params = {}
241
308
 
@@ -247,6 +314,7 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
247
314
  'action_dispatch.request.query_parameters' => params
248
315
  )
249
316
 
317
+ @user.stubs(:can?).with(:view_compliance).returns(false)
250
318
  @user.stubs(:can?).with(:view_vulnerability).returns(false)
251
319
 
252
320
  assert_raises(::Foreman::PermissionMissingException) do
@@ -307,9 +375,43 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
307
375
 
308
376
  # Data-driven tests for required_permission_for
309
377
  PERMISSION_MAPPINGS = [
378
+ # Shared inventory hosts endpoint - either view_compliance or view_vulnerability grants GET access
379
+ { path: 'api/inventory/v1/hosts', method: 'GET', expected: [:view_compliance, :view_vulnerability] },
380
+ { path: 'api/inventory/v1/hosts/abc-123', method: 'GET', expected: [:view_compliance, :view_vulnerability] },
381
+ # Compliance endpoints
382
+ { path: 'api/compliance/v2/policies', method: 'GET', expected: :view_compliance },
383
+ { path: 'api/compliance/v2/policies', method: 'POST', expected: :edit_compliance },
384
+ { path: 'api/compliance/v2/policies/policy-123', method: 'GET', expected: :view_compliance },
385
+ { path: 'api/compliance/v2/policies/policy-123', method: 'DELETE', expected: :edit_compliance },
386
+ { path: 'api/compliance/v2/policies/policy-123', method: 'PATCH', expected: :edit_compliance },
387
+ { path: 'api/compliance/v2/policies/policy-123/systems', method: 'GET', expected: :view_compliance },
388
+ { path: 'api/compliance/v2/policies/policy-123/systems', method: 'POST', expected: :edit_compliance },
389
+ { path: 'api/compliance/v2/policies/policy-123/systems/system-456', method: 'GET', expected: :view_compliance },
390
+ { path: 'api/compliance/v2/policies/policy-123/systems/system-456', method: 'DELETE', expected: :edit_compliance },
391
+ { path: 'api/compliance/v2/policies/policy-123/systems/system-456', method: 'PATCH', expected: :edit_compliance },
392
+ { path: 'api/compliance/v2/policies/policy-123/tailorings', method: 'GET', expected: :view_compliance },
393
+ { path: 'api/compliance/v2/policies/policy-123/tailorings', method: 'POST', expected: :edit_compliance },
394
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789', method: 'GET', expected: :view_compliance },
395
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789', method: 'PATCH', expected: :edit_compliance },
396
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules', method: 'GET', expected: :view_compliance },
397
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules', method: 'POST', expected: :edit_compliance },
398
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999', method: 'GET', expected: :view_compliance },
399
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999', method: 'DELETE', expected: :edit_compliance },
400
+ { path: 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999', method: 'PATCH', expected: :edit_compliance },
401
+ { path: 'api/compliance/v2/reports', method: 'GET', expected: :view_compliance },
402
+ { path: 'api/compliance/v2/reports/report-123', method: 'GET', expected: :view_compliance },
403
+ { path: 'api/compliance/v2/reports/report-123', method: 'DELETE', expected: :edit_compliance },
404
+ { path: 'api/compliance/v2/reports/report-123/systems', method: 'GET', expected: :view_compliance },
405
+ { path: 'api/compliance/v2/reports/report-123/systems/system-456', method: 'GET', expected: :view_compliance },
406
+ { path: 'api/compliance/v2/reports/report-123/systems/os_versions', method: 'GET', expected: :view_compliance },
407
+ { path: 'api/compliance/v2/policies/policy-123/systems/os_versions', method: 'GET', expected: :view_compliance },
408
+ { path: 'api/compliance/v2/systems', method: 'GET', expected: :view_compliance },
409
+ { path: 'api/compliance/v2/systems/system-456', method: 'GET', expected: :view_compliance },
410
+ { path: 'api/compliance/v2/systems/os_versions', method: 'GET', expected: :view_compliance },
411
+ { path: 'api/compliance/v2/systems/system-456/policies', method: 'GET', expected: :view_compliance },
412
+ { path: 'api/compliance/v2/systems/system-456/reports', method: 'GET', expected: :view_compliance },
413
+ { path: 'api/compliance/v2/profiles', method: 'GET', expected: :view_compliance },
310
414
  # Vulnerability endpoints
311
- { path: 'api/inventory/v1/hosts', method: 'GET', expected: :view_vulnerability },
312
- { path: 'api/inventory/v1/hosts/abc-123', method: 'GET', expected: :view_vulnerability },
313
415
  { path: 'api/vulnerability/v1/vulnerabilities/cves', method: 'POST', expected: :view_vulnerability },
314
416
  { path: 'api/vulnerability/v1/status', method: 'PATCH', expected: :edit_vulnerability },
315
417
  { path: 'api/vulnerability/v1/cves/status', method: 'PATCH', expected: :edit_vulnerability },
@@ -383,6 +485,140 @@ class UIRequestForwarderTest < ActiveSupport::TestCase
383
485
  end
384
486
  end
385
487
 
488
+ # Compliance permission integration tests
489
+ test 'should allow GET request to compliance policies when user has view_compliance permission' do
490
+ req = build_request(method: 'GET', uri: '/api/compliance/v2/policies')
491
+
492
+ @user.stubs(:can?).with(:view_compliance).returns(true)
493
+ @forwarder.expects(:execute_cloud_request).returns(true)
494
+
495
+ @forwarder.forward_request(req, 'api/compliance/v2/policies', 'test_controller', @user, @organization, @location)
496
+ end
497
+
498
+ test 'should deny GET request to compliance policies when user lacks view_compliance permission' do
499
+ req = build_request(method: 'GET', uri: '/api/compliance/v2/policies')
500
+
501
+ @user.stubs(:can?).with(:view_compliance).returns(false)
502
+
503
+ assert_raises(::Foreman::PermissionMissingException) do
504
+ @forwarder.forward_request(req, 'api/compliance/v2/policies', 'test_controller', @user, @organization, @location)
505
+ end
506
+ end
507
+
508
+ test 'should allow POST request to compliance policies when user has edit_compliance permission' do
509
+ req = build_request(method: 'POST', uri: '/api/compliance/v2/policies', data: '{"name": "test-policy"}')
510
+
511
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
512
+ @forwarder.expects(:execute_cloud_request).returns(true)
513
+
514
+ @forwarder.forward_request(req, 'api/compliance/v2/policies', 'test_controller', @user, @organization, @location)
515
+ end
516
+
517
+ test 'should deny POST request to compliance policies when user lacks edit_compliance permission' do
518
+ req = build_request(method: 'POST', uri: '/api/compliance/v2/policies', data: '{"name": "test-policy"}')
519
+
520
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
521
+
522
+ assert_raises(::Foreman::PermissionMissingException) do
523
+ @forwarder.forward_request(req, 'api/compliance/v2/policies', 'test_controller', @user, @organization, @location)
524
+ end
525
+ end
526
+
527
+ test 'should allow PATCH request to compliance policy when user has edit_compliance permission' do
528
+ req = build_request(method: 'PATCH', uri: '/api/compliance/v2/policies/policy-123', data: '{"name": "updated-policy"}')
529
+
530
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
531
+ @forwarder.expects(:execute_cloud_request).returns(true)
532
+
533
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123', 'test_controller', @user, @organization, @location)
534
+ end
535
+
536
+ test 'should deny PATCH request to compliance policy when user lacks edit_compliance permission' do
537
+ req = build_request(method: 'PATCH', uri: '/api/compliance/v2/policies/policy-123', data: '{"name": "updated-policy"}')
538
+
539
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
540
+
541
+ assert_raises(::Foreman::PermissionMissingException) do
542
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123', 'test_controller', @user, @organization, @location)
543
+ end
544
+ end
545
+
546
+ test 'should allow DELETE request to compliance policy when user has edit_compliance permission' do
547
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/policies/policy-123')
548
+
549
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
550
+ @forwarder.expects(:execute_cloud_request).returns(true)
551
+
552
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123', 'test_controller', @user, @organization, @location)
553
+ end
554
+
555
+ test 'should deny DELETE request to compliance policy when user lacks edit_compliance permission' do
556
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/policies/policy-123')
557
+
558
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
559
+
560
+ assert_raises(::Foreman::PermissionMissingException) do
561
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123', 'test_controller', @user, @organization, @location)
562
+ end
563
+ end
564
+
565
+ test 'should allow POST request to compliance policy systems when user has edit_compliance permission' do
566
+ req = build_request(method: 'POST', uri: '/api/compliance/v2/policies/policy-123/systems', data: '{"id": "system-456"}')
567
+
568
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
569
+ @forwarder.expects(:execute_cloud_request).returns(true)
570
+
571
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123/systems', 'test_controller', @user, @organization, @location)
572
+ end
573
+
574
+ test 'should deny POST request to compliance policy systems when user lacks edit_compliance permission' do
575
+ req = build_request(method: 'POST', uri: '/api/compliance/v2/policies/policy-123/systems', data: '{"id": "system-456"}')
576
+
577
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
578
+
579
+ assert_raises(::Foreman::PermissionMissingException) do
580
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123/systems', 'test_controller', @user, @organization, @location)
581
+ end
582
+ end
583
+
584
+ test 'should allow DELETE request to compliance report when user has edit_compliance permission' do
585
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/reports/report-123')
586
+
587
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
588
+ @forwarder.expects(:execute_cloud_request).returns(true)
589
+
590
+ @forwarder.forward_request(req, 'api/compliance/v2/reports/report-123', 'test_controller', @user, @organization, @location)
591
+ end
592
+
593
+ test 'should deny DELETE request to compliance report when user lacks edit_compliance permission' do
594
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/reports/report-123')
595
+
596
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
597
+
598
+ assert_raises(::Foreman::PermissionMissingException) do
599
+ @forwarder.forward_request(req, 'api/compliance/v2/reports/report-123', 'test_controller', @user, @organization, @location)
600
+ end
601
+ end
602
+
603
+ test 'should allow DELETE request to compliance tailoring rule when user has edit_compliance permission' do
604
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999')
605
+
606
+ @user.stubs(:can?).with(:edit_compliance).returns(true)
607
+ @forwarder.expects(:execute_cloud_request).returns(true)
608
+
609
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999', 'test_controller', @user, @organization, @location)
610
+ end
611
+
612
+ test 'should deny DELETE request to compliance tailoring rule when user lacks edit_compliance permission' do
613
+ req = build_request(method: 'DELETE', uri: '/api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999')
614
+
615
+ @user.stubs(:can?).with(:edit_compliance).returns(false)
616
+
617
+ assert_raises(::Foreman::PermissionMissingException) do
618
+ @forwarder.forward_request(req, 'api/compliance/v2/policies/policy-123/tailorings/tailoring-789/rules/rule-999', 'test_controller', @user, @organization, @location)
619
+ end
620
+ end
621
+
386
622
  # Edge case: anonymous user
387
623
  test 'should deny request when user is nil' do
388
624
  req = build_request(method: 'GET', uri: '/api/insights/v1/stats/systems')
@@ -32,11 +32,6 @@ export const processStatusName = 'upload_report_status';
32
32
 
33
33
  export const filterTerm = 'some_filter';
34
34
 
35
- export const CloudConnectorStatus = {
36
- id: 7,
37
- task: { id: 11 },
38
- };
39
-
40
35
  export const props = {
41
36
  accounts,
42
37
  fetchAccountsStatus: noop,
@@ -47,7 +42,6 @@ export const props = {
47
42
 
48
43
  export const pollingResponse = {
49
44
  accounts,
50
- CloudConnectorStatus,
51
45
  };
52
46
 
53
47
  export const fetchAccountsStatusResponse = {
@@ -20,7 +20,6 @@ export default (state = initialState, action) => {
20
20
  accounts,
21
21
  accountID,
22
22
  processStatusName,
23
- CloudConnectorStatus,
24
23
  } = {},
25
24
  } = action;
26
25
 
@@ -29,7 +28,6 @@ export default (state = initialState, action) => {
29
28
  return state.merge({
30
29
  ...state,
31
30
  accounts,
32
- CloudConnectorStatus,
33
31
  error: null,
34
32
  });
35
33
  case INVENTORY_ACCOUNT_STATUS_POLLING_ERROR:
@@ -1,13 +1,14 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import EmptyResults from '../EmptyResults';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('EmptyResults', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(EmptyResults, fixtures));
6
+ it('renders the empty results message', () => {
7
+ render(<EmptyResults />);
8
+ expect(
9
+ screen.getByText(
10
+ "Oops! Couldn't find organization that matches your query"
11
+ )
12
+ ).toBeTruthy();
13
+ });
13
14
  });
@@ -1,13 +1,17 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import EmptyState from '../EmptyState';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('EmptyState', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(EmptyState, fixtures));
6
+ it('renders fetching data message', () => {
7
+ render(<EmptyState />);
8
+ expect(
9
+ screen.getByText('Fetching data about your accounts')
10
+ ).toBeTruthy();
11
+ });
12
+
13
+ it('renders loading indicator', () => {
14
+ render(<EmptyState />);
15
+ expect(screen.getByText('Loading...')).toBeTruthy();
16
+ });
13
17
  });
@@ -1,13 +1,24 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import ErrorState from '../ErrorState';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('ErrorState', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(ErrorState, fixtures));
6
+ it('renders the error message heading', () => {
7
+ render(<ErrorState error="Something went wrong" />);
8
+ expect(
9
+ screen.getByText(
10
+ 'Encountered an error while trying to access the server:'
11
+ )
12
+ ).toBeTruthy();
13
+ });
14
+
15
+ it('renders the error description', () => {
16
+ render(<ErrorState error="Connection refused" />);
17
+ expect(screen.getByText('Connection refused')).toBeTruthy();
18
+ });
19
+
20
+ it('renders with empty error by default', () => {
21
+ const { container } = render(<ErrorState />);
22
+ expect(container.querySelector('.error_description')).toBeTruthy();
23
+ });
13
24
  });
@@ -1,13 +1,36 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Accordion } from '@patternfly/react-core';
3
4
  import ListItem from '../ListItem';
4
- import { props } from '../ListItem.fixtures';
5
5
 
6
- const fixtures = {
7
- 'render with Props': props,
8
- };
6
+ jest.mock('../../../../Dashboard', () => () => null);
7
+
8
+ const renderListItem = props =>
9
+ render(
10
+ <Accordion>
11
+ <ListItem {...props} />
12
+ </Accordion>
13
+ );
9
14
 
10
15
  describe('ListItem', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(ListItem, fixtures));
16
+ const defaultProps = {
17
+ label: 'test-org',
18
+ account: {
19
+ generated_status: 'unknown',
20
+ uploaded_status: 'unknown',
21
+ id: 1,
22
+ },
23
+ };
24
+
25
+ it('renders the account label', () => {
26
+ renderListItem(defaultProps);
27
+ expect(screen.getByText('test-org')).toBeTruthy();
28
+ });
29
+
30
+ it('renders Generated and Uploaded status labels', () => {
31
+ renderListItem(defaultProps);
32
+ expect(screen.getByText('Generated')).toBeTruthy();
33
+ expect(screen.getByText('Uploaded')).toBeTruthy();
34
+ });
35
+
13
36
  });
@@ -1,14 +1,30 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import ListItemStatus from '../ListItemStatus';
4
- import { props } from '../ListItemStatus.fixtures';
5
-
6
- const fixtures = {
7
- 'render without Props': {},
8
- 'render with Props': props,
9
- };
10
4
 
11
5
  describe('ListItemStatus', () => {
12
- describe('rendering', () =>
13
- testComponentSnapshotsWithFixtures(ListItemStatus, fixtures));
6
+ it('renders Generated and Uploaded labels', () => {
7
+ render(<ListItemStatus />);
8
+ expect(screen.getByText('Generated')).toBeTruthy();
9
+ expect(screen.getByText('Uploaded')).toBeTruthy();
10
+ });
11
+
12
+ it('shows dash placeholders for unknown status', () => {
13
+ render(
14
+ <ListItemStatus
15
+ account={{ generated_status: 'unknown', uploaded_status: 'unknown' }}
16
+ />
17
+ );
18
+ expect(screen.getAllByText('--')).toHaveLength(2);
19
+ });
20
+
21
+ it('renders with provided account statuses', () => {
22
+ render(
23
+ <ListItemStatus
24
+ account={{ generated_status: 'success', uploaded_status: 'running' }}
25
+ />
26
+ );
27
+ expect(screen.getByText('Generated')).toBeTruthy();
28
+ expect(screen.getByText('Uploaded')).toBeTruthy();
29
+ });
14
30
  });
@@ -1,14 +1,38 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import AccountList from '../AccountList';
4
- import { props } from '../AccountList.fixtures';
4
+ import { accounts } from '../AccountList.fixtures';
5
5
 
6
- const fixtures = {
7
- 'render with props': props,
8
- 'show empty results': { ...props, filterTerm: 'not_matching_term' },
9
- };
6
+ jest.mock('../Components/ListItem', () => ({ label }) => (
7
+ <div>{label}</div>
8
+ ));
10
9
 
11
10
  describe('AccountList', () => {
12
- describe('rendering', () =>
13
- testComponentSnapshotsWithFixtures(AccountList, fixtures));
11
+ it('renders account labels', () => {
12
+ render(<AccountList accounts={accounts} />);
13
+ Object.keys(accounts).forEach(label => {
14
+ expect(screen.getByText(label)).toBeTruthy();
15
+ });
16
+ });
17
+
18
+ it('shows empty results when filterTerm matches nothing', () => {
19
+ render(<AccountList accounts={accounts} filterTerm="not_matching_term" />);
20
+ expect(
21
+ screen.getByText(
22
+ "Oops! Couldn't find organization that matches your query"
23
+ )
24
+ ).toBeTruthy();
25
+ });
26
+
27
+ it('shows empty state when accounts are empty', () => {
28
+ render(<AccountList accounts={{}} />);
29
+ expect(
30
+ screen.getByText('Fetching data about your accounts')
31
+ ).toBeTruthy();
32
+ });
33
+
34
+ it('shows error state when error is present', () => {
35
+ render(<AccountList accounts={{}} error="Server error" />);
36
+ expect(screen.getByText('Server error')).toBeTruthy();
37
+ });
14
38
  });
@@ -1,5 +1,3 @@
1
- import { testReducerSnapshotWithFixtures } from '@theforeman/test';
2
-
3
1
  import {
4
2
  INVENTORY_ACCOUNT_STATUS_POLLING,
5
3
  INVENTORY_ACCOUNT_STATUS_POLLING_START,
@@ -14,45 +12,65 @@ import {
14
12
  accountID,
15
13
  processStatusName,
16
14
  pollingResponse,
15
+ accounts,
17
16
  } from '../AccountList.fixtures';
18
17
 
19
- const fixtures = {
20
- 'should return the initial state': {},
21
- 'should handle INVENTORY_ACCOUNT_STATUS_POLLING': {
22
- action: {
18
+ describe('AccountList reducer', () => {
19
+ it('returns the initial state', () => {
20
+ const state = reducer(undefined, { type: '@@INIT' });
21
+ expect(state.accounts).toEqual({});
22
+ expect(state.pollingProcessID).toBe(0);
23
+ expect(state.error).toBeNull();
24
+ });
25
+
26
+ it('handles INVENTORY_ACCOUNT_STATUS_POLLING', () => {
27
+ const state = reducer(undefined, {
23
28
  type: INVENTORY_ACCOUNT_STATUS_POLLING,
24
29
  payload: pollingResponse,
25
- },
26
- },
27
- 'should handle INVENTORY_ACCOUNT_STATUS_POLLING_ERROR': {
28
- action: {
30
+ });
31
+ expect(state.accounts).toEqual(accounts);
32
+ expect(state.error).toBeNull();
33
+ });
34
+
35
+ it('handles INVENTORY_ACCOUNT_STATUS_POLLING_ERROR', () => {
36
+ const state = reducer(undefined, {
29
37
  type: INVENTORY_ACCOUNT_STATUS_POLLING_ERROR,
30
38
  payload: { error },
31
- },
32
- },
33
- 'should handle INVENTORY_ACCOUNT_STATUS_POLLING_START': {
34
- action: {
39
+ });
40
+ expect(state.accounts).toEqual({});
41
+ expect(state.error).toBe(error);
42
+ });
43
+
44
+ it('handles INVENTORY_ACCOUNT_STATUS_POLLING_START', () => {
45
+ const state = reducer(undefined, {
46
+ type: INVENTORY_ACCOUNT_STATUS_POLLING_START,
47
+ payload: { pollingProcessID },
48
+ });
49
+ expect(state.pollingProcessID).toBe(pollingProcessID);
50
+ });
51
+
52
+ it('handles INVENTORY_ACCOUNT_STATUS_POLLING_STOP (default case)', () => {
53
+ const prevState = reducer(undefined, {
35
54
  type: INVENTORY_ACCOUNT_STATUS_POLLING_START,
36
- payload: {
37
- pollingProcessID,
38
- },
39
- },
40
- },
41
- 'should handle INVENTORY_ACCOUNT_STATUS_POLLING_STOP': {
42
- action: {
55
+ payload: { pollingProcessID },
56
+ });
57
+ const state = reducer(prevState, {
43
58
  type: INVENTORY_ACCOUNT_STATUS_POLLING_STOP,
44
- },
45
- },
46
- 'should handle INVENTORY_PROCESS_RESTART': {
47
- action: {
48
- type: INVENTORY_PROCESS_RESTART,
49
- payload: {
50
- accountID,
51
- processStatusName,
52
- },
53
- },
54
- },
55
- };
59
+ });
60
+ expect(state.pollingProcessID).toBe(pollingProcessID);
61
+ expect(state.accounts).toEqual({});
62
+ expect(state.error).toBeNull();
63
+ });
56
64
 
57
- describe('AccountList reducer', () =>
58
- testReducerSnapshotWithFixtures(reducer, fixtures));
65
+ it('handles INVENTORY_PROCESS_RESTART', () => {
66
+ const stateWithAccounts = reducer(undefined, {
67
+ type: INVENTORY_ACCOUNT_STATUS_POLLING,
68
+ payload: pollingResponse,
69
+ });
70
+ const state = reducer(stateWithAccounts, {
71
+ type: INVENTORY_PROCESS_RESTART,
72
+ payload: { accountID, processStatusName },
73
+ });
74
+ expect(state.accounts[accountID][processStatusName]).toBe('Restarting...');
75
+ });
76
+ });