# frozen_string_literal: true describe Instructeurs::DossiersController, type: :controller do render_views let(:instructeur) { create(:instructeur) } let(:administration) { create(:administration) } let(:instructeurs) { [instructeur] } let(:types_de_champ_public) { [] } let(:procedure) { create(:procedure, :published, :for_individual, instructeurs: instructeurs, types_de_champ_public:) } let(:procedure_accuse_lecture) { create(:procedure, :published, :for_individual, :accuse_lecture, :new_administrateur, instructeurs: instructeurs) } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:dossier_accuse_lecture) { create(:dossier, :en_construction, :with_individual, procedure: procedure_accuse_lecture) } let(:dossier_for_tiers) { create(:dossier, :en_construction, :for_tiers_with_notification, procedure: procedure) } let(:dossier_for_tiers_without_notif) { create(:dossier, :en_construction, :for_tiers_without_notification, procedure: procedure) } let(:fake_justificatif) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } before { sign_in(instructeur.user) } describe '#send_to_instructeurs' do let(:mail) { double("mail") } before do allow(mail).to receive(:deliver_later) allow(InstructeurMailer) .to receive(:send_dossier) .with(instructeur, dossier, recipient) .and_return(mail) post( :send_to_instructeurs, params: { recipients: [recipient.id], procedure_id: procedure.id, dossier_id: dossier.id } ) end context 'when the recipient belongs to the dossier groupe instructeur' do let(:recipient) { instructeur } it do expect(InstructeurMailer).to have_received(:send_dossier) expect(response).to redirect_to(personnes_impliquees_instructeur_dossier_url) expect(recipient.followed_dossiers).to include(dossier) end end context 'when the recipient is random' do let(:recipient) { create(:instructeur) } it do expect(InstructeurMailer).not_to have_received(:send_dossier) expect(response).to redirect_to(personnes_impliquees_instructeur_dossier_url) expect(recipient.followed_dossiers).not_to include(dossier) end end end describe '#follow' do let(:batch_operation) {} subject do batch_operation patch :follow, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it do subject expect(instructeur.followed_dossiers).to match([dossier]) expect(flash.notice).to eq('Dossier suivi') expect(response).to redirect_to(instructeur_procedure_path(dossier.procedure)) end it { expect { subject }.to change { dossier.reload.updated_at } } context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it do subject expect(instructeur.followed_dossiers).to eq([]) expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") end end end describe '#unfollow' do let(:batch_operation) {} before { instructeur.followed_dossiers << dossier } subject do batch_operation patch :unfollow, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it do subject expect(instructeur.followed_dossiers).to match([]) expect(flash.notice).to eq("Vous ne suivez plus le dossier nº #{dossier.id}") expect(response).to redirect_to(instructeur_procedure_path(dossier.procedure)) end it { expect { subject }.to change { dossier.reload.updated_at } } context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it do subject expect(instructeur.followed_dossiers).to eq([dossier]) expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") end end end describe '#archive' do let(:batch_operation) {} before do batch_operation patch :archive, params: { procedure_id: procedure.id, dossier_id: dossier.id } dossier.reload instructeur.follow(dossier) end it { expect(dossier.archived).to eq(true) } it { expect(response).to redirect_to(instructeur_procedure_path(dossier.procedure)) } context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(dossier.archived).to eq(false) } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#unarchive' do let(:batch_operation) {} before do batch_operation dossier.update(archived: true) patch :unarchive, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it { expect(dossier.reload.archived).to eq(false) } it { expect(response).to redirect_to(instructeur_procedure_path(dossier.procedure)) } context 'with dossier in batch_operation' do let!(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(dossier.reload.archived).to eq(true) } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#passer_en_instruction' do let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } let(:batch_operation) {} before do batch_operation sign_in(instructeur.user) post :passer_en_instruction, params: { procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream end it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) } it { expect(instructeur.follow?(dossier)).to be true } it { expect(response).to have_http_status(:ok) } it { expect(response.body).to include('header-top') } context 'when the dossier has already been put en_instruction' do let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } it 'warns about the error' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) expect(response).to have_http_status(:ok) expect(response.body).to include('Le dossier est déjà en instruction.') end end context 'when the dossier has already been closed' do let(:dossier) { create(:dossier, :accepte, procedure: procedure) } it 'doesn’t change the dossier state' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte)) end it 'warns about the error' do expect(response).to have_http_status(:ok) expect(response.body).to include('Le dossier est en ce moment accepté : il n’est pas possible de le passer en instruction.') end end context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#repasser_en_construction' do let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } let(:batch_operation) {} before do batch_operation sign_in(instructeur.user) post :repasser_en_construction, params: { procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream end it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction)) } it { expect(response).to have_http_status(:ok) } it { expect(response.body).to include('header-top') } context 'when the dossier has already been put en_construction' do let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } it 'warns about the error' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction)) expect(response).to have_http_status(:ok) expect(response.body).to include('Le dossier est déjà en construction.') end end context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(dossier.reload.state).to eq('en_instruction') } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#repasser_en_instruction' do let(:dossier) { create(:dossier, :refuse, procedure: procedure) } let(:batch_operation) {} let(:current_user) { instructeur.user } before do sign_in current_user batch_operation post :repasser_en_instruction, params: { procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream end context 'when the dossier is refuse' do it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) } it { expect(response).to have_http_status(:ok) } it { expect(response.body).to include('header-top') } end context 'when the dossier has already been put en_instruction' do let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } it 'warns about the error' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) expect(response).to have_http_status(:ok) expect(response.body).to include('Le dossier est déjà en instruction.') end end context 'when the dossier is accepte' do let(:dossier) { create(:dossier, :accepte, procedure: procedure) } it 'it is possible to go back to en_instruction as instructeur' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) expect(response).to have_http_status(:ok) end end context 'when the dossier is done and the user delete it' do let!(:dossier) { create(:dossier, :accepte, procedure: procedure, user: current_user, hidden_by_user_at: Time.zone.now) } it 'reveals the dossier' do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_instruction)) expect(dossier.reload.hidden_by_user_at).to be_nil end end context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(dossier.reload.state).to eq('refuse') } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#terminer' do context "with refuser" do before do dossier.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) end context 'simple refusal' do subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream } it 'change state to refuse' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) expect(dossier.justificatif_motivation).to_not be_attached end it 'Notification email is sent' do expect(NotificationMailer).to receive(:send_refuse_notification) .with(dossier).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) subject end it 'creates a commentaire' do expect { subject }.to change { Commentaire.count }.by(1) end end context 'refusal with a justificatif' do subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: :turbo_stream } it 'attachs a justificatif' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) expect(dossier.justificatif_motivation).to be_attached end it { expect(subject.body).to include('header-top') } end context 'with dossier in batch_operation' do let!(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream } it { expect { subject }.not_to change { dossier.reload.state } } it { is_expected.to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it 'flashes message' do subject expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") end end end context "with for_tiers" do before do dossier_for_tiers.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) end context 'without continuation' do subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier_for_tiers.id }, format: :turbo_stream } it 'Notification email is sent' do expect(NotificationMailer).to receive(:send_sans_suite_notification) .with(dossier_for_tiers).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) expect(NotificationMailer).to receive(:send_notification_for_tiers) .with(dossier_for_tiers).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) subject end it '2 emails are sent' do expect { perform_enqueued_jobs { subject } }.to change { ActionMailer::Base.deliveries.count }.by(2) end end end context "with for_tiers_without_notif" do before do dossier_for_tiers_without_notif.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) end context 'without continuation' do subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier_for_tiers_without_notif.id }, format: :turbo_stream } it 'Notification email is sent' do expect(NotificationMailer).to receive(:send_sans_suite_notification) .with(dossier_for_tiers_without_notif).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) expect(NotificationMailer).to receive(:send_notification_for_tiers) .with(dossier_for_tiers_without_notif).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) subject end it 'only one email is sent' do expect { perform_enqueued_jobs { subject } }.to change { ActionMailer::Base.deliveries.count }.by(1) end end end context "with accuse de lecture procedure" do before do dossier_accuse_lecture.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) end context 'with classer_sans_suite' do subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure_accuse_lecture.id, dossier_id: dossier_accuse_lecture.id }, format: :turbo_stream } it 'Notification accuse de lecture email is sent and not the others' do expect(NotificationMailer).to receive(:send_accuse_lecture_notification) .with(dossier_accuse_lecture).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) expect(NotificationMailer).not_to receive(:send_sans_suite_notification) .with(dossier_accuse_lecture) subject end it { expect(subject.body).to include('header-top') } it 'creates a commentaire' do expect { subject }.to change { Commentaire.count }.by(1) expect(dossier_accuse_lecture.commentaires.last.body).to eq("

