foreman_rh_cloud 13.2.9 → 13.2.11

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +4 -8
  3. data/app/controllers/insights_cloud/ui_requests_controller.rb +3 -7
  4. data/app/models/concerns/rh_cloud_host.rb +36 -1
  5. data/app/services/foreman_rh_cloud/url_remediations_retriever.rb +19 -5
  6. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +2 -0
  7. data/lib/foreman_inventory_upload/generators/fact_helpers.rb +26 -4
  8. data/lib/foreman_inventory_upload.rb +8 -1
  9. data/lib/foreman_rh_cloud/version.rb +1 -1
  10. data/lib/foreman_rh_cloud.rb +36 -9
  11. data/lib/insights_cloud/async/insights_generate_notifications.rb +10 -1
  12. data/lib/inventory_sync/async/inventory_self_host_sync.rb +12 -2
  13. data/package.json +1 -1
  14. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +16 -2
  15. data/test/controllers/insights_cloud/ui_requests_controller_test.rb +16 -2
  16. data/test/jobs/insights_generate_notifications_test.rb +26 -0
  17. data/test/jobs/inventory_self_host_sync_test.rb +9 -0
  18. data/test/jobs/upload_report_direct_job_test.rb +29 -0
  19. data/test/unit/foreman_rh_cloud_self_host_test.rb +50 -2
  20. data/test/unit/insights_facet_test.rb +5 -0
  21. data/test/unit/metadata_generator_test.rb +24 -1
  22. data/test/unit/rh_cloud_host_test.rb +68 -0
  23. data/test/unit/services/foreman_rh_cloud/url_remediations_retriever_test.rb +82 -5
  24. data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +24 -2
  25. data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +73 -10
  26. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/EmptyResults.test.js +10 -9
  27. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +13 -9
  28. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/ErrorState.test.js +20 -9
  29. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +31 -8
  30. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/ListItemStatus.test.js +26 -10
  31. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +33 -9
  32. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListReducer.test.js +55 -35
  33. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +13 -9
  34. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilter.test.js +12 -15
  35. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +32 -12
  36. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +15 -7
  37. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +47 -18
  38. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +58 -15
  39. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +23 -9
  40. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButtonSelectors.test.js +19 -17
  41. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +25 -37
  42. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +28 -8
  43. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +25 -8
  44. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +11 -9
  45. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +11 -9
  46. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
  47. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +33 -12
  48. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +36 -8
  49. data/webpack/ForemanRhCloudHelpers.js +22 -0
  50. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +27 -1
  51. data/webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js +134 -22
  52. metadata +10 -43
  53. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/__snapshots__/EmptyResults.test.js.snap +0 -18
  54. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/__snapshots__/EmptyState.test.js.snap +0 -25
  55. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/__snapshots__/ErrorState.test.js.snap +0 -20
  56. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +0 -47
  57. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +0 -59
  58. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListActions.test.js +0 -34
  59. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListIntegration.test.js +0 -14
  60. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListSelectors.test.js +0 -25
  61. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +0 -49
  62. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +0 -86
  63. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +0 -75
  64. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +0 -46
  65. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/__snapshots__/FileDownload.test.js.snap +0 -26
  66. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterActions.test.js +0 -14
  67. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterReducer.test.js +0 -28
  68. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterSelectors.test.js +0 -21
  69. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilter.test.js.snap +0 -21
  70. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterActions.test.js.snap +0 -17
  71. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterReducer.test.js.snap +0 -19
  72. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterSelectors.test.js.snap +0 -9
  73. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -43
  74. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/AdvancedSettingActions.test.js +0 -9
  75. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/__snapshots__/AdvancedSettingActions.test.js.snap +0 -18
  76. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/InventorySettingsActions.test.js +0 -14
  77. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/__snapshots__/InventorySettingsActions.test.js.snap +0 -26
  78. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +0 -68
  79. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorActions.test.js +0 -9
  80. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorActions.test.js.snap +0 -11
  81. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorButton.test.js.snap +0 -59
  82. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/__snapshots__/SettingsWarning.test.js.snap +0 -32
  83. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButton.test.js.snap +0 -15
  84. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButtonSelectors.test.js.snap +0 -3
  85. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +0 -58
  86. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/__snapshots__/ScheduledRun.test.js.snap +0 -23
  87. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/__snapshots__/StatusChart.test.js.snap +0 -74
  88. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/__snapshots__/TabContainer.test.js.snap +0 -18
  89. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/__snapshots__/TabFooter.test.js.snap +0 -12
  90. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/__snapshots__/InventoryAutoUpload.test.js.snap +0 -96
  91. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryUpload.test.js +0 -10
  92. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +0 -5
  93. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryUpload.test.js.snap +0 -14
