foreman_openscap 4.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
  3. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
  4. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  5. data/app/helpers/compliance_hosts_helper.rb +1 -1
  6. data/app/helpers/policies_helper.rb +1 -1
  7. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  8. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  9. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  10. data/app/views/arf_reports/_metrics.html.erb +4 -4
  11. data/app/views/compliance_hosts/show.html.erb +4 -6
  12. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  13. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  14. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  15. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  16. data/lib/foreman_openscap/engine.rb +0 -7
  17. data/lib/foreman_openscap/version.rb +1 -1
  18. data/package.json +48 -0
  19. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  20. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  21. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  22. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  23. data/test/test_plugin_helper.rb +9 -4
  24. data/test/unit/policy_test.rb +1 -1
  25. data/test/unit/services/config_name_service_test.rb +1 -0
  26. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  27. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  28. data/webpack/components/EmptyState.js +73 -0
  29. data/webpack/components/IndexLayout.js +35 -0
  30. data/webpack/components/IndexLayout.scss +3 -0
  31. data/webpack/components/IndexTable/IndexTableHelper.js +9 -0
  32. data/webpack/components/IndexTable/index.js +65 -0
  33. data/webpack/components/RuleSeverity/RuleSeverity.scss +3 -0
  34. data/webpack/components/RuleSeverity/RuleSeverity.test.js +13 -0
  35. data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +41 -0
  36. data/webpack/components/RuleSeverity/i_severity-critical.svg +61 -0
  37. data/webpack/components/RuleSeverity/i_severity-high.svg +61 -0
  38. data/webpack/components/RuleSeverity/i_severity-low.svg +62 -0
  39. data/webpack/components/RuleSeverity/i_severity-med.svg +62 -0
  40. data/webpack/components/RuleSeverity/i_unknown.svg +33 -0
  41. data/webpack/components/RuleSeverity/index.js +33 -0
  42. data/webpack/components/withLoading.js +87 -0
  43. data/webpack/global_index.js +5 -0
  44. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  45. data/webpack/graphql/queries/cves.gql +23 -0
  46. data/webpack/graphql/queries/ovalContents.gql +16 -0
  47. data/webpack/graphql/queries/ovalPolicies.gql +17 -0
  48. data/webpack/graphql/queries/ovalPolicy.gql +26 -0
  49. data/webpack/helpers/commonHelper.js +1 -0
  50. data/webpack/helpers/globalIdHelper.js +13 -0
  51. data/webpack/helpers/pageParamsHelper.js +31 -0
  52. data/webpack/helpers/pathsHelper.js +22 -0
  53. data/webpack/helpers/permissionsHelper.js +42 -0
  54. data/webpack/helpers/tableHelper.js +9 -0
  55. data/webpack/index.js +8 -0
  56. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +46 -0
  57. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +46 -0
  58. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +120 -0
  59. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +101 -0
  60. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -0
  61. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +47 -0
  62. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +44 -0
  63. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +95 -0
  64. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +98 -0
  65. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -0
  66. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +49 -0
  67. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +63 -0
  68. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +78 -0
  69. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +39 -0
  70. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +87 -0
  71. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +129 -0
  72. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +36 -0
  73. data/webpack/routes/routes.js +28 -0
  74. data/webpack/testHelper.js +96 -0
  75. metadata +56 -7
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { within } from '@testing-library/dom';
5
+ import '@testing-library/jest-dom';
6
+
7
+ import {
8
+ withMockedProvider,
9
+ withRouter,
10
+ tick,
11
+ historyMock,
12
+ } from '../../../../testHelper';
13
+
14
+ import {
15
+ mocks,
16
+ pushMock,
17
+ pageParamsMocks,
18
+ pageParamsHistoryMock,
19
+ emptyMocks,
20
+ errorMocks,
21
+ viewerMocks,
22
+ unauthorizedMocks,
23
+ } from './OvalPoliciesIndex.fixtures';
24
+
25
+ import OvalPoliciesIndex from '../OvalPoliciesIndex';
26
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
27
+
28
+ const TestComponent = withRouter(withMockedProvider(OvalPoliciesIndex));
29
+
30
+ describe('OvalPoliciesIndex', () => {
31
+ it('should load page', async () => {
32
+ const { container } = render(
33
+ <TestComponent history={historyMock} mocks={mocks} />
34
+ );
35
+ expect(screen.getByText('Loading')).toBeInTheDocument();
36
+ await waitFor(tick);
37
+ expect(screen.getByText('first policy')).toBeInTheDocument();
38
+ expect(screen.getByText('second policy')).toBeInTheDocument();
39
+ expect(screen.getByText('first content')).toBeInTheDocument();
40
+ expect(screen.getByText('second content')).toBeInTheDocument();
41
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
42
+ expect(within(pageItems).getByText(/1 - 2/)).toBeInTheDocument();
43
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
44
+ expect(within(pageItems).getByText('2')).toBeInTheDocument();
45
+ });
46
+ it('should load page with page params', async () => {
47
+ const { container } = render(
48
+ <TestComponent history={pageParamsHistoryMock} mocks={pageParamsMocks} />
49
+ );
50
+ await waitFor(tick);
51
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
52
+ expect(within(pageItems).getByText(/6 - 7/)).toBeInTheDocument();
53
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
54
+ expect(within(pageItems).getByText('7')).toBeInTheDocument();
55
+ userEvent.click(
56
+ screen.getByRole('button', { name: 'Go to previous page' })
57
+ );
58
+
59
+ expect(pushMock).toHaveBeenCalledWith(
60
+ `${ovalPoliciesPath}?page=1&perPage=5`
61
+ );
62
+ });
63
+ it('should show empty state', async () => {
64
+ render(<TestComponent history={historyMock} mocks={emptyMocks} />);
65
+ expect(screen.getByText('Loading')).toBeInTheDocument();
66
+ await waitFor(tick);
67
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
68
+ expect(screen.getByText('No OVAL Policies found')).toBeInTheDocument();
69
+ });
70
+ it('should show errors', async () => {
71
+ render(<TestComponent history={historyMock} mocks={errorMocks} />);
72
+ expect(screen.getByText('Loading')).toBeInTheDocument();
73
+ await waitFor(tick);
74
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
75
+ expect(
76
+ screen.getByText('Something very bad happened.')
77
+ ).toBeInTheDocument();
78
+ expect(screen.getByText('Error!')).toBeInTheDocument();
79
+ });
80
+ it('should load page for user with permissions', async () => {
81
+ render(<TestComponent history={historyMock} mocks={viewerMocks} />);
82
+ await waitFor(tick);
83
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
84
+ expect(screen.getByText('first policy')).toBeInTheDocument();
85
+ });
86
+ it('should not load page for user without permissions', async () => {
87
+ render(<TestComponent history={historyMock} mocks={unauthorizedMocks} />);
88
+ await waitFor(tick);
89
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
90
+ expect(screen.queryByText('first policy')).not.toBeInTheDocument();
91
+ expect(
92
+ screen.getByText(
93
+ 'You are not authorized to view the page. Request the following permissions from administrator: view_oval_policies.'
94
+ )
95
+ ).toBeInTheDocument();
96
+ expect(screen.getByText('Permission denied')).toBeInTheDocument();
97
+ });
98
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ import OvalPoliciesIndex from './OvalPoliciesIndex';
4
+
5
+ const WrappedOvalPoliciesIndex = props => <OvalPoliciesIndex {...props} />;
6
+
7
+ export default WrappedOvalPoliciesIndex;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { useQuery } from '@apollo/client';
6
+
7
+ import CvesTable from './CvesTable';
8
+
9
+ import cves from '../../../graphql/queries/cves.gql';
10
+ import {
11
+ useParamsToVars,
12
+ useCurrentPagination,
13
+ } from '../../../helpers/pageParamsHelper';
14
+
15
+ const CvesTab = props => {
16
+ const useFetchFn = componentProps =>
17
+ useQuery(cves, {
18
+ variables: {
19
+ search: `oval_policy_id = ${componentProps.match.params.id}`,
20
+ ...useParamsToVars(componentProps.history),
21
+ },
22
+ });
23
+
24
+ const renameData = data => ({
25
+ cves: data.cves.nodes,
26
+ totalCount: data.cves.totalCount,
27
+ });
28
+
29
+ const pagination = useCurrentPagination(props.history);
30
+
31
+ return (
32
+ <CvesTable
33
+ {...props}
34
+ fetchFn={useFetchFn}
35
+ renameData={renameData}
36
+ resultPath="cves.nodes"
37
+ pagination={pagination}
38
+ emptyStateTitle={__('No CVEs found.')}
39
+ permissions={['view_oval_policies']}
40
+ />
41
+ );
42
+ };
43
+
44
+ CvesTab.propTypes = {
45
+ match: PropTypes.object.isRequired,
46
+ history: PropTypes.object.isRequired,
47
+ };
48
+
49
+ export default CvesTab;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { linkCell } from '../../../helpers/tableHelper';
6
+ import { hostsPath } from '../../../helpers/pathsHelper';
7
+ import { decodeId } from '../../../helpers/globalIdHelper';
8
+ import { addSearch } from '../../../helpers/pageParamsHelper';
9
+
10
+ import withLoading from '../../../components/withLoading';
11
+ import IndexTable from '../../../components/IndexTable';
12
+
13
+ const CvesTable = props => {
14
+ const columns = [
15
+ { title: __('Ref Id') },
16
+ { title: __('Has Errata?') },
17
+ { title: __('Hosts Count') },
18
+ ];
19
+
20
+ const cveRefId = cve => (
21
+ <a href={cve.refUrl} rel="noopener noreferrer" target="_blank">
22
+ {cve.refId}
23
+ </a>
24
+ );
25
+
26
+ const hostCount = cve =>
27
+ linkCell(
28
+ addSearch(hostsPath, { search: `cve_id = ${decodeId(cve)}` }),
29
+ cve.hosts.nodes.length
30
+ );
31
+
32
+ const rows = props.cves.map(cve => ({
33
+ cells: [
34
+ { title: cveRefId(cve) },
35
+ { title: cve.hasErrata ? __('Yes') : __('No') },
36
+ { title: hostCount(cve) },
37
+ ],
38
+ cve,
39
+ }));
40
+
41
+ const actions = [];
42
+
43
+ return (
44
+ <IndexTable
45
+ columns={columns}
46
+ rows={rows}
47
+ actions={actions}
48
+ pagination={props.pagination}
49
+ totalCount={props.totalCount}
50
+ history={props.history}
51
+ ariaTableLabel={__('Table of CVEs for OVAL policy')}
52
+ />
53
+ );
54
+ };
55
+
56
+ CvesTable.propTypes = {
57
+ cves: PropTypes.array.isRequired,
58
+ pagination: PropTypes.object.isRequired,
59
+ totalCount: PropTypes.number.isRequired,
60
+ history: PropTypes.object.isRequired,
61
+ };
62
+
63
+ export default withLoading(CvesTable);
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Link } from 'react-router-dom';
4
+ import { Helmet } from 'react-helmet';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import {
7
+ Button,
8
+ Grid,
9
+ GridItem,
10
+ TextContent,
11
+ Text,
12
+ TextVariants,
13
+ Tabs,
14
+ Tab,
15
+ TabTitleText,
16
+ } from '@patternfly/react-core';
17
+
18
+ import withLoading from '../../../components/withLoading';
19
+
20
+ import CvesTab from './CvesTab';
21
+
22
+ import { policySchedule, newJobFormPath } from './OvalPoliciesShowHelper';
23
+ import { resolvePath } from '../../../helpers/pathsHelper';
24
+
25
+ const OvalPoliciesShow = props => {
26
+ const { policy, match, history } = props;
27
+ const activeTab = match.params.tab ? match.params.tab : 'details';
28
+
29
+ const handleTabSelect = (event, value) => {
30
+ history.push(
31
+ resolvePath(match.path, { ':id': match.params.id, ':tab?': value })
32
+ );
33
+ };
34
+
35
+ return (
36
+ <React.Fragment>
37
+ <Helmet>
38
+ <title>{`${policy.name} | OVAL Policy`}</title>
39
+ </Helmet>
40
+ <Grid className="scap-page-grid">
41
+ <GridItem span={10}>
42
+ <Text component={TextVariants.h1}>{policy.name}</Text>
43
+ </GridItem>
44
+ <GridItem span={2}>
45
+ <Link to={newJobFormPath(policy, match.params.id)}>
46
+ <Button variant="secondary">{__('Scan All Hostgroups')}</Button>
47
+ </Link>
48
+ </GridItem>
49
+ <GridItem span={12}>
50
+ <Tabs mountOnEnter activeKey={activeTab} onSelect={handleTabSelect}>
51
+ <Tab
52
+ eventKey="details"
53
+ title={<TabTitleText>Details</TabTitleText>}
54
+ >
55
+ <TextContent className="pf-u-pt-md">
56
+ <Text component={TextVariants.h3}>Period</Text>
57
+ <Text component={TextVariants.p}>{policySchedule(policy)}</Text>
58
+ <Text component={TextVariants.h3}>Description</Text>
59
+ <Text component={TextVariants.p}>{policy.description}</Text>
60
+ </TextContent>
61
+ </Tab>
62
+ <Tab eventKey="cves" title={<TabTitleText>CVEs</TabTitleText>}>
63
+ <CvesTab {...props} />
64
+ </Tab>
65
+ </Tabs>
66
+ </GridItem>
67
+ </Grid>
68
+ </React.Fragment>
69
+ );
70
+ };
71
+
72
+ OvalPoliciesShow.propTypes = {
73
+ match: PropTypes.object.isRequired,
74
+ history: PropTypes.object.isRequired,
75
+ policy: PropTypes.object.isRequired,
76
+ };
77
+
78
+ export default withLoading(OvalPoliciesShow);
@@ -0,0 +1,39 @@
1
+ import { decodeId } from '../../../helpers/globalIdHelper';
2
+ import { addSearch } from '../../../helpers/pageParamsHelper';
3
+ import { newJobPath } from '../../../helpers/pathsHelper';
4
+
5
+ export const policySchedule = policy => {
6
+ switch (policy.period) {
7
+ case 'weekly':
8
+ return `Weekly, on ${policy.weekday}`;
9
+ case 'monthly':
10
+ return `Monthly, day of month: ${policy.dayOfMonth}`;
11
+ case 'custom':
12
+ return `Custom cron: ${policy.cronLine}`;
13
+ default:
14
+ return 'Unknown schedule';
15
+ }
16
+ };
17
+
18
+ const targetingScopedSearchQuery = policy => {
19
+ const hgIds = policy.hostgroups.nodes.reduce((memo, hg) => {
20
+ const ids = [decodeId(hg)].concat(hg.descendants.nodes.map(decodeId));
21
+ return ids.reduce(
22
+ (acc, id) => (acc.includes(id) ? acc : [...acc, id]),
23
+ memo
24
+ );
25
+ }, []);
26
+
27
+ if (hgIds.length === 0) {
28
+ return '';
29
+ }
30
+
31
+ return `hostgroup_id ^ (${hgIds.join(' ')})`;
32
+ };
33
+
34
+ export const newJobFormPath = (policy, policyId) =>
35
+ addSearch(newJobPath, {
36
+ feature: 'foreman_openscap_run_oval_scans',
37
+ host_ids: targetingScopedSearchQuery(policy),
38
+ 'inputs[oval_policies]': policyId,
39
+ });
@@ -0,0 +1,87 @@
1
+ import { mockFactory, admin, intruder } from '../../../../testHelper';
2
+ import ovalPolicyQuery from '../../../../graphql/queries/ovalPolicy.gql';
3
+ import cvesQuery from '../../../../graphql/queries/cves.gql';
4
+
5
+ const policyDetailMockFactory = mockFactory('ovalPolicy', ovalPolicyQuery);
6
+ const cvesMockFactory = mockFactory('cves', cvesQuery);
7
+
8
+ const ovalPolicy = {
9
+ id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTM=',
10
+ name: 'Third policy',
11
+ period: 'weekly',
12
+ cronLine: null,
13
+ weekday: 'tuesday',
14
+ dayOfMonth: null,
15
+ description: 'A very strict policy',
16
+ hostgroups: {
17
+ nodes: [
18
+ {
19
+ id: 'MDE6SG9zdGdyb3VwLTQ=',
20
+ name: 'oval hg',
21
+ descendants: {
22
+ nodes: [
23
+ { id: 'MDE6SG9zdGdyb3VwLTEw' },
24
+ { id: 'MDE6SG9zdGdyb3VwLTEy' },
25
+ { id: 'MDE6SG9zdGdyb3VwLTEx' },
26
+ ],
27
+ },
28
+ },
29
+ ],
30
+ },
31
+ };
32
+
33
+ const cvesResult = {
34
+ totalCount: 1,
35
+ nodes: [
36
+ {
37
+ id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpDdmUtMjY3',
38
+ refId: 'CVE-2020-14365',
39
+ refUrl: 'https://access.redhat.com/security/cve/CVE-2020-14365',
40
+ definitionId: 'oval:com.redhat.rhsa:def:20203601',
41
+ hasErrata: true,
42
+ hosts: {
43
+ nodes: [
44
+ {
45
+ id: 'MDE6SG9zdC0z',
46
+ name: 'centos-random.example.com',
47
+ },
48
+ ],
49
+ },
50
+ },
51
+ ],
52
+ };
53
+
54
+ export const ovalPolicyId = 3;
55
+
56
+ export const pushMock = jest.fn();
57
+
58
+ export const historyMock = {
59
+ location: {
60
+ search: '',
61
+ },
62
+ push: pushMock,
63
+ };
64
+
65
+ export const historyWithSearch = {
66
+ location: {
67
+ search: '?page=1&perPage=5',
68
+ },
69
+ };
70
+
71
+ export const policyDetailMock = policyDetailMockFactory(
72
+ { id: ovalPolicy.id },
73
+ ovalPolicy,
74
+ { currentUser: admin }
75
+ );
76
+
77
+ export const policyUnauthorizedMock = policyDetailMockFactory(
78
+ { id: ovalPolicy.id },
79
+ ovalPolicy,
80
+ { currentUser: intruder }
81
+ );
82
+
83
+ export const policyCvesMock = cvesMockFactory(
84
+ { search: `oval_policy_id = ${ovalPolicyId}`, first: 5, last: 5 },
85
+ cvesResult,
86
+ { currentUser: admin }
87
+ );
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { Router } from 'react-router-dom';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import { within } from '@testing-library/dom';
5
+ import '@testing-library/jest-dom';
6
+ import userEvent from '@testing-library/user-event';
7
+ import { createMemoryHistory } from 'history';
8
+
9
+ import OvalPoliciesShow from '../index';
10
+ import {
11
+ ovalPoliciesShowPath,
12
+ resolvePath,
13
+ } from '../../../../helpers/pathsHelper';
14
+
15
+ import { withMockedProvider, tick, withRouter } from '../../../../testHelper';
16
+ import {
17
+ policyDetailMock,
18
+ historyMock,
19
+ historyWithSearch,
20
+ pushMock,
21
+ policyCvesMock,
22
+ ovalPolicyId,
23
+ policyUnauthorizedMock,
24
+ } from './OvalPoliciesShow.fixtures';
25
+
26
+ const TestComponent = withRouter(withMockedProvider(OvalPoliciesShow));
27
+
28
+ describe('OvalPoliciesShow', () => {
29
+ it('should load details by default and handle tab change', async () => {
30
+ const { container } = render(
31
+ <TestComponent
32
+ history={historyMock}
33
+ match={{ params: { id: ovalPolicyId }, path: ovalPoliciesShowPath }}
34
+ mocks={policyDetailMock}
35
+ />
36
+ );
37
+ expect(screen.getByText('Loading')).toBeInTheDocument();
38
+ await waitFor(tick);
39
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
40
+ expect(screen.getByText('Third policy')).toBeInTheDocument();
41
+ expect(screen.getByText('Weekly, on tuesday')).toBeInTheDocument();
42
+ expect(screen.getByText('A very strict policy')).toBeInTheDocument();
43
+ const activeTabHeader = container.querySelector(
44
+ '.pf-c-tabs__item.pf-m-current'
45
+ );
46
+ expect(within(activeTabHeader).getByText('Details')).toBeInTheDocument();
47
+ userEvent.click(screen.getByRole('button', { name: 'CVEs' }));
48
+ expect(pushMock).toHaveBeenCalledWith(
49
+ resolvePath(ovalPoliciesShowPath, {
50
+ ':id': ovalPolicyId,
51
+ ':tab?': 'cves',
52
+ })
53
+ );
54
+ });
55
+ it('should load details tab when specified in URL', async () => {
56
+ render(
57
+ <TestComponent
58
+ history={historyMock}
59
+ match={{
60
+ params: { id: ovalPolicyId, tab: 'details' },
61
+ path: ovalPoliciesShowPath,
62
+ }}
63
+ mocks={policyDetailMock}
64
+ />
65
+ );
66
+ expect(screen.getByText('Loading')).toBeInTheDocument();
67
+ await waitFor(tick);
68
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
69
+ expect(screen.getByText('Weekly, on tuesday')).toBeInTheDocument();
70
+ });
71
+ it('should not load the page when user does not have permissions', async () => {
72
+ render(
73
+ <TestComponent
74
+ history={historyMock}
75
+ match={{ params: { id: ovalPolicyId }, path: ovalPoliciesShowPath }}
76
+ mocks={policyUnauthorizedMock}
77
+ />
78
+ );
79
+ await waitFor(tick);
80
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
81
+ expect(
82
+ screen.getByText(
83
+ 'You are not authorized to view the page. Request the following permissions from administrator: view_oval_policies.'
84
+ )
85
+ ).toBeInTheDocument();
86
+ });
87
+ it('should load CVEs tab when specified in URL', async () => {
88
+ const mocks = policyDetailMock.concat(policyCvesMock);
89
+ render(
90
+ <TestComponent
91
+ history={historyWithSearch}
92
+ match={{
93
+ params: { id: ovalPolicyId, tab: 'cves' },
94
+ path: ovalPoliciesShowPath,
95
+ }}
96
+ mocks={mocks}
97
+ />
98
+ );
99
+ expect(screen.getByText('Loading')).toBeInTheDocument();
100
+ await waitFor(tick);
101
+ await waitFor(tick);
102
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
103
+ expect(screen.getByText('CVE-2020-14365')).toBeInTheDocument();
104
+ });
105
+ it('should have button for scanning all hostgroups', async () => {
106
+ const btnText = 'Scan All Hostgroups';
107
+
108
+ const WithProvider = withMockedProvider(OvalPoliciesShow);
109
+ const history = createMemoryHistory();
110
+ history.push = jest.fn();
111
+
112
+ render(
113
+ <Router history={history}>
114
+ <WithProvider
115
+ history={history}
116
+ match={{ params: { id: ovalPolicyId }, path: ovalPoliciesShowPath }}
117
+ mocks={policyDetailMock}
118
+ />
119
+ </Router>
120
+ );
121
+ await waitFor(tick);
122
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
123
+ expect(screen.getByText(btnText)).toBeInTheDocument();
124
+ userEvent.click(screen.getByRole('button', { name: btnText }));
125
+ expect(history.push).toHaveBeenCalledWith(
126
+ '/job_invocations/new?feature=foreman_openscap_run_oval_scans&host_ids=hostgroup_id+%5E+%284+10+12+11%29&inputs%5Boval_policies%5D=3'
127
+ );
128
+ });
129
+ });