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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
- data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
- data/app/helpers/arf_report_dashboard_helper.rb +2 -4
- data/app/helpers/compliance_hosts_helper.rb +1 -1
- data/app/helpers/policies_helper.rb +1 -1
- data/app/services/foreman_openscap/client_config/base.rb +1 -0
- data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
- data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
- data/app/views/arf_reports/_metrics.html.erb +4 -4
- data/app/views/compliance_hosts/show.html.erb +4 -6
- data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
- data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
- data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
- data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
- data/lib/foreman_openscap/engine.rb +0 -7
- data/lib/foreman_openscap/version.rb +1 -1
- data/package.json +48 -0
- data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
- data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
- data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
- data/test/helpers/policy_dashboard_helper_test.rb +1 -1
- data/test/test_plugin_helper.rb +9 -4
- data/test/unit/policy_test.rb +1 -1
- data/test/unit/services/config_name_service_test.rb +1 -0
- data/test/unit/services/hostgroup_overrider_test.rb +2 -1
- data/test/unit/services/lookup_key_overrider_test.rb +4 -1
- data/webpack/components/EmptyState.js +73 -0
- data/webpack/components/IndexLayout.js +35 -0
- data/webpack/components/IndexLayout.scss +3 -0
- data/webpack/components/IndexTable/IndexTableHelper.js +9 -0
- data/webpack/components/IndexTable/index.js +65 -0
- data/webpack/components/RuleSeverity/RuleSeverity.scss +3 -0
- data/webpack/components/RuleSeverity/RuleSeverity.test.js +13 -0
- data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +41 -0
- data/webpack/components/RuleSeverity/i_severity-critical.svg +61 -0
- data/webpack/components/RuleSeverity/i_severity-high.svg +61 -0
- data/webpack/components/RuleSeverity/i_severity-low.svg +62 -0
- data/webpack/components/RuleSeverity/i_severity-med.svg +62 -0
- data/webpack/components/RuleSeverity/i_unknown.svg +33 -0
- data/webpack/components/RuleSeverity/index.js +33 -0
- data/webpack/components/withLoading.js +87 -0
- data/webpack/global_index.js +5 -0
- data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
- data/webpack/graphql/queries/cves.gql +23 -0
- data/webpack/graphql/queries/ovalContents.gql +16 -0
- data/webpack/graphql/queries/ovalPolicies.gql +17 -0
- data/webpack/graphql/queries/ovalPolicy.gql +26 -0
- data/webpack/helpers/commonHelper.js +1 -0
- data/webpack/helpers/globalIdHelper.js +13 -0
- data/webpack/helpers/pageParamsHelper.js +31 -0
- data/webpack/helpers/pathsHelper.js +22 -0
- data/webpack/helpers/permissionsHelper.js +42 -0
- data/webpack/helpers/tableHelper.js +9 -0
- data/webpack/index.js +8 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +46 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +46 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +120 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +101 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +47 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +44 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +95 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +98 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +49 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +63 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +78 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +39 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +87 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +129 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +36 -0
- data/webpack/routes/routes.js +28 -0
- data/webpack/testHelper.js +96 -0
- 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,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
|
+
});
|