foreman_remote_execution 4.3.0 → 4.5.1
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/job_invocations_controller.rb +27 -22
- data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
- data/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/job_templates_controller.rb +4 -4
- data/app/controllers/ui_job_wizard_controller.rb +12 -0
- data/app/helpers/job_invocations_helper.rb +2 -2
- data/app/helpers/remote_execution_helper.rb +35 -8
- data/app/lib/actions/remote_execution/run_host_job.rb +37 -7
- data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
- data/app/models/host_proxy_invocation.rb +4 -0
- data/app/models/host_status/execution_status.rb +5 -5
- data/app/models/invocation_provider_input_value.rb +12 -0
- data/app/models/job_invocation.rb +35 -12
- data/app/models/job_invocation_composer.rb +74 -19
- data/app/models/remote_execution_provider.rb +18 -3
- data/app/models/setting/remote_execution.rb +11 -1
- data/app/models/ssh_execution_provider.rb +4 -4
- data/app/models/targeting.rb +5 -1
- data/app/models/template_invocation.rb +2 -0
- data/app/overrides/execution_interface.rb +8 -8
- data/app/overrides/subnet_proxies.rb +6 -6
- data/app/services/renderer_methods.rb +12 -0
- data/app/views/job_invocations/_form.html.erb +8 -0
- data/app/views/job_invocations/index.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
- data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
- data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/extra/cockpit/foreman-cockpit-session +6 -6
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +14 -12
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
- data/locale/action_names.rb +1 -0
- data/locale/de/foreman_remote_execution.po +77 -27
- data/locale/en/foreman_remote_execution.po +77 -27
- data/locale/en_GB/foreman_remote_execution.po +77 -27
- data/locale/es/foreman_remote_execution.po +77 -27
- data/locale/foreman_remote_execution.pot +241 -163
- data/locale/fr/foreman_remote_execution.po +77 -27
- data/locale/ja/foreman_remote_execution.po +77 -27
- data/locale/ko/foreman_remote_execution.po +77 -27
- data/locale/pt_BR/foreman_remote_execution.po +77 -27
- data/locale/ru/foreman_remote_execution.po +77 -27
- data/locale/zh_CN/foreman_remote_execution.po +77 -27
- data/locale/zh_TW/foreman_remote_execution.po +77 -27
- data/package.json +4 -2
- data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
- data/test/helpers/remote_execution_helper_test.rb +16 -0
- data/test/unit/job_invocation_composer_test.rb +100 -3
- data/test/unit/job_invocation_report_template_test.rb +57 -0
- data/test/unit/job_invocation_test.rb +1 -1
- data/webpack/JobWizard/JobWizard.js +75 -11
- data/webpack/JobWizard/JobWizard.scss +14 -0
- data/webpack/JobWizard/JobWizardConstants.js +6 -0
- data/webpack/JobWizard/JobWizardSelectors.js +38 -0
- data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
- data/webpack/JobWizard/__tests__/fixtures.js +26 -0
- data/webpack/JobWizard/__tests__/integration.test.js +156 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
- data/webpack/JobWizard/steps/form/SelectField.js +48 -0
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
- data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
- data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
- data/webpack/global_index.js +6 -0
- data/webpack/index.js +3 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
- data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
- data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
- data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
- data/webpack/react_app/extend/reducers.js +5 -0
- metadata +49 -8
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- data/app/views/api/v2/registration/_form.html.erb +0 -12
- data/test/models/orchestration/ssh_test.rb +0 -56
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class JobReportTemplateTest < ActiveSupport::TestCase
|
4
|
+
class FakeTask < OpenStruct
|
5
|
+
class Jail < Safemode::Jail
|
6
|
+
allow :action_continuous_output, :result, :ended_at
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'with valid job invocation report template' do
|
11
|
+
let(:job_invocation_template) do
|
12
|
+
file_path = File.read(File.expand_path(Rails.root + "app/views/unattended/report_templates/jobs_-_invocation_report_template.erb"))
|
13
|
+
template = ReportTemplate.import_without_save("Job Invocation Report Template", file_path)
|
14
|
+
template.save!
|
15
|
+
template
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'template setting' do
|
19
|
+
it 'in settings includes only report templates with job_id input' do
|
20
|
+
FactoryBot.create(:report_template, name: 'Template 1')
|
21
|
+
job_invocation_template
|
22
|
+
templates = Setting::RemoteExecution.job_invocation_report_templates_select
|
23
|
+
|
24
|
+
assert_include templates, 'Job Invocation Report Template'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'task reporting' do
|
29
|
+
let(:fake_outputs) do
|
30
|
+
[
|
31
|
+
{ 'output_type' => 'stderr', 'output' => "error", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
32
|
+
{ 'output_type' => 'stdout', 'output' => "output", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
33
|
+
{ 'output_type' => 'stdebug', 'output' => "debug", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
34
|
+
]
|
35
|
+
end
|
36
|
+
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs) }
|
37
|
+
|
38
|
+
it 'should render task outputs' do
|
39
|
+
job_invocation = FactoryBot.create(:job_invocation, :with_task)
|
40
|
+
JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
|
41
|
+
|
42
|
+
input = job_invocation_template.template_inputs.first
|
43
|
+
composer_params = { template_id: job_invocation_template.id, input_values: { input.id.to_s => { value: job_invocation.id.to_s } } }
|
44
|
+
result = ReportComposer.new(composer_params).render
|
45
|
+
|
46
|
+
# parsing the CSV result
|
47
|
+
CSV.parse(result.strip, headers: true).each_with_index do |row, i|
|
48
|
+
row_hash = row.to_h
|
49
|
+
assert_equal 'success', row_hash['result']
|
50
|
+
assert_equal fake_outputs[i]['output_type'], row_hash['type']
|
51
|
+
assert_equal fake_outputs[i]['output'], row_hash['message']
|
52
|
+
assert_kind_of Time, Time.zone.parse(row_hash['time']), 'Parsing of time column failed'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'is able to perform search through job invocations' do
|
13
|
-
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).
|
13
|
+
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
|
14
14
|
_(found_jobs).must_equal [job_invocation]
|
15
15
|
end
|
16
16
|
|
@@ -1,30 +1,94 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
2
3
|
import { Wizard } from '@patternfly/react-core';
|
4
|
+
import { get } from 'foremanReact/redux/API';
|
3
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
4
6
|
import history from 'foremanReact/history';
|
7
|
+
import CategoryAndTemplate from './steps/CategoryAndTemplate/';
|
8
|
+
import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
|
9
|
+
import { JOB_TEMPLATE } from './JobWizardConstants';
|
10
|
+
import { selectTemplateError } from './JobWizardSelectors';
|
11
|
+
import './JobWizard.scss';
|
5
12
|
|
6
13
|
export const JobWizard = () => {
|
14
|
+
const [jobTemplateID, setJobTemplateID] = useState(null);
|
15
|
+
const [category, setCategory] = useState('');
|
16
|
+
const [advancedValues, setAdvancedValues] = useState({});
|
17
|
+
const dispatch = useDispatch();
|
18
|
+
|
19
|
+
const setDefaults = useCallback(response => {
|
20
|
+
const responseJob = response.data;
|
21
|
+
setAdvancedValues({
|
22
|
+
effectiveUserValue: responseJob.effective_user?.value || '',
|
23
|
+
timeoutToKill: responseJob.job_template.execution_timeout_interval || '',
|
24
|
+
});
|
25
|
+
}, []);
|
26
|
+
useEffect(() => {
|
27
|
+
if (jobTemplateID) {
|
28
|
+
dispatch(
|
29
|
+
get({
|
30
|
+
key: JOB_TEMPLATE,
|
31
|
+
url: `/ui_job_wizard/template/${jobTemplateID}`,
|
32
|
+
handleSuccess: setDefaults,
|
33
|
+
})
|
34
|
+
);
|
35
|
+
}
|
36
|
+
}, [jobTemplateID, setDefaults, dispatch]);
|
37
|
+
|
38
|
+
const templateError = !!useSelector(selectTemplateError);
|
39
|
+
const isTemplate = !templateError && !!jobTemplateID;
|
7
40
|
const steps = [
|
8
41
|
{
|
9
|
-
name: __('Category and
|
10
|
-
component:
|
42
|
+
name: __('Category and Template'),
|
43
|
+
component: (
|
44
|
+
<CategoryAndTemplate
|
45
|
+
jobTemplate={jobTemplateID}
|
46
|
+
setJobTemplate={setJobTemplateID}
|
47
|
+
category={category}
|
48
|
+
setCategory={setCategory}
|
49
|
+
/>
|
50
|
+
),
|
51
|
+
},
|
52
|
+
{
|
53
|
+
name: __('Target Hosts'),
|
54
|
+
component: <p>Target Hosts</p>,
|
55
|
+
canJumpTo: isTemplate,
|
56
|
+
},
|
57
|
+
{
|
58
|
+
name: __('Advanced Fields'),
|
59
|
+
component: (
|
60
|
+
<AdvancedFields
|
61
|
+
advancedValues={advancedValues}
|
62
|
+
setAdvancedValues={newValues => {
|
63
|
+
setAdvancedValues(currentAdvancedValues => ({
|
64
|
+
...currentAdvancedValues,
|
65
|
+
...newValues,
|
66
|
+
}));
|
67
|
+
}}
|
68
|
+
jobTemplateID={jobTemplateID}
|
69
|
+
/>
|
70
|
+
),
|
71
|
+
canJumpTo: isTemplate,
|
72
|
+
},
|
73
|
+
{
|
74
|
+
name: __('Schedule'),
|
75
|
+
component: <p>Schedule</p>,
|
76
|
+
canJumpTo: isTemplate,
|
11
77
|
},
|
12
|
-
{ name: __('Target hosts'), component: <p>TargetHosts </p> },
|
13
|
-
{ name: __('Advanced fields'), component: <p> AdvancedFields </p> },
|
14
|
-
{ name: __('Schedule'), component: <p>Schedule</p> },
|
15
78
|
{
|
16
|
-
name: __('Review
|
17
|
-
component: <p>
|
79
|
+
name: __('Review Details'),
|
80
|
+
component: <p>Review Details</p>,
|
18
81
|
nextButtonText: 'Run',
|
82
|
+
canJumpTo: isTemplate,
|
19
83
|
},
|
20
84
|
];
|
21
|
-
const title = __('Run Job');
|
22
85
|
return (
|
23
86
|
<Wizard
|
24
87
|
onClose={() => history.goBack()}
|
25
|
-
navAriaLabel=
|
88
|
+
navAriaLabel="Run Job steps"
|
26
89
|
steps={steps}
|
27
|
-
height="
|
90
|
+
height="100%"
|
91
|
+
className="job-wizard"
|
28
92
|
/>
|
29
93
|
);
|
30
94
|
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
.job-wizard {
|
2
|
+
.pf-c-wizard__main {
|
3
|
+
z-index: calc(
|
4
|
+
var(--pf-c-wizard__footer--ZIndex) + 1
|
5
|
+
); // So the select box can be shown above the wizard footer
|
6
|
+
}
|
7
|
+
|
8
|
+
.pf-c-wizard__main-body {
|
9
|
+
max-width: 500px;
|
10
|
+
.advanced-fields-title {
|
11
|
+
margin-bottom: 10px;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
2
|
+
|
3
|
+
export const JOB_TEMPLATES = 'JOB_TEMPLATES';
|
4
|
+
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
|
5
|
+
export const JOB_TEMPLATE = 'JOB_TEMPLATE';
|
6
|
+
export const templatesUrl = foremanUrl('/api/v2/job_templates');
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import {
|
2
|
+
selectAPIResponse,
|
3
|
+
selectAPIStatus,
|
4
|
+
selectAPIErrorMessage,
|
5
|
+
} from 'foremanReact/redux/API/APISelectors';
|
6
|
+
|
7
|
+
import {
|
8
|
+
JOB_TEMPLATES,
|
9
|
+
JOB_CATEGORIES,
|
10
|
+
JOB_TEMPLATE,
|
11
|
+
} from './JobWizardConstants';
|
12
|
+
|
13
|
+
export const selectJobTemplatesStatus = state =>
|
14
|
+
selectAPIStatus(state, JOB_TEMPLATES);
|
15
|
+
|
16
|
+
export const filterJobTemplates = templates =>
|
17
|
+
templates?.filter(template => !template.snippet) || [];
|
18
|
+
|
19
|
+
export const selectJobTemplates = state =>
|
20
|
+
filterJobTemplates(selectAPIResponse(state, JOB_TEMPLATES)?.results);
|
21
|
+
|
22
|
+
export const selectJobCategories = state =>
|
23
|
+
selectAPIResponse(state, JOB_CATEGORIES).job_categories || [];
|
24
|
+
|
25
|
+
export const selectJobCategoriesStatus = state =>
|
26
|
+
selectAPIStatus(state, JOB_CATEGORIES);
|
27
|
+
|
28
|
+
export const selectCategoryError = state =>
|
29
|
+
selectAPIErrorMessage(state, JOB_CATEGORIES);
|
30
|
+
|
31
|
+
export const selectAllTemplatesError = state =>
|
32
|
+
selectAPIErrorMessage(state, JOB_TEMPLATES);
|
33
|
+
|
34
|
+
export const selectTemplateError = state =>
|
35
|
+
selectAPIErrorMessage(state, JOB_TEMPLATE);
|
36
|
+
|
37
|
+
export const selectJobTemplate = state =>
|
38
|
+
selectAPIResponse(state, JOB_TEMPLATE);
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import * as patternfly from '@patternfly/react-core';
|
3
|
+
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
4
|
+
import JobWizardPage from '../index';
|
5
|
+
|
6
|
+
jest.spyOn(patternfly, 'Wizard');
|
7
|
+
patternfly.Wizard.mockImplementation(props => <div>{props.navAriaLabel}</div>);
|
8
|
+
|
9
|
+
const fixtures = {
|
10
|
+
'renders ': {},
|
11
|
+
};
|
12
|
+
describe('JobWizardPage rendering', () =>
|
13
|
+
testComponentSnapshotsWithFixtures(JobWizardPage, fixtures));
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`JobWizardPage rendering renders 1`] = `
|
4
|
+
<PageLayout
|
5
|
+
breadcrumbOptions={
|
6
|
+
Object {
|
7
|
+
"breadcrumbItems": Array [
|
8
|
+
Object {
|
9
|
+
"caption": "Jobs",
|
10
|
+
"url": "/jobs",
|
11
|
+
},
|
12
|
+
Object {
|
13
|
+
"caption": "Run job",
|
14
|
+
},
|
15
|
+
],
|
16
|
+
}
|
17
|
+
}
|
18
|
+
header="Run job"
|
19
|
+
searchable={false}
|
20
|
+
>
|
21
|
+
<Title
|
22
|
+
headingLevel="h2"
|
23
|
+
size="2xl"
|
24
|
+
>
|
25
|
+
Run job
|
26
|
+
</Title>
|
27
|
+
<Divider
|
28
|
+
component="div"
|
29
|
+
/>
|
30
|
+
<JobWizard />
|
31
|
+
</PageLayout>
|
32
|
+
`;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`Job wizard fill should select template: initial 1`] = `
|
4
|
+
Array [
|
5
|
+
Object {
|
6
|
+
"key": "JOB_CATEGORIES",
|
7
|
+
"type": "get",
|
8
|
+
"url": "/ui_job_wizard/categories",
|
9
|
+
},
|
10
|
+
Object {
|
11
|
+
"key": "JOB_TEMPLATES",
|
12
|
+
"type": "get",
|
13
|
+
"url": URI {
|
14
|
+
"_deferred_build": true,
|
15
|
+
"_parts": Object {
|
16
|
+
"duplicateQueryParameters": false,
|
17
|
+
"escapeQuerySpace": true,
|
18
|
+
"fragment": null,
|
19
|
+
"hostname": null,
|
20
|
+
"password": null,
|
21
|
+
"path": "foreman/api/v2/job_templates",
|
22
|
+
"port": null,
|
23
|
+
"preventInvalidHostname": false,
|
24
|
+
"protocol": null,
|
25
|
+
"query": "search=job_category%3D%22Ansible+Commands%22&per_page=all",
|
26
|
+
"urn": null,
|
27
|
+
"username": null,
|
28
|
+
},
|
29
|
+
"_string": "",
|
30
|
+
},
|
31
|
+
},
|
32
|
+
]
|
33
|
+
`;
|
34
|
+
|
35
|
+
exports[`Job wizard fill should select template: select template 1`] = `
|
36
|
+
Array [
|
37
|
+
Object {
|
38
|
+
"key": "JOB_TEMPLATE",
|
39
|
+
"type": "get",
|
40
|
+
"url": "/ui_job_wizard/template/178",
|
41
|
+
},
|
42
|
+
]
|
43
|
+
`;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
const jobTemplate = {
|
2
|
+
id: 178,
|
3
|
+
name: 'Run Command - Ansible Default',
|
4
|
+
template:
|
5
|
+
"---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
|
6
|
+
snippet: false,
|
7
|
+
default: true,
|
8
|
+
job_category: 'Ansible Commands',
|
9
|
+
provider_type: 'Ansible',
|
10
|
+
description_format: 'Run %{command}',
|
11
|
+
execution_timeout_interval: 2,
|
12
|
+
description: null,
|
13
|
+
};
|
14
|
+
|
15
|
+
export const jobTemplates = [jobTemplate];
|
16
|
+
|
17
|
+
export const jobTemplateResponse = {
|
18
|
+
job_template: jobTemplate,
|
19
|
+
effective_user: {
|
20
|
+
id: null,
|
21
|
+
job_template_id: 178,
|
22
|
+
value: 'default effective user',
|
23
|
+
overridable: true,
|
24
|
+
current_user: false,
|
25
|
+
},
|
26
|
+
};
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import configureMockStore from 'redux-mock-store';
|
4
|
+
import { mount } from '@theforeman/test';
|
5
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
6
|
+
import * as api from 'foremanReact/redux/API';
|
7
|
+
import { JobWizard } from '../JobWizard';
|
8
|
+
import * as selectors from '../JobWizardSelectors';
|
9
|
+
import { jobTemplates, jobTemplateResponse as jobTemplate } from './fixtures';
|
10
|
+
|
11
|
+
jest.spyOn(api, 'get');
|
12
|
+
jest.spyOn(selectors, 'selectJobTemplate');
|
13
|
+
jest.spyOn(selectors, 'selectJobTemplates');
|
14
|
+
jest.spyOn(selectors, 'selectJobCategories');
|
15
|
+
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
16
|
+
|
17
|
+
const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
|
18
|
+
|
19
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
20
|
+
if (action.key === 'JOB_CATEGORIES') {
|
21
|
+
handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
|
22
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
23
|
+
handleSuccess &&
|
24
|
+
handleSuccess({
|
25
|
+
data: jobTemplate,
|
26
|
+
});
|
27
|
+
}
|
28
|
+
return { type: 'get', ...action };
|
29
|
+
});
|
30
|
+
|
31
|
+
selectors.selectJobTemplate.mockImplementation(() => null);
|
32
|
+
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
33
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
34
|
+
selectors.selectJobTemplates.mockImplementation(() => jobTemplates);
|
35
|
+
|
36
|
+
const mockStore = configureMockStore([]);
|
37
|
+
const store = mockStore({});
|
38
|
+
describe('Job wizard fill', () => {
|
39
|
+
it('should select template', async () => {
|
40
|
+
const wrapper = mount(
|
41
|
+
<Provider store={store}>
|
42
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
43
|
+
</Provider>
|
44
|
+
);
|
45
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
46
|
+
4
|
47
|
+
);
|
48
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
49
|
+
expect(store.getActions()).toMatchSnapshot('initial');
|
50
|
+
|
51
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
52
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
53
|
+
await act(async () => {
|
54
|
+
await wrapper.find('.pf-c-select__menu-item').simulate('click');
|
55
|
+
await wrapper.update();
|
56
|
+
});
|
57
|
+
expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
|
58
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
59
|
+
0
|
60
|
+
);
|
61
|
+
});
|
62
|
+
|
63
|
+
it('should save data between steps for advanced fields', async () => {
|
64
|
+
const wrapper = mount(
|
65
|
+
<Provider store={store}>
|
66
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
67
|
+
</Provider>
|
68
|
+
);
|
69
|
+
// setup
|
70
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
71
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
72
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
73
|
+
wrapper.find('.pf-c-select__menu-item').simulate('click');
|
74
|
+
|
75
|
+
// test
|
76
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
77
|
+
0
|
78
|
+
);
|
79
|
+
wrapper
|
80
|
+
.find('.pf-c-wizard__nav-link')
|
81
|
+
.at(2)
|
82
|
+
.simulate('click'); // Advanced step
|
83
|
+
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
84
|
+
const effectiveUesrValue = 'effective user new value';
|
85
|
+
effectiveUserInput().getDOMNode().value = effectiveUesrValue;
|
86
|
+
await act(async () => {
|
87
|
+
await effectiveUserInput().simulate('change');
|
88
|
+
wrapper.update();
|
89
|
+
});
|
90
|
+
|
91
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
92
|
+
|
93
|
+
wrapper
|
94
|
+
.find('.pf-c-wizard__nav-link')
|
95
|
+
.at(1)
|
96
|
+
.simulate('click');
|
97
|
+
|
98
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-current').text()).toEqual(
|
99
|
+
'Target Hosts'
|
100
|
+
);
|
101
|
+
wrapper
|
102
|
+
.find('.pf-c-wizard__nav-link')
|
103
|
+
.at(2)
|
104
|
+
.simulate('click'); // Advanced step
|
105
|
+
|
106
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
107
|
+
});
|
108
|
+
|
109
|
+
it('have all steps', async () => {
|
110
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
111
|
+
selectors.selectJobTemplate.mockRestore();
|
112
|
+
selectors.selectJobTemplates.mockRestore();
|
113
|
+
selectors.selectJobCategories.mockRestore();
|
114
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
115
|
+
if (action.key === 'JOB_CATEGORIES') {
|
116
|
+
handleSuccess &&
|
117
|
+
handleSuccess({ data: { job_categories: jobCategories } });
|
118
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
119
|
+
handleSuccess &&
|
120
|
+
handleSuccess({
|
121
|
+
data: jobTemplate,
|
122
|
+
});
|
123
|
+
} else if (action.key === 'JOB_TEMPLATES') {
|
124
|
+
handleSuccess &&
|
125
|
+
handleSuccess({
|
126
|
+
data: { results: [jobTemplate.job_template] },
|
127
|
+
});
|
128
|
+
}
|
129
|
+
return { type: 'get', ...action };
|
130
|
+
});
|
131
|
+
|
132
|
+
render(
|
133
|
+
<Provider store={store}>
|
134
|
+
<JobWizard />
|
135
|
+
</Provider>
|
136
|
+
);
|
137
|
+
const steps = [
|
138
|
+
'Target Hosts',
|
139
|
+
'Advanced Fields',
|
140
|
+
'Schedule',
|
141
|
+
'Review Details',
|
142
|
+
'Category and Template',
|
143
|
+
];
|
144
|
+
// eslint-disable-next-line no-unused-vars
|
145
|
+
for await (const step of steps) {
|
146
|
+
const stepSelector = screen.getByText(step);
|
147
|
+
const stepTitle = screen.getAllByText(step);
|
148
|
+
expect(stepTitle).toHaveLength(1);
|
149
|
+
await act(async () => {
|
150
|
+
await fireEvent.click(stepSelector);
|
151
|
+
});
|
152
|
+
const stepTitles = screen.getAllByText(step);
|
153
|
+
expect(stepTitles).toHaveLength(3);
|
154
|
+
}
|
155
|
+
});
|
156
|
+
});
|