foreman_remote_execution 4.6.0 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +7 -0
  3. data/app/controllers/job_invocations_controller.rb +1 -1
  4. data/app/controllers/ui_job_wizard_controller.rb +7 -0
  5. data/app/graphql/types/job_invocation.rb +16 -0
  6. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
  7. data/app/helpers/remote_execution_helper.rb +9 -3
  8. data/app/lib/actions/remote_execution/run_host_job.rb +5 -1
  9. data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
  10. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -0
  11. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  12. data/app/models/host_proxy_invocation.rb +4 -0
  13. data/app/models/host_status/execution_status.rb +3 -3
  14. data/app/models/job_invocation.rb +9 -6
  15. data/app/models/job_invocation_composer.rb +4 -4
  16. data/app/models/job_template.rb +1 -1
  17. data/app/models/remote_execution_feature.rb +5 -1
  18. data/app/models/setting/remote_execution.rb +2 -2
  19. data/app/models/targeting.rb +5 -1
  20. data/app/views/job_invocations/index.html.erb +1 -1
  21. data/app/views/templates/ssh/module_action.erb +1 -0
  22. data/app/views/templates/ssh/power_action.erb +2 -0
  23. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  24. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  25. data/foreman_remote_execution.gemspec +2 -3
  26. data/lib/foreman_remote_execution/engine.rb +3 -2
  27. data/lib/foreman_remote_execution/version.rb +1 -1
  28. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  29. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  30. data/test/unit/concerns/host_extensions_test.rb +4 -4
  31. data/test/unit/input_template_renderer_test.rb +1 -89
  32. data/test/unit/job_invocation_composer_test.rb +15 -13
  33. data/test/unit/job_invocation_test.rb +1 -1
  34. data/webpack/JobWizard/JobWizard.js +28 -8
  35. data/webpack/JobWizard/JobWizard.scss +39 -0
  36. data/webpack/JobWizard/JobWizardConstants.js +10 -0
  37. data/webpack/JobWizard/JobWizardSelectors.js +9 -0
  38. data/webpack/JobWizard/__tests__/fixtures.js +104 -2
  39. data/webpack/JobWizard/__tests__/integration.test.js +13 -85
  40. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
  41. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  42. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
  43. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
  44. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  45. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
  46. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  47. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  48. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  49. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  50. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  51. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  52. data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
  53. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  54. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  55. data/webpack/JobWizard/steps/form/SelectField.js +14 -2
  56. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  57. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  58. data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
  59. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +72 -66
  60. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
  61. data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
  62. data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
  63. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  64. data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
  65. metadata +25 -16
  66. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  67. data/test/models/orchestration/ssh_test.rb +0 -56
  68. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  69. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  70. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
  71. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  72. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  73. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  74. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  75. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
  76. data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ module Queries
4
+ class JobInvocationQueryTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query (
8
+ $id: String!
9
+ ) {
10
+ jobInvocation(id: $id) {
11
+ id
12
+ jobCategory
13
+ }
14
+ }
15
+ GRAPHQL
16
+ end
17
+
18
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
19
+
20
+ let(:global_id) { Foreman::GlobalId.for(job_invocation) }
21
+ let(:variables) { { id: global_id } }
22
+ let(:data) { result['data']['jobInvocation'] }
23
+
24
+ test 'fetching job invocation attributes' do
25
+ assert_empty result['errors']
26
+
27
+ assert_equal global_id, data['id']
28
+ assert_equal job_invocation.job_category, data['jobCategory']
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Queries
4
+ class JobInvocationsQueryTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query {
8
+ jobInvocations {
9
+ totalCount
10
+ nodes {
11
+ id
12
+ jobCategory
13
+ }
14
+ }
15
+ }
16
+ GRAPHQL
17
+ end
18
+
19
+ let(:data) { result['data']['jobInvocations'] }
20
+
21
+ setup do
22
+ FactoryBot.create_list(:job_invocation, 2)
23
+ end
24
+
25
+ test 'should fetch job invocations' do
26
+ assert_empty result['errors']
27
+
28
+ expected_count = JobInvocation.count
29
+
30
+ assert_not_equal 0, expected_count
31
+ assert_equal expected_count, data['totalCount']
32
+ assert_equal expected_count, data['nodes'].count
33
+ end
34
+ end
35
+ end
@@ -131,23 +131,23 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
131
131
  end
