foreman_remote_execution 4.6.0 → 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.
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
  };