@@ -343,6 +343,74 @@ class RhCloudHostTest < ActiveSupport::TestCase
343
343
  end
344
344
  end
345
345
 
346
+ context 'scoped search on insights_recommendations_count' do
347
+ setup do
348
+ @org = FactoryBot.create(:organization)
349
+ end
350
+
351
+ teardown do
352
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
353
+ end
354
+
355
+ test 'searches insights_facets.hits_count in non-IoP mode' do
356
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
357
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
358
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
359
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
360
+
361
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
362
+
363
+ assert_includes results, host_with_hits
364
+ assert_not_includes results, host_without_hits
365
+ end
366
+
367
+ test 'supports greater-than operator in non-IoP mode' do
368
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
369
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
370
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
371
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
372
+
373
+ results = Host::Managed.search_for('insights_recommendations_count > 0')
374
+
375
+ assert_includes results, host_with_hits
376
+ assert_not_includes results, host_without_hits
377
+ end
378
+
379
+ test 'raises QueryNotSupported in IoP mode' do
380
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
381
+
382
+ error = assert_raises(ScopedSearch::QueryNotSupported) do
383
+ Host::Managed.search_for('insights_recommendations_count = 1')
384
+ end
385
+
386
+ assert_match(/not available in IoP mode/, error.message)
387
+ end
388
+
389
+ test 'supports IN operator in non-IoP mode' do
390
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
391
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
392
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
393
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
394
+
395
+ results = Host::Managed.search_for('insights_recommendations_count ^ (1,2,3)')
396
+
397
+ assert_includes results, host_with_hits
398
+ assert_not_includes results, host_without_hits
399
+ end
400
+
401
+ test 'handles hosts without insights facets in non-IoP mode' do
402
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
403
+ host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
404
+ host_with_facet = FactoryBot.create(:host, :with_insights_hits, organization: @org)
405
+ InsightsFacet.reset_counters(host_with_facet.insights.id, :hits_count)
406
+
407
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
408
+
409
+ assert_includes results, host_with_facet
410
+ assert_not_includes results, host_without_facet
411
+ end
412
+ end
413
+
346
414
  test 'scoped search for user_omitted inventory status works' do
347
415
  host1 = FactoryBot.create(:host, :managed)
348
416
  host2 = FactoryBot.create(:host, :managed)
@@ -1,8 +1,8 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class URLRemediationsRetrieverTest < ActiveSupport::TestCase
4
- test 'Calls the given url' do
5
- retreiver = ForemanRhCloud::URLRemediationsRetriever.new(
4
+ test 'Calls the given url with GET when no hosts in URL' do
5
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
6
6
  organization_id: FactoryBot.create(:organization).id,
7
7
  url: 'http://test.example.com',
8
8
  payload: 'TEST_PAYLOAD',
@@ -11,19 +11,96 @@ class URLRemediationsRetrieverTest < ActiveSupport::TestCase
11
11
  }
12
12
  )
13
13
 
14
- retreiver.stubs(:cert_auth_available?).returns(true)
14
+ retriever.stubs(:cert_auth_available?).returns(true)
15
15
 
16
16
  response = mock('response')
17
17
  response.stubs(:body).returns('TEST_RESPONSE')
18
- retreiver.expects(:execute_cloud_request).with do |params|
18
+ retriever.expects(:execute_cloud_request).with do |params|
19
19
  params[:method] == :get &&
20
20
  params[:url] == 'http://test.example.com' &&
21
21
  params[:headers][:custom1] == 'TEST_HEADER' &&
22
22
  params[:payload] == "\"TEST_PAYLOAD\""
23
23
  end.returns(response)
24
24
 
25
- actual = retreiver.create_playbook
25
+ actual = retriever.create_playbook
26
26
 
27
27
  assert_equal 'TEST_RESPONSE', actual
28
28
  end