132
132
 
133
133
  context 'fallback strategy' do
134
- let(:host) { FactoryBot.build(:host, :with_puppet) }
134
+ let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
135
135
 
136
136
  context 'enabled' do
137
137
  before do
138
138
  Setting[:remote_execution_fallback_proxy] = true
139
- host.puppet_proxy.features << FactoryBot.create(:feature, :ssh)
139
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
140
140
  end
141
141
 
142
142
  it 'returns a fallback proxy' do
143
- host.remote_execution_proxies(provider)[:fallback].must_include host.puppet_proxy
143
+ host.remote_execution_proxies(provider)[:fallback].must_include host.subnet.tftp
144
144
  end
145
145
  end
146
146
 
147
147
  context 'disabled' do
148
148
  before do
149
149
  Setting[:remote_execution_fallback_proxy] = false
150
- host.puppet_proxy.features << FactoryBot.create(:feature, :ssh)
150
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
151
151
  end
152
152
 
153
153
  it 'returns no proxy' do
@@ -446,8 +446,7 @@ class InputTemplateRendererTest < ActiveSupport::TestCase
446
446
  before { User.current = FactoryBot.build(:user, :admin) }
447
447
  after { User.current = nil }
448
448
 
449
- let(:environment) { FactoryBot.create(:environment) }
450
- before { renderer.host = FactoryBot.create(:host, :environment => environment) }
449
+ before { renderer.host = FactoryBot.create(:host) }
451
450
 
452
451
  describe 'rendering' do
453
452
  it 'can\'t render the content without host since we don\'t have variable value in classification' do
@@ -497,91 +496,4 @@ class InputTemplateRendererTest < ActiveSupport::TestCase
497
496
  end
498
497
  end
499
498
  end
500
-
501
- context 'renderer for template with puppet parameter input used' do
502
- let(:template) { FactoryBot.build(:job_template, :template => 'echo "This is WebServer with nginx <%= input("nginx_version") -%>" > /etc/motd') }
503
- let(:renderer) { InputTemplateRenderer.new(template) }
504
-
505
- context 'with matching input defined' do
506
- before do
507
- renderer.template.template_inputs<< FactoryBot.build(:template_input,
508
- :name => 'nginx_version',
509
- :input_type => 'puppet_parameter',
510
- :puppet_parameter_name => 'version',
511
- :puppet_class_name => 'nginx')
512
- end
513
- let(:result) { renderer.render }
514
-
515
- describe 'rendering' do
516
- it 'can\'t render the content without host since we don\'t have host so no classification' do
517
- assert_not result
518
- end
519
-
520
- it 'registers an error' do
521
- result # let is lazy
522
- _(renderer.error_message).wont_be_nil
523
- _(renderer.error_message).wont_be_empty
524
- end
525
-
526
- context 'with host specified' do
527
- let(:environment) { FactoryBot.create(:environment) }
528
- before { renderer.host = FactoryBot.create(:host, :environment => environment) }
529
-
530
- describe 'rendering' do
531
- it 'can\'t render the content without host since we don\'t have puppet parameter in classification' do
532
- assert_not result
533
- end
534
-
535
- it 'registers an error' do
536
- result # let is lazy
537
- _(renderer.error_message).wont_be_nil
538
- _(renderer.error_message).wont_be_empty
539
- end
540
- end
541
-
542
- describe 'preview' do
543
- it 'should render preview' do
544
- _(renderer.preview).must_equal 'echo "This is WebServer with nginx $PUPPET_PARAMETER_INPUT[nginx_version]" > /etc/motd'
545
- end
546
- end
547
-
548
- context 'with existing puppet parameter with matching override' do
549
- let(:puppet_class) do
550
- puppetclass = FactoryBot.create(:puppetclass, :environments => [environment], :name => 'nginx')
551
- puppetclass.update_attribute(:hosts, [renderer.host])
552
- puppetclass
553
- end
554
- let(:lookup_key) do
555
- FactoryBot.create(:puppetclass_lookup_key, :as_smart_class_param,
556
- :key => 'version',
557
- :puppetclass => puppet_class,
558
- :path => 'fqdn',
559
- :override => true,
560
- :overrides => {"fqdn=#{renderer.host.fqdn}" => '1.4.7'})
561
- end
562
-
563
- describe 'rendering' do
564
- it 'renders the value from puppet parameter' do
565
- lookup_key
566
- _(result).must_equal 'echo "This is WebServer with nginx 1.4.7" > /etc/motd'
567
- end
568
- end
569
-
570
- describe 'preview' do
571
- it 'should render preview' do
572
- lookup_key
573
- _(renderer.preview).must_equal 'echo "This is WebServer with nginx 1.4.7" > /etc/motd'
574
- end
575
- end
576
- end
577
- end
578
-
579
- describe 'preview' do
580
- it 'should render preview' do
581
- _(renderer.preview).must_equal 'echo "This is WebServer with nginx $PUPPET_PARAMETER_INPUT[nginx_version]" > /etc/motd'
582
- end
583
- end
584
- end
585
- end
586
- end
587
499
  end
