foreman_remote_execution 4.5.1 → 4.7.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/.github/workflows/ruby_ci.yml +7 -0
- data/app/controllers/ui_job_wizard_controller.rb +7 -0
- data/app/graphql/types/job_invocation.rb +16 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
- data/app/helpers/remote_execution_helper.rb +9 -3
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/job_invocation_composer.rb +3 -3
- data/app/models/job_template.rb +1 -1
- data/app/models/remote_execution_feature.rb +5 -1
- data/app/models/remote_execution_provider.rb +1 -1
- data/app/views/templates/ssh/module_action.erb +1 -0
- data/app/views/templates/ssh/power_action.erb +2 -0
- data/app/views/templates/ssh/puppet_run_once.erb +1 -0
- data/foreman_remote_execution.gemspec +2 -4
- data/lib/foreman_remote_execution/engine.rb +3 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/unit/concerns/host_extensions_test.rb +4 -4
- data/test/unit/input_template_renderer_test.rb +1 -89
- data/test/unit/job_invocation_composer_test.rb +1 -12
- data/webpack/JobWizard/JobWizard.js +28 -8
- data/webpack/JobWizard/JobWizard.scss +39 -0
- data/webpack/JobWizard/JobWizardConstants.js +10 -0
- data/webpack/JobWizard/JobWizardSelectors.js +9 -0
- data/webpack/JobWizard/__tests__/fixtures.js +104 -2
- data/webpack/JobWizard/__tests__/integration.test.js +13 -85
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
- data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
- data/webpack/JobWizard/steps/Schedule/index.js +41 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
- data/webpack/JobWizard/steps/form/Formatter.js +149 -0
- data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
- data/webpack/JobWizard/steps/form/SelectField.js +14 -2
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
- data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +72 -66
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
- data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
- data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
- metadata +23 -27
- data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
- data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
@@ -0,0 +1,67 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { FormGroup, TextInput, Button } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
|
6
|
+
export const DescriptionField = ({ inputs, value, setValue }) => {
|
7
|
+
const generateDesc = () => {
|
8
|
+
let newDesc = value;
|
9
|
+
if (value) {
|
10
|
+
const re = new RegExp('%\\{([^\\}]+)\\}', 'gm');
|
11
|
+
const results = [...newDesc.matchAll(re)].map(result => ({
|
12
|
+
name: result[1],
|
13
|
+
text: result[0],
|
14
|
+
}));
|
15
|
+
results.forEach(result => {
|
16
|
+
newDesc = newDesc.replace(
|
17
|
+
result.text,
|
18
|
+
// TODO: Replace with the value of the input from Target Hosts step
|
19
|
+
inputs.find(input => input.name === result.name)?.name || result.text
|
20
|
+
);
|
21
|
+
});
|
22
|
+
}
|
23
|
+
return newDesc;
|
24
|
+
};
|
25
|
+
const [generatedDesc, setGeneratedDesc] = useState(generateDesc());
|
26
|
+
const [isPreview, setIsPreview] = useState(true);
|
27
|
+
|
28
|
+
const togglePreview = () => {
|
29
|
+
setGeneratedDesc(generateDesc());
|
30
|
+
setIsPreview(v => !v);
|
31
|
+
};
|
32
|
+
|
33
|
+
return (
|
34
|
+
<FormGroup
|
35
|
+
label={__('Description')}
|
36
|
+
fieldId="description"
|
37
|
+
helperText={
|
38
|
+
<Button variant="link" isInline onClick={togglePreview}>
|
39
|
+
{isPreview
|
40
|
+
? __('Edit job description template')
|
41
|
+
: __('Preview job description')}
|
42
|
+
</Button>
|
43
|
+
}
|
44
|
+
>
|
45
|
+
{isPreview ? (
|
46
|
+
<TextInput id="description-preview" value={generatedDesc} isDisabled />
|
47
|
+
) : (
|
48
|
+
<TextInput
|
49
|
+
type="text"
|
50
|
+
autoComplete="description"
|
51
|
+
id="description"
|
52
|
+
value={value}
|
53
|
+
onChange={newValue => setValue(newValue)}
|
54
|
+
/>
|
55
|
+
)}
|
56
|
+
</FormGroup>
|
57
|
+
);
|
58
|
+
};
|
59
|
+
|
60
|
+
DescriptionField.propTypes = {
|
61
|
+
inputs: PropTypes.array.isRequired,
|
62
|
+
value: PropTypes.string,
|
63
|
+
setValue: PropTypes.func.isRequired,
|
64
|
+
};
|
65
|
+
DescriptionField.defaultProps = {
|
66
|
+
value: '',
|
67
|
+
};
|
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { FormGroup, TextInput } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { helpLabel } from '../form/FormHelpers';
|
6
|
+
import { formatter } from '../form/Formatter';
|
7
|
+
import { NumberInput } from '../form/NumberInput';
|
6
8
|
|
7
9
|
export const EffectiveUserField = ({ value, setValue }) => (
|
8
10
|
<FormGroup
|
@@ -26,25 +28,25 @@ export const EffectiveUserField = ({ value, setValue }) => (
|
|
26
28
|
);
|
27
29
|
|
28
30
|
export const TimeoutToKillField = ({ value, setValue }) => (
|
29
|
-
<
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
<NumberInput
|
32
|
+
formProps={{
|
33
|
+
label: __('Timeout to kill'),
|
34
|
+
labelIcon: helpLabel(
|
35
|
+
__(
|
36
|
+
'Time in seconds from the start on the remote host after which the job should be killed.'
|
37
|
+
),
|
38
|
+
'timeout-to-kill'
|
34
39
|
),
|
35
|
-
'timeout-to-kill'
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
onChange={newValue => setValue(newValue)}
|
46
|
-
/>
|
47
|
-
</FormGroup>
|
40
|
+
fieldId: 'timeout-to-kill',
|
41
|
+
}}
|
42
|
+
inputProps={{
|
43
|
+
value,
|
44
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
45
|
+
autoComplete: 'timeout-to-kill',
|
46
|
+
id: 'timeout-to-kill',
|
47
|
+
onChange: newValue => setValue(newValue),
|
48
|
+
}}
|
49
|
+
/>
|
48
50
|
);
|
49
51
|
|
50
52
|
export const PasswordField = ({ value, setValue }) => (
|
@@ -56,11 +58,11 @@ export const PasswordField = ({ value, setValue }) => (
|
|
56
58
|
),
|
57
59
|
'password'
|
58
60
|
)}
|
59
|
-
fieldId="password"
|
61
|
+
fieldId="job-password"
|
60
62
|
>
|
61
63
|
<TextInput
|
62
|
-
autoComplete="password"
|
63
|
-
id="password"
|
64
|
+
autoComplete="new-password" // to prevent firefox from autofilling the user password
|
65
|
+
id="job-password"
|
64
66
|
type="password"
|
65
67
|
placeholder="*****"
|
66
68
|
value={value}
|
@@ -114,51 +116,54 @@ export const EffectiveUserPasswordField = ({ value, setValue }) => (
|
|
114
116
|
);
|
115
117
|
|
116
118
|
export const ConcurrencyLevelField = ({ value, setValue }) => (
|
117
|
-
<
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
<NumberInput
|
120
|
+
formProps={{
|
121
|
+
label: __('Concurrency level'),
|
122
|
+
labelIcon: helpLabel(
|
123
|
+
__(
|
124
|
+
'Run at most N tasks at a time. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1.'
|
125
|
+
),
|
126
|
+
'concurrency-level'
|
122
127
|
),
|
123
|
-
'concurrency-level'
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
onChange={newValue => setValue(newValue)}
|
135
|
-
/>
|
136
|
-
</FormGroup>
|
128
|
+
fieldId: 'concurrency-level',
|
129
|
+
}}
|
130
|
+
inputProps={{
|
131
|
+
min: 1,
|
132
|
+
autoComplete: 'concurrency-level',
|
133
|
+
id: 'concurrency-level',
|
134
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
135
|
+
value,
|
136
|
+
onChange: newValue => setValue(newValue),
|
137
|
+
}}
|
138
|
+
/>
|
137
139
|
);
|
138
140
|
|
139
141
|
export const TimeSpanLevelField = ({ value, setValue }) => (
|
140
|
-
<
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
142
|
+
<NumberInput
|
143
|
+
formProps={{
|
144
|
+
label: __('Time span'),
|
145
|
+
labelIcon: helpLabel(
|
146
|
+
__(
|
147
|
+
'Distribute execution over N seconds. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1.'
|
148
|
+
),
|
149
|
+
'time-span'
|
145
150
|
),
|
146
|
-
'time-span'
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
onChange={newValue => setValue(newValue)}
|
158
|
-
/>
|
159
|
-
</FormGroup>
|
151
|
+
fieldId: 'time-span',
|
152
|
+
}}
|
153
|
+
inputProps={{
|
154
|
+
min: 1,
|
155
|
+
autoComplete: 'time-span',
|
156
|
+
id: 'time-span',
|
157
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
158
|
+
value,
|
159
|
+
onChange: newValue => setValue(newValue),
|
160
|
+
}}
|
161
|
+
/>
|
160
162
|
);
|
161
163
|
|
164
|
+
export const TemplateInputsFields = ({ inputs, value, setValue }) => (
|
165
|
+
<>{inputs?.map(input => formatter(input, value, setValue))}</>
|
166
|
+
);
|
162
167
|
EffectiveUserField.propTypes = {
|
163
168
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
164
169
|
setValue: PropTypes.func.isRequired,
|
@@ -179,3 +184,12 @@ ConcurrencyLevelField.propTypes = EffectiveUserField.propTypes;
|
|
179
184
|
ConcurrencyLevelField.defaultProps = EffectiveUserField.defaultProps;
|
180
185
|
TimeSpanLevelField.propTypes = EffectiveUserField.propTypes;
|
181
186
|
TimeSpanLevelField.defaultProps = EffectiveUserField.defaultProps;
|
187
|
+
TemplateInputsFields.propTypes = {
|
188
|
+
inputs: PropTypes.array.isRequired,
|
189
|
+
value: PropTypes.object,
|
190
|
+
setValue: PropTypes.func.isRequired,
|
191
|
+
};
|
192
|
+
|
193
|
+
TemplateInputsFields.defaultProps = {
|
194
|
+
value: {},
|
195
|
+
};
|
@@ -1,25 +1,144 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { Provider } from 'react-redux';
|
3
|
-
import configureMockStore from 'redux-mock-store';
|
4
|
-
import * as patternfly from '@patternfly/react-core';
|
5
3
|
import { mount } from '@theforeman/test';
|
6
|
-
import {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
import { fireEvent, screen, render, act } from '@testing-library/react';
|
5
|
+
import * as api from 'foremanReact/redux/API';
|
6
|
+
import { JobWizard } from '../../../JobWizard';
|
7
|
+
import * as selectors from '../../../JobWizardSelectors';
|
8
|
+
import {
|
9
|
+
jobTemplateResponse as jobTemplate,
|
10
|
+
testSetup,
|
11
|
+
mockApi,
|
12
|
+
} from '../../../__tests__/fixtures';
|
13
|
+
|
14
|
+
const store = testSetup(selectors, api);
|
15
|
+
mockApi(api);
|
16
|
+
|
17
|
+
jest.spyOn(selectors, 'selectEffectiveUser');
|
18
|
+
jest.spyOn(selectors, 'selectTemplateInputs');
|
19
|
+
jest.spyOn(selectors, 'selectAdvancedTemplateInputs');
|
20
|
+
|
21
|
+
selectors.selectEffectiveUser.mockImplementation(
|
22
|
+
() => jobTemplate.effective_user
|
23
|
+
);
|
24
|
+
selectors.selectTemplateInputs.mockImplementation(
|
25
|
+
() => jobTemplate.template_inputs
|
26
|
+
);
|
27
|
+
|
28
|
+
selectors.selectAdvancedTemplateInputs.mockImplementation(
|
29
|
+
() => jobTemplate.advanced_template_inputs
|
30
|
+
);
|
16
31
|
describe('AdvancedFields', () => {
|
17
|
-
it('
|
18
|
-
const
|
32
|
+
it('should save data between steps for advanced fields', async () => {
|
33
|
+
const wrapper = mount(
|
19
34
|
<Provider store={store}>
|
20
|
-
<
|
35
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
21
36
|
</Provider>
|
22
37
|
);
|
23
|
-
|
38
|
+
// setup
|
39
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
40
|
+
wrapper
|
41
|
+
.find('.pf-c-select__menu-item')
|
42
|
+
.first()
|
43
|
+
.simulate('click');
|
44
|
+
|
45
|
+
// test
|
46
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
47
|
+
0
|
48
|
+
);
|
49
|
+
wrapper
|
50
|
+
.find('.pf-c-wizard__nav-link')
|
51
|
+
.at(2)
|
52
|
+
.simulate('click'); // Advanced step
|
53
|
+
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
54
|
+
const advancedTemplateInput = () =>
|
55
|
+
wrapper.find('.pf-c-form__group-control textarea');
|
56
|
+
const effectiveUesrValue = 'effective user new value';
|
57
|
+
const advancedTemplateInputValue = 'advanced input new value';
|
58
|
+
effectiveUserInput().getDOMNode().value = effectiveUesrValue;
|
59
|
+
|
60
|
+
effectiveUserInput().simulate('change');
|
61
|
+
wrapper.update();
|
62
|
+
advancedTemplateInput().getDOMNode().value = advancedTemplateInputValue;
|
63
|
+
advancedTemplateInput().simulate('change');
|
64
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
65
|
+
expect(advancedTemplateInput().prop('value')).toEqual(
|
66
|
+
advancedTemplateInputValue
|
67
|
+
);
|
68
|
+
|
69
|
+
wrapper
|
70
|
+
.find('.pf-c-wizard__nav-link')
|
71
|
+
.at(1)
|
72
|
+
.simulate('click');
|
73
|
+
|
74
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-current').text()).toEqual(
|
75
|
+
'Target Hosts'
|
76
|
+
);
|
77
|
+
wrapper
|
78
|
+
.find('.pf-c-wizard__nav-link')
|
79
|
+
.at(2)
|
80
|
+
.simulate('click'); // Advanced step
|
81
|
+
|
82
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
83
|
+
expect(advancedTemplateInput().prop('value')).toEqual(
|
84
|
+
advancedTemplateInputValue
|
85
|
+
);
|
86
|
+
});
|
87
|
+
it('fill template fields', async () => {
|
88
|
+
render(
|
89
|
+
<Provider store={store}>
|
90
|
+
<JobWizard />
|
91
|
+
</Provider>
|
92
|
+
);
|
93
|
+
await act(async () => {
|
94
|
+
fireEvent.click(screen.getByText('Advanced Fields'));
|
95
|
+
});
|
96
|
+
const searchValue = 'search test';
|
97
|
+
const textValue = 'I am a text';
|
98
|
+
const dateValue = '08/07/2021';
|
99
|
+
const textField = screen.getByLabelText('adv plain hidden', {
|
100
|
+
selector: 'textarea',
|
101
|
+
});
|
102
|
+
const selectField = screen.getByText('option 1');
|
103
|
+
const searchField = screen.getByPlaceholderText('Filter...');
|
104
|
+
const dateField = screen.getByLabelText('adv date', {
|
105
|
+
selector: 'input',
|
106
|
+
});
|
107
|
+
|
108
|
+
fireEvent.click(selectField);
|
109
|
+
await act(async () => {
|
110
|
+
await fireEvent.click(screen.getByText('option 2'));
|
111
|
+
fireEvent.click(screen.getAllByText('Advanced Fields')[0]); // to remove focus
|
112
|
+
await fireEvent.change(textField, {
|
113
|
+
target: { value: textValue },
|
114
|
+
});
|
115
|
+
|
116
|
+
await fireEvent.change(searchField, {
|
117
|
+
target: { value: searchValue },
|
118
|
+
});
|
119
|
+
await fireEvent.change(dateField, {
|
120
|
+
target: { value: dateValue },
|
121
|
+
});
|
122
|
+
});
|
123
|
+
expect(
|
124
|
+
screen.getByLabelText('adv plain hidden', {
|
125
|
+
selector: 'textarea',
|
126
|
+
}).value
|
127
|
+
).toBe(textValue);
|
128
|
+
expect(searchField.value).toBe(searchValue);
|
129
|
+
expect(dateField.value).toBe(dateValue);
|
130
|
+
await act(async () => {
|
131
|
+
fireEvent.click(screen.getByText('Category and Template'));
|
132
|
+
});
|
133
|
+
expect(screen.getAllByText('Category and Template')).toHaveLength(3);
|
134
|
+
|
135
|
+
await act(async () => {
|
136
|
+
fireEvent.click(screen.getByText('Advanced Fields'));
|
137
|
+
});
|
138
|
+
expect(textField.value).toBe(textValue);
|
139
|
+
expect(searchField.value).toBe(searchValue);
|
140
|
+
expect(dateField.value).toBe(dateValue);
|
141
|
+
expect(screen.queryAllByText('option 1')).toHaveLength(0);
|
142
|
+
expect(screen.queryAllByText('option 2')).toHaveLength(1);
|
24
143
|
});
|
25
144
|
});
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { mount } from '@theforeman/test';
|
3
|
+
import { DescriptionField } from '../DescriptionField';
|
4
|
+
|
5
|
+
describe('DescriptionField', () => {
|
6
|
+
it('rendring', () => {
|
7
|
+
const component = mount(
|
8
|
+
<DescriptionField
|
9
|
+
inputs={[{ name: 'command' }]}
|
10
|
+
value="Run %{command}"
|
11
|
+
setValue={jest.fn()}
|
12
|
+
/>
|
13
|
+
);
|
14
|
+
const preview = component.find('#description-preview').hostNodes();
|
15
|
+
const findLink = () => component.find('.pf-m-link.pf-m-inline');
|
16
|
+
expect(findLink().text()).toEqual('Edit job description template');
|
17
|
+
expect(preview.props().value).toEqual('Run command');
|
18
|
+
findLink().simulate('click');
|
19
|
+
const description = component.find('#description').hostNodes();
|
20
|
+
expect(description.props().value).toEqual('Run %{command}');
|
21
|
+
expect(findLink().text()).toEqual('Preview job description');
|
22
|
+
});
|
23
|
+
});
|
@@ -1,52 +1,123 @@
|
|
1
|
-
import
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
const
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import { fireEvent, screen, render, act } from '@testing-library/react';
|
4
|
+
import * as api from 'foremanReact/redux/API';
|
5
|
+
import { JobWizard } from '../../JobWizard';
|
6
|
+
import * as selectors from '../../JobWizardSelectors';
|
7
|
+
import { testSetup, mockApi } from '../../__tests__/fixtures';
|
8
|
+
|
9
|
+
const store = testSetup(selectors, api);
|
10
|
+
mockApi(api);
|
11
|
+
jest.spyOn(selectors, 'selectCategoryError');
|
12
|
+
jest.spyOn(selectors, 'selectAllTemplatesError');
|
13
|
+
jest.spyOn(selectors, 'selectTemplateError');
|
14
|
+
|
15
|
+
describe('Category And Template', () => {
|
16
|
+
it('should select ', async () => {
|
17
|
+
selectors.selectCategoryError.mockImplementation(() => null);
|
18
|
+
selectors.selectAllTemplatesError.mockImplementation(() => null);
|
19
|
+
selectors.selectTemplateError.mockImplementation(() => null);
|
20
|
+
render(
|
21
|
+
<Provider store={store}>
|
22
|
+
<JobWizard />
|
23
|
+
</Provider>
|
24
|
+
);
|
25
|
+
|
26
|
+
expect(screen.queryAllByLabelText('Error')).toHaveLength(0);
|
27
|
+
expect(screen.queryAllByLabelText('failed')).toHaveLength(0);
|
28
|
+
// Category
|
29
|
+
fireEvent.click(
|
30
|
+
screen.getByLabelText('Ansible Commands', { selector: 'button' })
|
31
|
+
);
|
32
|
+
await act(async () => {
|
33
|
+
await fireEvent.click(screen.getByText('Puppet'));
|
34
|
+
});
|
35
|
+
fireEvent.click(screen.getAllByText('Category and Template')[0]); // to remove focus
|
36
|
+
expect(
|
37
|
+
screen.queryAllByLabelText('Ansible Commands', { selector: 'button' })
|
38
|
+
).toHaveLength(0);
|
39
|
+
expect(
|
40
|
+
screen.queryAllByLabelText('Puppet', { selector: 'button' })
|
41
|
+
).toHaveLength(1);
|
42
|
+
|
43
|
+
// Template
|
44
|
+
fireEvent.click(
|
45
|
+
screen.getByDisplayValue('template1', { selector: 'button' })
|
46
|
+
);
|
47
|
+
await act(async () => {
|
48
|
+
await fireEvent.click(screen.getByText('template2'));
|
49
|
+
});
|
50
|
+
fireEvent.click(screen.getAllByText('Category and Template')[0]); // to remove focus
|
51
|
+
expect(
|
52
|
+
screen.queryAllByDisplayValue('template1', { selector: 'button' })
|
53
|
+
).toHaveLength(0);
|
54
|
+
expect(
|
55
|
+
screen.queryAllByDisplayValue('template2', { selector: 'button' })
|
56
|
+
).toHaveLength(1);
|
57
|
+
});
|
58
|
+
it('category error ', async () => {
|
59
|
+
selectors.selectCategoryError.mockImplementation(() => 'category error');
|
60
|
+
selectors.selectAllTemplatesError.mockImplementation(() => null);
|
61
|
+
selectors.selectTemplateError.mockImplementation(() => null);
|
62
|
+
render(
|
63
|
+
<Provider store={store}>
|
64
|
+
<JobWizard />
|
65
|
+
</Provider>
|
66
|
+
);
|
67
|
+
|
68
|
+
expect(
|
69
|
+
screen.queryAllByText('Categories list failed with:', { exact: false })
|
70
|
+
).toHaveLength(1);
|
71
|
+
|
72
|
+
expect(
|
73
|
+
screen.queryAllByText('Templates list failed with:', { exact: false })
|
74
|
+
).toHaveLength(0);
|
75
|
+
expect(
|
76
|
+
screen.queryAllByText('Template failed with:', { exact: false })
|
77
|
+
).toHaveLength(0);
|
78
|
+
});
|
79
|
+
it('templates error ', async () => {
|
80
|
+
selectors.selectCategoryError.mockImplementation(() => null);
|
81
|
+
selectors.selectAllTemplatesError.mockImplementation(
|
82
|
+
() => 'templates error'
|
83
|
+
);
|
84
|
+
selectors.selectTemplateError.mockImplementation(() => null);
|
85
|
+
render(
|
86
|
+
<Provider store={store}>
|
87
|
+
<JobWizard />
|
88
|
+
</Provider>
|
89
|
+
);
|
90
|
+
|
91
|
+
expect(
|
92
|
+
screen.queryAllByText('Categories list failed with:', { exact: false })
|
93
|
+
).toHaveLength(0);
|
94
|
+
|
95
|
+
expect(
|
96
|
+
screen.queryAllByText('Templates list failed with:', { exact: false })
|
97
|
+
).toHaveLength(1);
|
98
|
+
expect(
|
99
|
+
screen.queryAllByText('Template failed with:', { exact: false })
|
100
|
+
).toHaveLength(0);
|
101
|
+
});
|
102
|
+
it('template error ', async () => {
|
103
|
+
selectors.selectCategoryError.mockImplementation(() => null);
|
104
|
+
selectors.selectAllTemplatesError.mockImplementation(() => null);
|
105
|
+
selectors.selectTemplateError.mockImplementation(() => 'template error');
|
106
|
+
render(
|
107
|
+
<Provider store={store}>
|
108
|
+
<JobWizard />
|
109
|
+
</Provider>
|
110
|
+
);
|
111
|
+
|
112
|
+
expect(
|
113
|
+
screen.queryAllByText('Categories list failed with:', { exact: false })
|
114
|
+
).toHaveLength(0);
|
115
|
+
|
116
|
+
expect(
|
117
|
+
screen.queryAllByText('Templates list failed with:', { exact: false })
|
118
|
+
).toHaveLength(0);
|
119
|
+
expect(
|
120
|
+
screen.queryAllByText('Template failed with:', { exact: false })
|
121
|
+
).toHaveLength(1);
|
122
|
+
});
|
52
123
|
});
|