From 1e88d1a1d727b80c97ca86312b3f8cc96d58ff2a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 14 Oct 2022 13:52:05 +0200 Subject: [PATCH 01/13] chore(capybara): use same size in headless or not --- spec/support/capybara.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index b19a446fd..34dec9e8f 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -7,6 +7,7 @@ Capybara.register_driver :chrome do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument('--no-sandbox') unless ENV['SANDBOX'] options.add_argument('--mute-audio') + options.add_argument('--window-size=1440,900') download_path = Capybara.save_path # Chromedriver 77 requires setting this for headless mode on linux From 5d383454783ad1332b71ea36cc5768c3fdd47e42 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 24 Oct 2022 21:10:22 +0200 Subject: [PATCH 02/13] feat(system-spec): display js console error --- Gemfile | 1 + Gemfile.lock | 3 +++ README.md | 4 ++++ spec/support/capybara.rb | 6 ++++++ 4 files changed, 14 insertions(+) diff --git a/Gemfile b/Gemfile index b519e633a..ff6f05271 100644 --- a/Gemfile +++ b/Gemfile @@ -100,6 +100,7 @@ group :test do gem 'launchy' gem 'rails-controller-testing' gem 'rspec_junit_formatter' + gem 'selenium-devtools' gem 'selenium-webdriver' gem 'shoulda-matchers', require: false gem 'timecop' diff --git a/Gemfile.lock b/Gemfile.lock index 5f14aad5c..ef3ab8ebc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -660,6 +660,8 @@ GEM scss_lint (0.59.0) sass (~> 3.5, >= 3.5.5) selectize-rails (0.12.6) + selenium-devtools (0.106.0) + selenium-webdriver (~> 4.2) selenium-webdriver (4.5.0) childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) @@ -905,6 +907,7 @@ DEPENDENCIES sanitize-url sassc-rails scss_lint + selenium-devtools selenium-webdriver sentry-delayed_job sentry-rails diff --git a/README.md b/README.md index 8bf38b514..4ccc423d5 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ Pour exécuter les tests de l'application, plusieurs possibilités : NO_HEADLESS=1 bin/rspec spec/system +- Afficher les logs js en error issus de la console du navigateur `console.error('coucou')` + + JS_LOG=error bin/rspec spec/system + ### Ajout de taches à exécuter au déploiement rails generate after_party:task task_name diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 34dec9e8f..8bc563491 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -64,6 +64,12 @@ RSpec.configure do |config| config.before(:each, type: :system, js: true) do driven_by ENV['NO_HEADLESS'] ? :chrome : :headless_chrome + + if ENV['JS_LOG'].present? + page.driver.browser.on_log_event(:console) do |event| + puts event.args if event.type == ENV['JS_LOG'].downcase.to_sym + end + end end # Set the user preferred language before Javascript system specs. From 4ec13d652a88d5b120984ce15df5ee2ad63d8b78 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 14 Oct 2022 13:52:22 +0200 Subject: [PATCH 03/13] core(capybara): disable_animation --- spec/support/capybara.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 8bc563491..66ef268f7 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -48,6 +48,8 @@ Capybara.ignore_hidden_elements = false Capybara.enable_aria_label = true +Capybara.disable_animation = true + # Save a snapshot of the HTML page when an integration test fails Capybara::Screenshot.autosave_on_failure = true # Keep only the screenshots generated from the last failing test suite From 00d1552060c1bbdb930615f35a6865d6ff67a401 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 20 Oct 2022 17:03:54 +0200 Subject: [PATCH 04/13] clean(spec): remove useless autosave --- spec/system/users/brouillon_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/system/users/brouillon_spec.rb b/spec/system/users/brouillon_spec.rb index 54303225d..36e5fc1c3 100644 --- a/spec/system/users/brouillon_spec.rb +++ b/spec/system/users/brouillon_spec.rb @@ -114,15 +114,11 @@ describe 'The user' do fill_in('sub type de champ', with: 'un autre texte') end - expect(page).to have_content('Supprimer', count: 2) - wait_for_autosave - expect(page).to have_content('Supprimer', count: 2) within '.repetition .row:first-child' do click_on 'Supprimer l’élément' end - wait_for_autosave expect(page).to have_content('Supprimer', count: 1) end From f4f40ded6c15b9b48f8e420a477ba51d567f11ac Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 24 Oct 2022 20:03:44 +0200 Subject: [PATCH 05/13] refactor(js): send 'autosave:end' when there are no more pending promises --- app/javascript/controllers/autosave_controller.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/javascript/controllers/autosave_controller.ts b/app/javascript/controllers/autosave_controller.ts index ded2863da..93fd4f190 100644 --- a/app/javascript/controllers/autosave_controller.ts +++ b/app/javascript/controllers/autosave_controller.ts @@ -37,6 +37,7 @@ export class AutosaveController extends ApplicationController { #abortController?: AbortController; #latestPromise = Promise.resolve(); #needsRetry = false; + #pendingPromiseCount = 0; connect() { this.#latestPromise = Promise.resolve(); @@ -119,11 +120,15 @@ export class AutosaveController extends ApplicationController { } private didSucceed() { - this.globalDispatch('autosave:end'); + this.#pendingPromiseCount -= 1; + if (this.#pendingPromiseCount == 0) { + this.globalDispatch('autosave:end'); + } } private didFail(error: ResponseError) { this.#needsRetry = true; + this.#pendingPromiseCount -= 1; this.globalDispatch('autosave:error', { error }); } @@ -179,6 +184,8 @@ export class AutosaveController extends ApplicationController { } } + this.#pendingPromiseCount++; + return httpRequest(form.action, { method: 'patch', body: formData, From 5cadb70d3a050ec65dd045581a2fec4373ed80f0 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 24 Oct 2022 20:15:09 +0200 Subject: [PATCH 06/13] refactor(js): add 'debounced-empty' when there are no more debouncing call --- .../controllers/application_controller.ts | 13 ++++++++++++- .../controllers/autosave_status_controller.ts | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/javascript/controllers/application_controller.ts b/app/javascript/controllers/application_controller.ts index 5a5d6d5ad..01739b710 100644 --- a/app/javascript/controllers/application_controller.ts +++ b/app/javascript/controllers/application_controller.ts @@ -7,9 +7,20 @@ export class ApplicationController extends Controller { #debounced = new Map<() => void, () => void>(); protected debounce(fn: () => void, interval: number): void { + this.globalDispatch('debounced:added'); + let debounced = this.#debounced.get(fn); if (!debounced) { - debounced = debounce(fn.bind(this), interval); + let wrapper = () => { + fn.bind(this)(); + this.#debounced.delete(fn); + if (this.#debounced.size == 0) { + this.globalDispatch('debounced:empty'); + } + } + + debounced = debounce(wrapper.bind(this), interval); + this.#debounced.set(fn, debounced); } debounced(); diff --git a/app/javascript/controllers/autosave_status_controller.ts b/app/javascript/controllers/autosave_status_controller.ts index 99b6b9eb4..8a24aa0f4 100644 --- a/app/javascript/controllers/autosave_status_controller.ts +++ b/app/javascript/controllers/autosave_status_controller.ts @@ -32,6 +32,21 @@ export class AutosaveStatusController extends ApplicationController { this.onGlobal('autosave:error', (event) => this.didFail(event) ); + + this.onGlobal('debounced:added', () => this.debouncedAdded()); + this.onGlobal('debounced:empty', () => this.debouncedEmpty()); + } + + private debouncedAdded() { + const autosave = this.element as HTMLDivElement; + removeClass(autosave, 'debounced-empty'); + addClass(autosave, 'debounced-added'); + } + + private debouncedEmpty() { + const autosave = this.element as HTMLDivElement; + addClass(autosave, 'debounced-empty'); + removeClass(autosave, 'debounced-added'); } onClickRetryButton() { From e71b7eeee5d4e0bde9863b3594d019be112e5924 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 14 Oct 2022 17:08:04 +0200 Subject: [PATCH 07/13] refactor(spec): wait for debounce and in flight promise --- spec/support/system_helpers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index 8a528617b..3047292e2 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -174,7 +174,8 @@ module SystemHelpers def wait_for_autosave(brouillon = true) blur - expect(page).to have_css('span', text: "#{brouillon ? 'Brouillon' : 'Dossier'} enregistré", visible: true) + expect(page).to have_css('.debounced-empty') # no more debounce + expect(page).to have_css('.autosave-state-idle') # no more in flight promise page.execute_script("document.documentElement.dispatchEvent(new CustomEvent('autosave:reset'));") end end From dbb5b7deec0b2de4c7ff04fa1e59f7c894838058 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 24 Oct 2022 21:15:30 +0200 Subject: [PATCH 08/13] clean(js): remove unused autosave:reset --- app/javascript/controllers/autosave_status_controller.ts | 6 ------ spec/support/system_helpers.rb | 1 - 2 files changed, 7 deletions(-) diff --git a/app/javascript/controllers/autosave_status_controller.ts b/app/javascript/controllers/autosave_status_controller.ts index 8a24aa0f4..b0df9e295 100644 --- a/app/javascript/controllers/autosave_status_controller.ts +++ b/app/javascript/controllers/autosave_status_controller.ts @@ -27,8 +27,6 @@ export class AutosaveStatusController extends ApplicationController { connect(): void { this.onGlobal('autosave:enqueue', () => this.didEnqueue()); this.onGlobal('autosave:end', () => this.didSucceed()); - // This event is used in tests to reset the state of the controller - this.onGlobal('autosave:reset', () => this.didReset()); this.onGlobal('autosave:error', (event) => this.didFail(event) ); @@ -63,10 +61,6 @@ export class AutosaveStatusController extends ApplicationController { this.debounce(this.hideSucceededStatus, AUTOSAVE_STATUS_VISIBLE_DURATION); } - private didReset() { - this.setState('idle'); - } - private didFail(event: CustomEvent<{ error: ResponseError }>) { const error = event.detail.error; diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index 3047292e2..eb0e006d8 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -176,7 +176,6 @@ module SystemHelpers blur expect(page).to have_css('.debounced-empty') # no more debounce expect(page).to have_css('.autosave-state-idle') # no more in flight promise - page.execute_script("document.documentElement.dispatchEvent(new CustomEvent('autosave:reset'));") end end From b65faafc30e6c1cd9c56d32cb2f23f2704180ab8 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 24 Oct 2022 21:52:38 +0200 Subject: [PATCH 09/13] improve stability --- config/environments/test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index 9d411b7fd..d58d3363b 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -76,7 +76,7 @@ Rails.application.configure do config.active_storage.service = :test config.ds_autosave = { - debounce_delay: 500, + debounce_delay: 0, status_visible_duration: 500 } From cf592820c6b487ee6dc6f1711fd5639548c5fb80 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 25 Oct 2022 10:56:22 +0200 Subject: [PATCH 10/13] make linter happier --- app/javascript/controllers/application_controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/controllers/application_controller.ts b/app/javascript/controllers/application_controller.ts index 01739b710..ed00950d5 100644 --- a/app/javascript/controllers/application_controller.ts +++ b/app/javascript/controllers/application_controller.ts @@ -11,13 +11,13 @@ export class ApplicationController extends Controller { let debounced = this.#debounced.get(fn); if (!debounced) { - let wrapper = () => { + const wrapper = () => { fn.bind(this)(); this.#debounced.delete(fn); if (this.#debounced.size == 0) { this.globalDispatch('debounced:empty'); } - } + }; debounced = debounce(wrapper.bind(this), interval); From 0a1ab733b64f6a7f563e9c7b3dccfc8a8b827fdf Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 25 Oct 2022 13:52:52 +0200 Subject: [PATCH 11/13] refactor(spec): stabilize api_particulier_spec --- .../api_particulier/api_particulier_spec.rb | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/spec/system/api_particulier/api_particulier_spec.rb b/spec/system/api_particulier/api_particulier_spec.rb index 97395d90b..42ac7a00a 100644 --- a/spec/system/api_particulier/api_particulier_spec.rb +++ b/spec/system/api_particulier/api_particulier_spec.rb @@ -279,20 +279,18 @@ describe 'fetch API Particulier Data', js: true do dossier = Dossier.last cnaf_champ = dossier.champs.find(&:cnaf?) - expect(cnaf_champ.code_postal).to eq('wrong_code') + wait_until { cnaf_champ.reload.code_postal == 'wrong_code' } click_on 'Déposer le dossier' expect(page).to have_content(/code postal doit posséder 5 caractères/) VCR.use_cassette('api_particulier/success/composition_familiale') do - perform_enqueued_jobs do - fill_in 'Le code postal', with: code_postal - wait_for_autosave - end + fill_in 'Le code postal', with: code_postal + wait_for_autosave + click_on 'Déposer le dossier' + perform_enqueued_jobs end - click_on 'Déposer le dossier' - visit demande_dossier_path(dossier) expect(page).to have_content(/Des données.*ont été reçues depuis la CAF/) @@ -335,19 +333,18 @@ describe 'fetch API Particulier Data', js: true do dossier = Dossier.last pole_emploi_champ = dossier.champs.find(&:pole_emploi?) - expect(pole_emploi_champ.identifiant).to eq('wrong code') + wait_until { pole_emploi_champ.reload.identifiant == 'wrong code' } + clear_enqueued_jobs pole_emploi_champ.update(external_id: nil, identifiant: nil) VCR.use_cassette('api_particulier/success/situation_pole_emploi') do - perform_enqueued_jobs do - fill_in "Identifiant", with: identifiant - wait_until { pole_emploi_champ.reload.external_id.present? } - end + fill_in "Identifiant", with: identifiant + wait_until { pole_emploi_champ.reload.external_id.present? } + click_on 'Déposer le dossier' + perform_enqueued_jobs end - click_on 'Déposer le dossier' - visit demande_dossier_path(dossier) expect(page).to have_content(/Des données.*ont été reçues depuis Pôle emploi/) @@ -406,19 +403,17 @@ describe 'fetch API Particulier Data', js: true do dossier = Dossier.last mesri_champ = dossier.champs.find(&:mesri?) - expect(mesri_champ.ine).to eq('wrong code') + wait_until { mesri_champ.reload.ine == 'wrong code' } clear_enqueued_jobs mesri_champ.update(external_id: nil, ine: nil) VCR.use_cassette('api_particulier/success/etudiants') do - perform_enqueued_jobs do - fill_in "INE", with: ine - wait_until { mesri_champ.reload.external_id.present? } - end + fill_in "INE", with: ine + wait_until { mesri_champ.reload.external_id.present? } + click_on 'Déposer le dossier' + perform_enqueued_jobs end - click_on 'Déposer le dossier' - visit demande_dossier_path(dossier) expect(page).to have_content(/Des données.*ont été reçues depuis le MESRI/) @@ -469,20 +464,18 @@ describe 'fetch API Particulier Data', js: true do dossier = Dossier.last dgfip_champ = dossier.champs.find(&:dgfip?) - expect(dgfip_champ.reference_avis).to eq('wrong_code') + wait_until { dgfip_champ.reload.reference_avis == 'wrong_code' } click_on 'Déposer le dossier' expect(page).to have_content(/reference avis doit posséder 13 ou 14 caractères/) VCR.use_cassette('api_particulier/success/avis_imposition') do - perform_enqueued_jobs do - fill_in "La référence d'avis d'imposition", with: reference_avis - wait_for_autosave - end + fill_in "La référence d'avis d'imposition", with: reference_avis + wait_for_autosave + click_on 'Déposer le dossier' + perform_enqueued_jobs end - click_on 'Déposer le dossier' - visit demande_dossier_path(dossier) expect(page).to have_content(/Des données.*ont été reçues depuis la DGFiP/) From 6af4fe624abe0495ad1d3113e03fdc73839a9b3a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 25 Oct 2022 14:35:36 +0200 Subject: [PATCH 12/13] refactor(service): explicit call to service job to avoid test side effect --- .../administrateurs/services_controller.rb | 6 ++++++ app/models/service.rb | 4 ---- .../services_controller_spec.rb | 6 ++++-- spec/models/service_spec.rb | 19 ------------------- 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/app/controllers/administrateurs/services_controller.rb b/app/controllers/administrateurs/services_controller.rb index 1126f29f7..19567336e 100644 --- a/app/controllers/administrateurs/services_controller.rb +++ b/app/controllers/administrateurs/services_controller.rb @@ -15,6 +15,8 @@ module Administrateurs @service.administrateur = current_administrateur if @service.save + @service.enqueue_api_entreprise + redirect_to admin_services_path(procedure_id: params[:procedure_id]), notice: "#{@service.nom} créé" else @@ -33,6 +35,10 @@ module Administrateurs @service = service if @service.update(service_params) + if @service.siret_previously_changed? + @service.enqueue_api_entreprise + end + redirect_to admin_services_path(procedure_id: params[:procedure_id]), notice: "#{@service.nom} modifié" else diff --git a/app/models/service.rb b/app/models/service.rb index bac400e41..5bd98d3a5 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -45,8 +45,6 @@ class Service < ApplicationRecord validates :adresse, presence: { message: 'doit être renseignée' }, allow_nil: false validates :administrateur, presence: { message: 'doit être renseigné' }, allow_nil: false - after_commit :enqueue_api_entreprise, if: -> { siret_previously_changed? } - def clone_and_assign_to_administrateur(administrateur) service_cloned = self.dup service_cloned.administrateur = administrateur @@ -67,8 +65,6 @@ class Service < ApplicationRecord [etablissement_lat, etablissement_lng] end - private - def enqueue_api_entreprise APIEntreprise::ServiceJob.perform_later(self.id) end diff --git a/spec/controllers/administrateurs/services_controller_spec.rb b/spec/controllers/administrateurs/services_controller_spec.rb index 71c075ae9..121188c1f 100644 --- a/spec/controllers/administrateurs/services_controller_spec.rb +++ b/spec/controllers/administrateurs/services_controller_spec.rb @@ -34,7 +34,8 @@ describe Administrateurs::ServicesController, type: :controller do it { expect(Service.last.telephone).to eq('1234') } it { expect(Service.last.horaires).to eq('horaires') } it { expect(Service.last.adresse).to eq('adresse') } - it { expect(Service.last.siret).to eq('35600082800018') } + it { expect(Service.last.siret).to eq('35600082800018') } + it { expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(Service.last.id) } it { expect(response).to redirect_to(admin_services_path(procedure_id: procedure.id)) } end @@ -49,7 +50,7 @@ describe Administrateurs::ServicesController, type: :controller do describe '#update' do let!(:service) { create(:service, administrateur: admin) } - let(:service_params) { { nom: 'nom', type_organisme: Service.type_organismes.fetch(:association) } } + let(:service_params) { { nom: 'nom', type_organisme: Service.type_organismes.fetch(:association), siret: "13002526500013" } } before do sign_in(admin.user) @@ -67,6 +68,7 @@ describe Administrateurs::ServicesController, type: :controller do it { expect(Service.last.nom).to eq('nom') } it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:association)) } it { expect(response).to redirect_to(admin_services_path(procedure_id: procedure.id)) } + it { expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(service.id) } end context 'when updating a service with invalid data' do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 95abc24a6..c29313d1c 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -87,25 +87,6 @@ describe Service, type: :model do end end - describe "API Entreprise job" do - subject { create(:service) } - it "should enqueue a job when created" do - expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(subject.id) - end - - it "should enqueue a job when siret changed" do - subject.update(siret: "35600082800018") - expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(subject.id) - end - - it "should not enqueue a job when siret is unchanged" do - subject - clear_enqueued_jobs - subject.update(telephone: "09879789") - expect(APIEntreprise::ServiceJob).not_to have_been_enqueued - end - end - describe "etablissement adresse & geo coordinates" do subject { create(:service, etablissement_lat: latitude, etablissement_lng: longitude, etablissement_infos: etablissement_infos) } From d1a8731bfc367db899bd76177faf9de8563eb7b6 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 25 Oct 2022 14:36:58 +0200 Subject: [PATCH 13/13] refactor(spec): faster service spec --- .../services_controller_spec.rb | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/spec/controllers/administrateurs/services_controller_spec.rb b/spec/controllers/administrateurs/services_controller_spec.rb index 121188c1f..f2cbcbedf 100644 --- a/spec/controllers/administrateurs/services_controller_spec.rb +++ b/spec/controllers/administrateurs/services_controller_spec.rb @@ -25,18 +25,21 @@ describe Administrateurs::ServicesController, type: :controller do } end - it { expect(flash.alert).to be_nil } - it { expect(flash.notice).to eq('super service créé') } - it { expect(Service.last.nom).to eq('super service') } - it { expect(Service.last.organisme).to eq('organisme') } - it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:association)) } - it { expect(Service.last.email).to eq('email@toto.com') } - it { expect(Service.last.telephone).to eq('1234') } - it { expect(Service.last.horaires).to eq('horaires') } - it { expect(Service.last.adresse).to eq('adresse') } - it { expect(Service.last.siret).to eq('35600082800018') } - it { expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(Service.last.id) } - it { expect(response).to redirect_to(admin_services_path(procedure_id: procedure.id)) } + it do + expect(flash.alert).to be_nil + expect(flash.notice).to eq('super service créé') + expect(Service.last.nom).to eq('super service') + expect(Service.last.organisme).to eq('organisme') + expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:association)) + expect(Service.last.email).to eq('email@toto.com') + expect(Service.last.telephone).to eq('1234') + expect(Service.last.horaires).to eq('horaires') + expect(Service.last.adresse).to eq('adresse') + expect(Service.last.siret).to eq('35600082800018') + expect(APIEntreprise::ServiceJob).to have_been_enqueued.with(Service.last.id) + + expect(response).to redirect_to(admin_services_path(procedure_id: procedure.id)) + end end context 'when submitting an invalid service' do