@@ -350,12 +350,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
350
350
  end
351
351
 
352
352
  describe '#available_bookmarks' do
353
- it 'obeys authorization' do
354
- composer
355
- Bookmark.expects(:authorized).with(:view_bookmarks).returns(Bookmark.where({}))
356
- composer.available_bookmarks
357
- end
358
-
359
353
  context 'there are hostgroups and hosts bookmark' do
360
354
  let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
361
355
  let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
@@ -864,7 +858,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
864
858
  it 'marks targeting as resolved if static' do
865
859
  created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
866
860
  assert created.targeting.resolved?
867
- assert_equal job_invocation.template_invocations_host_ids, created.targeting.host_ids
861
+ created.targeting.save
862
+ created.targeting.reload
863
+ assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
868
864
  end
869
865
 
870
866
  it 'takes randomized_ordering from the original job invocation when rerunning failed' do
@@ -874,6 +870,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
874
870
  composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
875
871
  assert composer.job_invocation.targeting.randomized_ordering
876
872
  end
873
+
874
+ it 'works with invalid hosts' do
875
+ host = job_invocation.targeting.hosts.first
876
+ ::Host::Managed.any_instance.stubs(:valid?).returns(false)
877
+ composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
878
+ targeting = composer.compose.job_invocation.targeting
879
+ targeting.save!
880
+ targeting.reload
881
+ assert targeting.valid?
882
+ assert_equal targeting.hosts.pluck(:id), [host.id]
883
+ end
877
884
  end
878
885
 
879
886
  describe '.for_feature' do
@@ -918,12 +925,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
918
925
 
919
926
  context 'with template in setting present' do
920
927
  before do
921
- FactoryBot.create(
922
- :setting,
923
- :name => 'remote_execution_form_job_template',
924
- :category => 'Setting::RemoteExecution',
925
- :value => setting_template.name
926
- )
928
+ Setting[:remote_execution_form_job_template] = setting_template.name
927
929
  end
928
930
 
929
931
  it 'should resolve category to the setting value' do
@@ -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).with_task.order('job_invocations.id DESC')
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,3 +1,4 @@
1
+ /* eslint-disable camelcase */
1
2
  import React, { useState, useEffect, useCallback } from 'react';
2
3
  import { useDispatch, useSelector } from 'react-redux';
3
4
  import { Wizard } from '@patternfly/react-core';
@@ -8,6 +9,7 @@ import CategoryAndTemplate from './steps/CategoryAndTemplate/';
8
9
  import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
9
10
  import { JOB_TEMPLATE } from './JobWizardConstants';
10
11
  import { selectTemplateError } from './JobWizardSelectors';
12
+ import Schedule from './steps/Schedule/';
11
13
  import './JobWizard.scss';