29
+
30
+ test 'Uses POST with hosts in body when URL contains hosts query param' do
31
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
32
+ organization_id: FactoryBot.create(:organization).id,
33
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1,uuid-2,uuid-3'
34
+ )
35
+
36
+ retriever.stubs(:cert_auth_available?).returns(true)
37
+
38
+ response = mock('response')
39
+ response.stubs(:body).returns('TEST_PLAYBOOK')
40
+ retriever.expects(:execute_cloud_request).with do |params|
41
+ params[:method] == :post &&
42
+ params[:url] == 'http://test.example.com/api/remediations/1234/playbook' &&
43
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2', 'uuid-3']
44
+ end.returns(response)
45
+
46
+ actual = retriever.create_playbook
47
+
48
+ assert_equal 'TEST_PLAYBOOK', actual
49
+ end
50
+
51
+ test 'Preserves other query params when extracting hosts' do
52
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
53
+ organization_id: FactoryBot.create(:organization).id,
54
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1,uuid-2&localhost=false'
55
+ )
56
+
57
+ retriever.stubs(:cert_auth_available?).returns(true)
58
+
59
+ response = mock('response')
60
+ response.stubs(:body).returns('TEST_PLAYBOOK')
61
+ retriever.expects(:execute_cloud_request).with do |params|
62
+ params[:method] == :post &&
63
+ params[:url].include?('localhost=false') &&
64
+ !params[:url].include?('hosts=') &&
65
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2']
66
+ end.returns(response)
67
+
68
+ retriever.create_playbook
69
+ end
70
+
71
+ test 'Merges multiple hosts query params into a single array' do
72
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
73
+ organization_id: FactoryBot.create(:organization).id,
74
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1&hosts=uuid-2'
75
+ )
76
+
77
+ retriever.stubs(:cert_auth_available?).returns(true)
78
+
79
+ response = mock('response')
80
+ response.stubs(:body).returns('TEST_PLAYBOOK')
81
+ retriever.expects(:execute_cloud_request).with do |params|
82
+ params[:method] == :post &&
83
+ params[:url] == 'http://test.example.com/api/remediations/1234/playbook' &&
84
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2']
85
+ end.returns(response)
86
+
87
+ retriever.create_playbook
88
+ end
89
+
90
+ test 'Falls back to GET when hosts query param is empty' do
91
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
92
+ organization_id: FactoryBot.create(:organization).id,
93
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts='
94
+ )
95
+
96
+ retriever.stubs(:cert_auth_available?).returns(true)
97
+
98
+ response = mock('response')
99
+ response.stubs(:body).returns('TEST_PLAYBOOK')
100
+ retriever.expects(:execute_cloud_request).with do |params|
101
+ params[:method] == :get
102
+ end.returns(response)
103
+
104
+ retriever.create_playbook
105
+ end
29
106
  end
@@ -3,6 +3,10 @@ import PropTypes from 'prop-types';
3
3
  import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
4
4
  import { createProviderOptions } from '../common/ScalprumModule/ScalprumContext';
5
5
  import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
6
+ import {
7
+ vulnerabilityDisabled,
8
+ useTabRedirect,
9
+ } from '../ForemanRhCloudHelpers';
6
10
  import './CVEsHostDetailsTab.scss';
7
11
 
