foreman_openscap 4.3.0 → 5.0.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.
- 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
|
+
});
|