12
14
 
13
15
  export const JobWizard = () => {
@@ -16,13 +18,31 @@ export const JobWizard = () => {
16
18
  const [advancedValues, setAdvancedValues] = useState({});
17
19
  const dispatch = useDispatch();
18
20
 
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
- }, []);
21
+ const setDefaults = useCallback(
22
+ ({
23
+ data: {
24
+ advanced_template_inputs,
25
+ effective_user,
26
+ job_template: { executionTimeoutInterval, description_format },
27
+ },
28
+ }) => {
29
+ const advancedTemplateValues = {};
30
+ const advancedInputs = advanced_template_inputs;
31
+ if (advancedInputs) {
32
+ advancedInputs.forEach(input => {
33
+ advancedTemplateValues[input.name] = input?.default || '';
34
+ });
35
+ }
36
+ setAdvancedValues(currentAdvancedValues => ({
37
+ ...currentAdvancedValues,
38
+ effectiveUserValue: effective_user?.value || '',
39
+ timeoutToKill: executionTimeoutInterval || '',
40
+ templateValues: advancedTemplateValues,
41
+ description: description_format || '',
42
+ }));
43
+ },
44
+ []
45
+ );
26
46
  useEffect(() => {
27
47
  if (jobTemplateID) {
28
48
  dispatch(
@@ -72,7 +92,7 @@ export const JobWizard = () => {
72
92
  },
73
93
  {
74
94
  name: __('Schedule'),
75
- component: <p>Schedule</p>,
95
+ component: <Schedule />,
76
96
  canJumpTo: isTemplate,
77
97
  },
78
98
  {
@@ -10,5 +10,44 @@
10
10
  .advanced-fields-title {
11
11
  margin-bottom: 10px;
12
12
  }
13
+ #advanced-fields-job-template {
14
+ .foreman-search-field {
15
+ // Giving pf3 search bar a pf4 look
16
+ .search-bar {
17
+ display: block;
18
+ }
19
+ .input-group-btn {
20
+ display: none;
21
+ }
22
+ li {
23
+ font-size: 16px;
24
+ }
25
+ input {
26
+ font-size: 16px;
27
+ height: 36px;
28
+ }
29
+ .foreman-autocomplete .autocomplete-focus-shortcut {
30
+ top: 8px;
31
+ font-size: 16px;
32
+ }
33
+ .foreman-autocomplete .autocomplete-aux {
34
+ top: 8px;
35
+ font-size: 16px;
36
+ .autocomplete-clear-button {
37
+ font-size: 16px;
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ .schedule-tab {
45
+ input[type='radio'],
46
+ input[type='checkbox'] {
47
+ margin: 0;
48
+ }
49
+ .advanced-scheduling-button {
50
+ text-align: start;
51
+ }
13
52
  }
14
53
  }
@@ -1,6 +1,16 @@
1
+ import { translate as __ } from 'foremanReact/common/I18n';
1
2
  import { foremanUrl } from 'foremanReact/common/helpers';
2
3
 
3
4
  export const JOB_TEMPLATES = 'JOB_TEMPLATES';
4
5
  export const JOB_CATEGORIES = 'JOB_CATEGORIES';
5
6
  export const JOB_TEMPLATE = 'JOB_TEMPLATE';
6
7
  export const templatesUrl = foremanUrl('/api/v2/job_templates');
8
+
9
+ export const repeatTypes = {
10
+ noRepeat: __('Does not repeat'),
11
+ cronline: __('Cronline'),
12
+ monthly: __('Monthly'),
13
+ weekly: __('Weekly'),
14
+ daily: __('Daily'),
15
+ hourly: __('Hourly'),
16
+ };
@@ -36,3 +36,12 @@ export const selectTemplateError = state =>
36
36
 
37
37
  export const selectJobTemplate = state =>
38
38
  selectAPIResponse(state, JOB_TEMPLATE);
39
+
40
+ export const selectEffectiveUser = state =>
41
+ selectAPIResponse(state, JOB_TEMPLATE).effective_user;
42
+
43
+ export const selectAdvancedTemplateInputs = state =>
44
+ selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
45
+
46
+ export const selectTemplateInputs = state =>
47
+ selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
@@ -1,6 +1,8 @@
1
- const jobTemplate = {
1
+ import configureMockStore from 'redux-mock-store';
2
+
3
+ export const jobTemplate = {
2
4
  id: 178,
3
- name: 'Run Command - Ansible Default',
5
+ name: 'template1',
4
6
  template:
5
7
  "---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
6
8
  snippet: false,
@@ -23,4 +25,104 @@ export const jobTemplateResponse = {
23
25
  overridable: true,
24
26
  current_user: false,
25
27
  },
28
+ advanced_template_inputs: [
29
+ {
30
+ name: 'adv plain hidden',
31
+ required: true,
32
+ input_type: 'user',
33
+ description: 'some Description',
34
+ advanced: true,
35
+ value_type: 'plain',
36
+ resource_type: 'ansible_roles',
37
+ default: 'Default val',
38
+ hidden_value: true,
39
+ },
40
+ {
41
+ name: 'adv plain select',
42
+ required: false,
43
+ input_type: 'user',
44
+ options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
45
+ advanced: true,
46
+ value_type: 'plain',
47
+ resource_type: 'ansible_roles',
48
+ default: '',
49
+ hidden_value: false,
50
+ },
51
+ {
52
+ name: 'adv search',
53
+ required: false,
54
+ options: '',
55
+ advanced: true,
56
+ value_type: 'search',
57
+ resource_type: 'foreman_tasks/tasks',
58
+ default: '',
59
+ hidden_value: false,
60
+ },
61
+ {
62
+ name: 'adv date',
63
+ required: false,
64
+ options: '',
65
+ advanced: true,
66
+ value_type: 'date',
67
+ resource_type: 'ansible_roles',
68
+ default: '',
69
+ hidden_value: false,
70
+ },
71
+ ],
72
+ template_inputs: [
73
+ {
74
+ name: 'plain hidden',
75
+ required: true,
76
+ input_type: 'user',
77
+ description: 'some Description',
78
+ advanced: false,
79
+ value_type: 'plain',
80
+ resource_type: 'ansible_roles',
81
+ default: 'Default val',
82
+ hidden_value: true,
83
+ },
84
+ ],
85
+ };
86
+
87
+ export const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
88
+
89
+ export const testSetup = (selectors, api) => {
90
+ jest.spyOn(api, 'get');
91
+ jest.spyOn(selectors, 'selectJobTemplate');
92
+ jest.spyOn(selectors, 'selectJobTemplates');
93
+ jest.spyOn(selectors, 'selectJobCategories');
94
+ jest.spyOn(selectors, 'selectJobCategoriesStatus');
95
+
96
+ selectors.selectJobCategories.mockImplementation(() => jobCategories);
97
+ selectors.selectJobTemplates.mockImplementation(() => [
98
+ jobTemplate,
99
+ { ...jobTemplate, id: 2, name: 'template2' },
100
+ ]);
101
+ const mockStore = configureMockStore([]);
102
+ const store = mockStore({});
103
+ return store;
104
+ };
105
+
106
+ export const mockTemplate = selectors => {
107
+ selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
108
+ selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
109
+ };
110
+ export const mockApi = api => {
111
+ api.get.mockImplementation(({ handleSuccess, ...action }) => {
112
+ if (action.key === 'JOB_CATEGORIES') {
113
+ handleSuccess &&
114
+ handleSuccess({ data: { job_categories: jobCategories } });
115
+ } else if (action.key === 'JOB_TEMPLATE') {
116
+ handleSuccess &&
117
+ handleSuccess({
118
+ data: jobTemplateResponse,
119
+ });
120
+ } else if (action.key === 'JOB_TEMPLATES') {
121
+ handleSuccess &&
122
+ handleSuccess({
123
+ data: { results: [jobTemplate] },
124
+ });
125
+ }
126
+ return { type: 'get', ...action };
127
+ });
26
128
  };