Bonjour,

Nous vous informons qu'une décision sur votre dossier a été rendue.

Cordialement,
#{procedure_accuse_lecture.service.nom}") end end end context "with classer_sans_suite" do before do dossier.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) end context 'without attachment' do subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream } it 'change state to sans_suite' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) expect(dossier.justificatif_motivation).to_not be_attached end it 'Notification email is sent' do expect(NotificationMailer).to receive(:send_sans_suite_notification) .with(dossier).and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) expect(NotificationMailer).not_to receive(:send_notification_for_tiers) .with(dossier) subject end it { expect(subject.body).to include('header-top') } end context 'with attachment' do subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: :turbo_stream } it 'change state to sans_suite' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) expect(dossier.justificatif_motivation).to be_attached end it { expect(subject.body).to include('header-top') } end end context "with accepter" do before do dossier.passer_en_instruction!(instructeur: instructeur) sign_in(instructeur.user) expect(NotificationMailer).to receive(:send_accepte_notification) .with(dossier) .and_return(NotificationMailer) expect(NotificationMailer).to receive(:deliver_later) end subject { post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream } it 'change state to accepte' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) expect(dossier.justificatif_motivation).to_not be_attached end context 'when the dossier does not have any attestation' do it 'Notification email is sent' do subject end end context 'when the dossier has an attestation' do before do attestation = Attestation.new allow(attestation).to receive(:pdf).and_return(double(read: 'pdf', size: 2.megabytes, attached?: false)) allow(attestation).to receive(:pdf_url).and_return('http://some_document_url') allow_any_instance_of(Dossier).to receive(:build_attestation).and_return(attestation) end it 'The instructeur is sent back to the dossier page' do expect(subject.body).to include('header-top') end context 'and the dossier has already an attestation' do it 'should not crash' do dossier.attestation = Attestation.new dossier.save expect(subject.body).to include('header-top') end end end context 'when the attestation template uses the motivation field' do let(:emailable) { false } let(:template) { create(:attestation_template) } let(:procedure) { create(:procedure, :published, :for_individual, attestation_template: template, instructeurs: [instructeur]) } subject do post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { motivation: "Yallah" } }, format: :turbo_stream end before do expect_any_instance_of(AttestationTemplate) .to receive(:attestation_for) .with(have_attributes(motivation: "Yallah")) end it { subject } end context 'with an attachment' do subject { post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: :turbo_stream } it 'change state to accepte' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) expect(dossier.justificatif_motivation).to be_attached end it { expect(subject.body).to include('header-top') } end end context 'when related etablissement is still in degraded_mode' do let(:procedure) { create(:procedure, :published, for_individual: false, instructeurs: instructeurs) } let(:dossier) { create(:dossier, :en_instruction, :with_entreprise, procedure: procedure, as_degraded_mode: true) } subject { post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id }, format: :turbo_stream } context "with accepter" do it 'warns about the error' do subject dossier.reload expect(dossier.state).to eq(Dossier.states.fetch(:en_instruction)) expect(response).to have_http_status(:ok) expect(response.body).to match(/Les données relatives au SIRET .+ de le passer accepté/) end end end context 'when a dossier is already closed' do let(:dossier) { create(:dossier, :accepte, procedure: procedure) } before { allow(dossier).to receive(:after_accepter) } subject { post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: :turbo_stream } it 'does not close it again' do subject expect(dossier).not_to have_received(:after_accepter) expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) expect(response).to have_http_status(:ok) expect(response.body).to include('Le dossier est déjà accepté.') end end end describe '#pending_correction' do let(:message) { 'do that' } let(:justificatif) { nil } let(:reason) { nil } subject do post :pending_correction, params: { procedure_id: procedure.id, dossier_id: dossier.id, dossier: { motivation: message, justificatif_motivation: justificatif }, reason: }, format: :turbo_stream end before do sign_in(instructeur.user) expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :messagerie) end context "dossier en instruction sends an email to user" do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:) } it { expect { subject }.to have_enqueued_mail(DossierMailer, :notify_pending_correction) } end context "dossier en instruction" do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } before { subject } it 'pass en_construction and create a pending correction' do expect(response).to have_http_status(:ok) expect(response.body).to include('en attente de correction') expect(dossier.reload).to be_en_construction expect(dossier).to be_pending_correction expect(dossier.corrections.last).to be_dossier_incorrect end it 'create a comment with text body' do expect(dossier.commentaires.last.body).to eq("do that") expect(dossier.commentaires.last).to be_flagged_pending_correction end context 'flagged as incomplete' do let(:reason) { 'incomplete' } it 'create a correction of incomplete reason' do expect(dossier.corrections.last).to be_dossier_incomplete end end context 'with an attachment' do let(:justificatif) { fake_justificatif } it 'attach file to comment' do expect(dossier.commentaires.last.piece_jointe).to be_attached end end context 'with an invalid comment / attachment' do let(:justificatif) { Rack::Test::UploadedFile.new(Rails.root.join('Gemfile.lock'), 'text/lock') } it 'does not save anything' do expect(dossier.reload).not_to be_pending_correction expect(dossier.commentaires.count).to eq(0) expect(response.body).to include('pas d’un type accepté') end end context 'with an empty message' do let(:message) { '' } it 'requires a message' do expect(dossier.reload).not_to be_pending_correction expect(dossier.commentaires.count).to eq(0) expect(response.body).to include('Vous devez préciser') end end context 'dossier already having pending corrections' do before do create(:dossier_correction, dossier:) end it 'does not create an new pending correction' do expect { subject }.not_to change { DossierCorrection.count } end it 'shows a flash alert' do subject expect(response.body).to include('') end end end context 'dossier en_construction' do it 'can create a pending correction' do subject expect(dossier.reload).to be_pending_correction expect(dossier.commentaires.last).to be_flagged_pending_correction end end context 'dossier is termine' do let(:dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure) } it 'does not create a pending correction' do expect { subject }.not_to change { DossierCorrection.count } expect(response.body).to include('Impossible') end end end describe '#messagerie' do before { expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :messagerie) } subject { get :messagerie, params: { procedure_id: procedure.id, dossier_id: dossier.id } } it { expect(subject).to have_http_status(:ok) } end describe "#create_commentaire" do let(:saved_commentaire) { dossier.commentaires.first } let(:body) { "avant\napres" } let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } let(:scan_result) { true } let(:now) { Timecop.freeze("09/11/1989") } subject { post :create_commentaire, params: { procedure_id: procedure.id, dossier_id: dossier.id, commentaire: { body: body, file: file } } } before do expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :messagerie) allow(ClamavService).to receive(:safe_file?).and_return(scan_result) Timecop.freeze(now) end after { Timecop.return } it "creates a commentaire" do expect { subject }.to change(Commentaire, :count).by(1) expect(instructeur.followed_dossiers).to include(dossier) expect(response).to redirect_to(messagerie_instructeur_dossier_path(dossier.procedure, dossier)) expect(flash.notice).to be_present expect(dossier.reload.last_commentaire_updated_at).to eq(now) end context "when the commentaire created with virus file" do let(:scan_result) { false } it "creates a commentaire (shows message that file have a virus)" do expect { subject }.to change(Commentaire, :count).by(1) expect(instructeur.followed_dossiers).to include(dossier) expect(response).to redirect_to(messagerie_instructeur_dossier_path(dossier.procedure, dossier)) expect(flash.notice).to be_present end end context "when the dossier is deleted by user" do let(:dossier) { create(:dossier, :accepte, procedure: procedure) } before do dossier.update!(hidden_by_user_at: 1.hour.ago) subject end it "does not create a commentaire" do expect { subject }.to change(Commentaire, :count).by(0) expect(flash.alert).to be_present end end end describe "#create_avis" do let(:expert) { create(:expert) } let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: dossier.procedure) } let(:invite_linked_dossiers) { false } let(:saved_avis) { dossier.avis.first } let!(:old_avis_count) { Avis.count } before do expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :avis) end subject do post :create_avis, params: { procedure_id: procedure.id, dossier_id: dossier.id, avis: { emails: emails, introduction: 'intro', confidentiel: true, invite_linked_dossiers: invite_linked_dossiers, claimant: instructeur, experts_procedure: experts_procedure } } end let(:emails) { ["email@a.com"] } context "notifications updates" do context 'when an instructeur follows the dossier' do let(:follower) { create(:instructeur) } before { follower.follow(dossier) } it 'the follower has a notification' do expect(follower.followed_dossiers.with_notifications).to eq([]) subject expect(follower.followed_dossiers.with_notifications).to eq([dossier.reload]) end end end context 'as an instructeur, i auto follow the dossier so I get the notifications' do it 'works' do subject expect(instructeur.followed_dossiers).to match_array([dossier]) end end context 'email sending' do before do subject end it { expect(saved_avis.expert.email).to eq('email@a.com') } it { expect(saved_avis.introduction).to eq('intro') } it { expect(saved_avis.confidentiel).to eq(true) } it { expect(saved_avis.dossier).to eq(dossier) } it { expect(saved_avis.claimant).to eq(instructeur) } it { expect(response).to redirect_to(avis_instructeur_dossier_path(dossier.procedure, dossier)) } context "with an invalid email" do let(:emails) { ["emaila.com"] } before { subject } it { expect(response).to render_template :avis } it { expect(flash.alert).to eq(["emaila.com : Le champ « Email » est invalide. Saisir une adresse électronique valide, exemple : adresse@mail.com"]) } it { expect { subject }.not_to change(Avis, :count) } it { expect(dossier.last_avis_updated_at).to eq(nil) } end context "with no email" do let(:emails) { [] } before { subject } it { expect(response).to render_template :avis } it { expect(flash.alert).to eq("Le champ « Emails » doit être rempli") } it { expect { subject }.not_to change(Avis, :count) } it { expect(dossier.last_avis_updated_at).to eq(nil) } end context 'with multiple emails' do let(:emails) { ["toto.fr", "titi@titimail.com"] } before { subject } it { expect(response).to render_template :avis } it { expect(flash.alert).to eq(["toto.fr : Le champ « Email » est invalide. Saisir une adresse électronique valide, exemple : adresse@mail.com"]) } it { expect(flash.notice).to eq("Une demande d’avis a été envoyée à titi@titimail.com") } it { expect(Avis.count).to eq(old_avis_count + 1) } it { expect(saved_avis.expert.email).to eq("titi@titimail.com") } end context 'when the expert do not want to receive notification' do let(:emails) { ["email@a.com"] } let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: dossier.procedure, notify_on_new_avis: false) } before { subject } end context 'with linked dossiers' do let(:asked_confidentiel) { false } let(:previous_avis_confidentiel) { false } let(:types_de_champ_public) { [{ type: :dossier_link }] } let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:) } before { subject } context 'when the expert doesn’t share linked dossiers' do let(:invite_linked_dossiers) { false } it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' do expect(flash.notice).to eq("Une demande d’avis a été envoyée à email@a.com") expect(Avis.count).to eq(old_avis_count + 1) expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end end context 'when the expert also shares the linked dossiers' do let(:invite_linked_dossiers) { true } context 'and the expert can access the linked dossiers' do let(:saved_avis) { Avis.last(2).first } let(:linked_avis) { Avis.last } let(:linked_dossier) { Dossier.find_by(id: dossier.champs.first.value) } let(:invite_linked_dossiers) do instructeur.assign_to_procedure(linked_dossier.procedure) true end it 'sends one avis for the main dossier' do expect(flash.notice).to eq("Une demande d’avis a été envoyée à email@a.com") expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end it 'sends another avis for the linked dossiers' do expect(Avis.count).to eq(old_avis_count + 2) expect(linked_avis.dossier).to eq(linked_dossier) end end context 'but the expert can’t access the linked dossier' do it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' do expect(flash.notice).to eq("Une demande d’avis a été envoyée à email@a.com") expect(Avis.count).to eq(old_avis_count + 1) expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end end end end end end describe "#show" do context "when the dossier is exported as PDF" do let(:instructeur) { create(:instructeur) } let(:expert) { create(:expert) } let(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) } let(:dossier) do create(:dossier, :accepte, :with_populated_champs, :with_populated_annotations, :with_motivation, :with_entreprise, :with_commentaires, procedure: procedure) end let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } subject do avis get :show, params: { procedure_id: procedure.id, dossier_id: dossier.id, format: :pdf } end before do expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :demande) subject end it { expect(assigns(:acls)).to eq(PiecesJustificativesService.new(user_profile: instructeur, export_template: nil).acl_for_dossier_export(dossier.procedure)) } it { expect(assigns(:is_dossier_in_batch_operation)).to eq(false) } it { expect(response).to render_template 'dossiers/show' } context 'empty champs commune' do let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :communes }], instructeurs:) } let(:dossier) { create(:dossier, :accepte, procedure:) } it { expect(response).to render_template 'dossiers/show' } end end context 'with dossier in batch_operation' do let!(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it 'assigns variable with true value' do get :show, params: { procedure_id: procedure.id, dossier_id: dossier.id } expect(assigns(:is_dossier_in_batch_operation)).to eq(true) end end end describe "#update_annotations" do let(:procedure) do create(:procedure, :published, types_de_champ_public:, types_de_champ_private:, instructeurs: instructeurs) end let(:types_de_champ_private) do [ { type: :multiple_drop_down_list }, { type: :linked_drop_down_list }, { type: :datetime }, { type: :repetition, children: [{}] }, { type: :drop_down_list, options: [:a, :b, :other] } ] end let(:types_de_champ_public) { [] } let(:dossier) { create(:dossier, :en_construction, :with_populated_annotations, procedure: procedure) } let(:another_instructeur) { create(:instructeur) } let(:now) { Time.zone.parse('01/01/2100') } let(:champ_multiple_drop_down_list) { dossier.project_champs_private.first } let(:champ_linked_drop_down_list) { dossier.project_champs_private.second } let(:champ_datetime) { dossier.project_champs_private.third } let(:champ_repetition) { dossier.project_champs_private.fourth } let(:champ_drop_down_list) { dossier.project_champs_private.fifth } context 'when no invalid champs_public' do context "with new values for champs_private" do before do expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :annotations_privees) another_instructeur.follow(dossier) Timecop.freeze(now) patch :update_annotations, params: params, format: :turbo_stream champ_multiple_drop_down_list.reload champ_linked_drop_down_list.reload champ_datetime.reload champ_repetition.reload champ_drop_down_list.reload end after do Timecop.return end let(:params) do { procedure_id: procedure.id, dossier_id: dossier.id, dossier: { champs_private_attributes: { champ_multiple_drop_down_list.public_id => { value: ['', 'val1', 'val2'] }, champ_datetime.public_id => { value: '2019-12-21T13:17' }, champ_linked_drop_down_list.public_id => { primary_value: 'primary', secondary_value: 'secondary' }, champ_repetition.rows.first.first.public_id => { value: 'text' }, champ_drop_down_list.public_id => { value: '__other__', value_other: 'other value' } } } } end it { expect(champ_multiple_drop_down_list.value).to eq('["val1","val2"]') expect(champ_linked_drop_down_list.primary_value).to eq('primary') expect(champ_linked_drop_down_list.secondary_value).to eq('secondary') expect(champ_datetime.value).to eq(Time.zone.parse('2019-12-21T13:17:00').iso8601) expect(champ_repetition.rows.first.first.value).to eq('text') expect(champ_drop_down_list.value).to eq('other value') expect(dossier.reload.last_champ_private_updated_at).to eq(now) expect(response).to have_http_status(200) assert_enqueued_jobs(1, only: DossierIndexSearchTermsJob) } it 'updates the annotations' do Timecop.travel(now + 1.hour) expect(instructeur.followed_dossiers.with_notifications).to eq([]) expect(another_instructeur.followed_dossiers.with_notifications).to eq([dossier.reload]) end end context "without new values for champs_private" do let(:params) do { procedure_id: procedure.id, dossier_id: dossier.id, dossier: { champs_private_attributes: {}, champs_public_attributes: { '0': { id: champ_multiple_drop_down_list.id, value: ['', 'val1', 'val2'] } } } } end it { expect(dossier.reload.last_champ_private_updated_at).to eq(nil) expect(response).to have_http_status(200) } end end after do Timecop.return end context "without new values for champs_private" do let(:params) do { procedure_id: procedure.id, dossier_id: dossier.id, dossier: { champs_private_attributes: {}, champs_public_attributes: { champ_multiple_drop_down_list.public_id => { value: ['', 'val1', 'val2'] } } } } end it { expect(dossier.reload.last_champ_private_updated_at).to eq(nil) expect(response).to have_http_status(200) } end context "with invalid champs_public (DecimalNumberChamp)" do let(:types_de_champ_public) do [ { type: :decimal_number } ] end let(:champ_decimal_number) { dossier.project_champs_public.first } let(:params) do { procedure_id: procedure.id, dossier_id: dossier.id, dossier: { champs_private_attributes: { champ_datetime.public_id => { value: '2024-03-30T07:03' } } } } end it 'update champs_private' do too_long_float = '3.1415' champ_decimal_number.update_column(:value, too_long_float) patch :update_annotations, params: params, format: :turbo_stream champ_datetime.reload expect(champ_datetime.value).to eq(Time.zone.parse('2024-03-30T07:03:00').iso8601) end end end describe "#telecharger_pjs" do subject do get :telecharger_pjs, params: { procedure_id: procedure.id, dossier_id: dossier.id } end before do allow_any_instance_of(PiecesJustificativesService).to receive(:generate_dossiers_export).with([dossier]).and_call_original end it 'includes an attachment' do expect(subject.headers['Content-Disposition']).to start_with('attachment; ') end it 'the attachment.zip is extractable' do content = ZipTricks::FileReader.read_zip_structure(io: StringIO.new(subject.body)) file_names = content.map(&:filename) expect(file_names.size).to eq(1) expect(file_names.first).to start_with("dossier-#{dossier.id}/export-") end end describe "#destroy" do let(:batch_operation) {} subject do batch_operation delete :destroy, params: { procedure_id: procedure.id, dossier_id: dossier.id } end before do dossier.passer_en_instruction(instructeur: instructeur) end context 'just before delete the dossier, the operation must be equal to 2' do before do dossier.accepter!(instructeur: instructeur, motivation: 'le dossier est correct') end it 'has 2 operations logs before deletion' do expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(2) end end context 'when the instructeur want to delete a dossier with a decision and already hidden by user' do before do dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct") dossier.update!(hidden_by_user_at: Time.zone.now.beginning_of_day.utc) subject end it 'deletes previous logs and add a suppression log' do expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(3) expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer') end it 'does not add a record into deleted_dossiers table' do expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) end it 'is not visible by administration' do expect(dossier.reload.visible_by_administration?).to be_falsy end end context 'when the instructeur want to delete a dossier with a decision and not hidden by user' do before do dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct") subject end it 'does not deletes previous logs and adds a suppression log' do expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(3) expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer') end it 'add a record into deleted_dossiers table' do expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) end it 'fill hidden by reason' do expect(dossier.reload.hidden_by_reason).not_to eq(nil) expect(dossier.reload.hidden_by_reason).to eq("instructeur_request") end end context 'when the instructeur want to delete a dossier without a decision' do before do subject end it 'does not delete the dossier' do expect { dossier.reload }.not_to raise_error # A deleted dossier would raise an ActiveRecord::RecordNotFound end it 'does not add a record into deleted_dossiers table' do expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) end end context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect { subject }.not_to change { dossier.reload.hidden_by_administration_at } } it { is_expected.to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it 'flashes message' do subject expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") end end end describe '#extend_conservation' do subject { post :extend_conservation, params: { procedure_id: procedure.id, dossier_id: dossier.id } } context 'when user logged in' do it 'works' do expect(subject).to redirect_to(instructeur_dossier_path(procedure, dossier)) end it 'extends conservation_extension by 1 month' do subject expect(dossier.reload.conservation_extension).to eq(1.month) end it 'flashed notice success' do subject expect(flash[:notice]).to eq(I18n.t('views.instructeurs.dossiers.archived_dossier')) end end context 'with dossier in batch_operation' do let!(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect { subject }.not_to change { dossier.reload.conservation_extension } } it { is_expected.to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it 'flashes message' do subject expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") end end end describe '#restore' do let(:instructeur) { create(:instructeur) } let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) } let!(:procedure) { create(:procedure, :published, :for_individual, instructeurs: [instructeur]) } let!(:dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first, hidden_by_administration_at: 1.hour.ago) } let(:batch_operation) {} before do sign_in(instructeur.user) batch_operation instructeur.groupe_instructeurs << gi_p1_1 patch :restore, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it "puts hidden_by_administration_at to nil" do expect(dossier.reload.hidden_by_administration_at).to eq(nil) end context 'with dossier in batch_operation' do let(:batch_operation) { create(:batch_operation, operation: :archiver, dossiers: [dossier], instructeur: instructeur) } it { expect(dossier.hidden_by_administration_at).not_to eq(nil) } it { expect(response).to redirect_to(instructeur_dossier_path(dossier.procedure, dossier)) } it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end describe '#extend_conservation and restore' do subject { post :extend_conservation_and_restore, params: { procedure_id: procedure.id, dossier_id: dossier.id } } before do dossier.update(hidden_by_expired_at: 1.hour.ago, hidden_by_reason: 'expired') end context 'when dossier has expired but was not hidden by anyone' do it 'works' do expect(subject).to redirect_to(instructeur_dossier_path(procedure, dossier)) end it 'extends conservation_extension by 1 month and let dossier not hidden' do subject expect(dossier.reload.conservation_extension).to eq(1.month) expect(dossier.reload.hidden_by_reason).to eq(nil) expect(dossier.reload.hidden_by_expired_at).to eq(nil) expect(dossier.reload.hidden_by_administration_at).to eq(nil) expect(dossier.reload.hidden_by_user_at).to eq(nil) end it 'flashed notice success' do subject expect(flash[:notice]).to eq(I18n.t('views.instructeurs.dossiers.archived_dossier')) end end context 'when dossier has expired and was hidden by instructeur' do let!(:dossier) { create(:dossier, :hidden_by_administration, :accepte, :with_individual, procedure: procedure) } it 'extends conservation_extension by 1 month and restore dossier for instructeur' do subject expect(dossier.reload.conservation_extension).to eq(1.month) expect(dossier.reload.hidden_by_reason).to eq(nil) expect(dossier.reload.hidden_by_expired_at).to eq(nil) expect(dossier.reload.hidden_by_administration_at).to eq(nil) expect(dossier.reload.hidden_by_user_at).to eq(nil) end end context 'when dossier has expired and was hidden by user' do let!(:dossier) { create(:dossier, :hidden_by_user, :accepte, :with_individual, procedure: procedure) } it 'extends conservation_extension by 1 month and let dossier hidden for user' do subject expect(dossier.reload.conservation_extension).to eq(1.month) expect(dossier.reload.hidden_by_reason).to eq("user_request") expect(dossier.reload.hidden_by_expired_at).to eq(nil) expect(dossier.reload.hidden_by_administration_at).to eq(nil) expect(dossier.reload.hidden_by_user_at).not_to eq(nil) end end context 'when dossier has expired and was hidden by user and instructeur' do let!(:dossier) { create(:dossier, :hidden_by_user, :hidden_by_administration, :accepte, :with_individual, procedure: procedure) } it 'extends conservation_extension by 1 month and let dossier hidden for user' do subject expect(dossier.reload.conservation_extension).to eq(1.month) expect(dossier.reload.hidden_by_reason).to eq("user_request") expect(dossier.reload.hidden_by_expired_at).to eq(nil) expect(dossier.reload.hidden_by_administration_at).to eq(nil) expect(dossier.reload.hidden_by_user_at).not_to eq(nil) end end end describe '#reaffectation' do let!(:gi_2) { GroupeInstructeur.create(label: 'deuxième groupe', procedure: procedure) } let!(:gi_3) { GroupeInstructeur.create(label: 'troisième groupe', procedure: procedure) } let!(:dossier) { create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.reorder(:id).first) } before do post :reaffectation, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it do expect(response).to have_http_status(:ok) expect(response.body).to include("Vous pouvez réaffecter le dossier nº #{dossier.id} à l’un des groupes d’instructeurs suivants.") expect(response.body).to include('2 groupes existent') end end describe '#reaffecter' do let!(:gi_1) { procedure.groupe_instructeurs.first } let!(:gi_2) { GroupeInstructeur.create(label: 'deuxième groupe', procedure: procedure) } let!(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1) } let!(:new_instructeur) { create(:instructeur) } before do gi_1.instructeurs << new_instructeur new_instructeur.followed_dossiers << dossier post :reaffecter, params: { procedure_id: procedure.id, dossier_id: dossier.id, groupe_instructeur_id: gi_2.id } end it do expect(dossier.reload.groupe_instructeur.id).to eq(gi_2.id) expect(dossier.forced_groupe_instructeur).to be_truthy expect(dossier.followers_instructeurs).to eq [] expect(dossier.dossier_assignment.previous_groupe_instructeur_id).to eq(gi_1.id) expect(dossier.dossier_assignment.previous_groupe_instructeur_label).to eq(gi_1.label) expect(dossier.dossier_assignment.groupe_instructeur_id).to eq(gi_2.id) expect(dossier.dossier_assignment.groupe_instructeur_label).to eq(gi_2.label) expect(dossier.dossier_assignment.mode).to eq('manual') expect(dossier.dossier_assignment.assigned_by).to eq(instructeur.email) expect(response).to redirect_to(instructeur_procedure_path(procedure)) expect(flash.notice).to eq("Le dossier nº #{dossier.id} a été réaffecté au groupe d’instructeurs « deuxième groupe ».") end end describe '#personnes_impliquees' do let(:routed_procedure) { create(:procedure, :routee, :published, :for_individual) } let(:gi_1) { routed_procedure.groupe_instructeurs.reorder(:id).first } let(:gi_2) { routed_procedure.groupe_instructeurs.reorder(:id).last } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: routed_procedure, groupe_instructeur: gi_1) } let(:new_instructeur) { create(:instructeur) } before do gi_1.instructeurs << new_instructeur gi_2.instructeurs << instructeur new_instructeur.followed_dossiers << dossier dossier.assign_to_groupe_instructeur(gi_2, DossierAssignment.modes.fetch(:manual), new_instructeur) get :personnes_impliquees, params: { procedure_id: routed_procedure.id, dossier_id: dossier.id } end it do expect(response.body).to include('a réaffecté ce dossier du groupe « défaut » au groupe « deuxième groupe »') end end describe '#print' do let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } subject do get :print, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it { expect(subject).to have_http_status(:ok) } end describe '#pieces_jointes' do let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :piece_justificative }], instructeurs:) } let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure: procedure) } let(:logo_path) { 'spec/fixtures/files/logo_test_procedure.png' } let(:rib_path) { 'spec/fixtures/files/RIB.pdf' } let(:commentaire) { create(:commentaire, dossier: dossier) } before do dossier.champs.first.piece_justificative_file.attach( io: File.open(logo_path), filename: "logo_test_procedure.png", content_type: "image/png", metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } ) commentaire.piece_jointe.attach( io: File.open(rib_path), filename: "RIB.pdf", content_type: "application/pdf", metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } ) get :pieces_jointes, params: { procedure_id: procedure.id, dossier_id: dossier.id } end it 'returns pieces jointes from champs and from messagerie' do expect(response.body).to include('Télécharger le fichier toto.txt') expect(response.body).to include('Télécharger le fichier logo_test_procedure.png') expect(response.body).to include('Télécharger le fichier RIB.pdf') expect(response.body).to include('Visualiser') expect(assigns(:gallery_attachments).count).to eq 3 expect(assigns(:gallery_attachments)).to all(be_a(ActiveStorage::Attachment)) expect([Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp, Commentaire]).to include(*assigns(:gallery_attachments).map { _1.record.class }) end end end