8
12
  const CVEsHostDetailsTab = ({ systemId }) => {
@@ -26,6 +30,15 @@ CVEsHostDetailsTab.propTypes = {
26
30
 
27
31
  const CVEsHostDetailsTabWrapper = ({ response }) => {
28
32
  const permissions = useInsightsPermissions();
33
+ const isHostDataLoaded = Boolean(response?.id);
34
+ const shouldHideTab = useTabRedirect(
35
+ isHostDataLoaded && vulnerabilityDisabled({ hostDetails: response })
36
+ );
37
+
38
+ if (shouldHideTab) {
39
+ return null;
40
+ }
41
+
29
42
  return (
30
43
  <ScalprumProvider {...createProviderOptions(permissions)}>
31
44
  <CVEsHostDetailsTab
@@ -38,10 +51,19 @@ const CVEsHostDetailsTabWrapper = ({ response }) => {
38
51
 
39
52
  CVEsHostDetailsTabWrapper.propTypes = {
40
53
  response: PropTypes.shape({
54
+ id: PropTypes.number,
55
+ operatingsystem_name: PropTypes.string,
56
+ vulnerability: PropTypes.shape({
57
+ enabled: PropTypes.bool,
58
+ }),
41
59
  subscription_facet_attributes: PropTypes.shape({
42
- uuid: PropTypes.string.isRequired,
60
+ uuid: PropTypes.string,
43
61
  }),
44
- }).isRequired,
62
+ }),
63
+ };
64
+
65
+ CVEsHostDetailsTabWrapper.defaultProps = {
66
+ response: {},
45
67
  };
46
68
 
47
69
  export default CVEsHostDetailsTabWrapper;
@@ -1,6 +1,17 @@
1
1
  import React from 'react';
2
2
  import { render } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
3
4
  import CVEsHostDetailsTabWrapper from '../CVEsHostDetailsTab';
5
+ import { OVERVIEW_TAB_PATH } from '../../ForemanRhCloudHelpers';
6
+
7
+ const mockHistoryReplace = jest.fn();
8
+
9
+ jest.mock('react-router-dom', () => ({
10
+ ...jest.requireActual('react-router-dom'),
11
+ useHistory: () => ({
12
+ replace: mockHistoryReplace,
13
+ }),
14
+ }));
4
15
 
5
16
  jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
6
17
  useForemanContext: () => ({
@@ -25,31 +36,44 @@ jest.mock('@scalprum/react-core', () => {
25
36
  };
26
37
  });
27
38
 
39
+ const defaultResponse = {
40
+ id: 1,
41
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
42
+ vulnerability: { enabled: true },
43
+ subscription_facet_attributes: { uuid: '1-2-3' },
44
+ };
45
+
28
46
  describe('CVEsHostDetailsTabWrapper', () => {
29
47
  beforeEach(() => {
30
48
  jest.clearAllMocks();
31
49
  });
32
50
 
33
- it('renders without crashing', () => {
51
+ it('renders without crashing and does not redirect for valid host', () => {
34
52
  const { container } = render(
35
- <CVEsHostDetailsTabWrapper
36
- response={{ subscription_facet_attributes: { uuid: '1-2-3' } }}
37
- />
53
+ <MemoryRouter>
54
+ <CVEsHostDetailsTabWrapper response={defaultResponse} />
55
+ </MemoryRouter>
38
56
  );
39
57
  expect(
40
58
  container.querySelector(
41
59
  '.rh-cloud-insights-vulnerability-host-details-component'
42
60
  )
43
61
  ).toBeTruthy();
62
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
44
63
  });
45
64
 
46
65
  it('remounts ScalprumComponent when systemId changes', () => {
47
66
  const { ScalprumComponent } = require('@scalprum/react-core');
48
67
 
68
+ const responseHostA = {
69
+ ...defaultResponse,
70
+ subscription_facet_attributes: { uuid: 'uuid-host-A' },
71
+ };
72
+
49
73
  const { rerender } = render(
50
- <CVEsHostDetailsTabWrapper
51
- response={{ subscription_facet_attributes: { uuid: 'uuid-host-A' } }}
52
- />
74
+ <MemoryRouter>
75
+ <CVEsHostDetailsTabWrapper response={responseHostA} />
76
+ </MemoryRouter>
53
77
  );
54
78
 
55
79
  expect(mockUnmountTracker).not.toHaveBeenCalled();
@@ -58,10 +82,15 @@ describe('CVEsHostDetailsTabWrapper', () => {
58
82
  expect.anything()
59
83
  );
60
84
 
85
+ const responseHostB = {
86
+ ...defaultResponse,
87
+ subscription_facet_attributes: { uuid: 'uuid-host-B' },
88
+ };
89
+
61
90
  rerender(
62
- <CVEsHostDetailsTabWrapper
63
- response={{ subscription_facet_attributes: { uuid: 'uuid-host-B' } }}
64
- />
91
+ <MemoryRouter>
92
+ <CVEsHostDetailsTabWrapper response={responseHostB} />
93
+ </MemoryRouter>
65
94
  );
66
95
 
67
96
  expect(mockUnmountTracker).toHaveBeenCalledTimes(1);
@@ -70,4 +99,38 @@ describe('CVEsHostDetailsTabWrapper', () => {
70
99
  expect.anything()
71
100
  );
72
101
  });
102
+
103
+ it('redirects to Overview when tab should be hidden', () => {
104
+ const nonRhelResponse = {
105
+ id: 2,
106
+ operatingsystem_name: 'Ubuntu 20.04',
107
+ vulnerability: { enabled: false },
108
+ subscription_facet_attributes: { uuid: '1-2-3' },
109
+ };
110
+
111
+ const { container } = render(
112
+ <MemoryRouter>
113
+ <CVEsHostDetailsTabWrapper response={nonRhelResponse} />
114
+ </MemoryRouter>
115
+ );
116
+
117
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
118
+ expect(
119
+ container.querySelector(
120
+ '.rh-cloud-insights-vulnerability-host-details-component'
121
+ )
122
+ ).toBeNull();
123
+ });
124
+
125
+ it('does not redirect when host data is not yet loaded', () => {
126
+ const emptyResponse = { subscription_facet_attributes: { uuid: '1-2-3' } };
127
+
128
+ render(
129
+ <MemoryRouter>
130
+ <CVEsHostDetailsTabWrapper response={emptyResponse} />
131
+ </MemoryRouter>
132
+ );
133
+
134
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
135
+ });
73
136
  });
@@ -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
  });