demarches-normaliennes/spec/models/procedure_spec.rb
Christophe Robillard 16b8995191
extend procedure.all_revision_types_de_champ
for header section

Co-authored-by: mfo <mfo@users.noreply.github.com>
2024-11-14 14:45:39 +01:00

1937 lines
73 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
describe Procedure do
describe 'mail templates' do
subject { create(:procedure) }
it "returns expected classes" do
expect(subject.passer_en_construction_email_template).to be_a(Mails::InitiatedMail)
expect(subject.passer_en_instruction_email_template).to be_a(Mails::ReceivedMail)
expect(subject.accepter_email_template).to be_a(Mails::ClosedMail)
expect(subject.refuser_email_template).to be_a(Mails::RefusedMail)
expect(subject.classer_sans_suite_email_template).to be_a(Mails::WithoutContinuationMail)
expect(subject.repasser_en_instruction_email_template).to be_a(Mails::ReInstructedMail)
end
end
describe 'compute_dossiers_count' do
let(:procedure) { create(:procedure_with_dossiers, dossiers_count: 2, dossiers_count_computed_at: Time.zone.now - Procedure::DOSSIERS_COUNT_EXPIRING) }
it 'caches estimated_dossiers_count' do
procedure.dossiers.each(&:passer_en_construction!)
expect { procedure.compute_dossiers_count }.to change(procedure, :estimated_dossiers_count).from(nil).to(2)
expect { create(:dossier, procedure: procedure).passer_en_construction! }.not_to change(procedure, :estimated_dossiers_count)
Timecop.freeze(Time.zone.now + Procedure::DOSSIERS_COUNT_EXPIRING)
expect { procedure.compute_dossiers_count }.to change(procedure, :estimated_dossiers_count).from(2).to(3)
Timecop.return
end
end
describe 'initiated_mail' do
let(:procedure) { create(:procedure) }
subject { procedure }
context 'when initiated_mail is not customize' do
it { expect(subject.passer_en_construction_email_template.body).to eq(Mails::InitiatedMail.default_for_procedure(procedure).body) }
end
context 'when initiated_mail is customize' do
before :each do
subject.initiated_mail = Mails::InitiatedMail.new(body: 'sisi')
subject.save
subject.reload
end
it { expect(subject.passer_en_construction_email_template.body).to eq('sisi') }
end
context 'when initiated_mail is customize ... again' do
before :each do
subject.initiated_mail = Mails::InitiatedMail.new(body: 'toto')
subject.save
subject.reload
end
it { expect(subject.passer_en_construction_email_template.body).to eq('toto') }
it { expect(Mails::InitiatedMail.count).to eq(1) }
end
end
describe 'closed mail template body' do
let(:procedure) { create(:procedure, attestation_template: attestation_template) }
let(:attestation_template) { nil }
subject { procedure.accepter_email_template.rich_body.body.to_html }
context 'for procedures without an attestation' do
it { is_expected.not_to include('lien attestation') }
end
context 'for procedures with an attestation' do
let(:attestation_template) { build(:attestation_template, activated: activated) }
context 'when the attestation is inactive' do
let(:activated) { false }
it { is_expected.not_to include('lien attestation') }
end
context 'when the attestation is inactive' do
let(:activated) { true }
it { is_expected.to include('lien attestation') }
end
end
end
describe '#closed_mail_template_attestation_inconsistency_state' do
let(:procedure_without_attestation) { create(:procedure, closed_mail: closed_mail, attestation_template: nil) }
let(:procedure_with_active_attestation) do
create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: true))
end
let(:procedure_with_inactive_attestation) do
create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: false))
end
subject { procedure.closed_mail_template_attestation_inconsistency_state }
context 'with a custom mail template' do
context 'that contains a lien attestation tag' do
let(:closed_mail) { build(:closed_mail, body: '--lien attestation--') }
context 'when the procedure doesnt have an attestation' do
let(:procedure) { procedure_without_attestation }
it { is_expected.to eq(:extraneous_tag) }
end
context 'when the procedure has an active attestation' do
let(:procedure) { procedure_with_active_attestation }
it { is_expected.to be(nil) }
end
context 'when the procedure has an inactive attestation' do
let(:procedure) { procedure_with_inactive_attestation }
it { is_expected.to eq(:extraneous_tag) }
end
end
context 'that doesnt contain a lien attestation tag' do
let(:closed_mail) { build(:closed_mail) }
context 'when the procedure doesnt have an attestation' do
let(:procedure) { procedure_without_attestation }
it { is_expected.to be(nil) }
end
context 'when the procedure has an active attestation' do
let(:procedure) { procedure_with_active_attestation }
it { is_expected.to eq(:missing_tag) }
end
context 'when the procedure has an inactive attestation' do
let(:procedure) { procedure_with_inactive_attestation }
it { is_expected.to be(nil) }
end
end
end
context 'with the default mail template' do
let(:closed_mail) { nil }
context 'when the procedure doesnt have an attestation' do
let(:procedure) { procedure_without_attestation }
it { is_expected.to be(nil) }
end
context 'when the procedure has an active attestation' do
let(:procedure) { procedure_with_active_attestation }
it { is_expected.to be(nil) }
end
context 'when the procedure has an inactive attestation' do
let(:procedure) { procedure_with_inactive_attestation }
it { is_expected.to be(nil) }
end
end
end
describe 'scopes' do
let!(:procedure) { create(:procedure) }
let!(:discarded_procedure) { create(:procedure, :discarded) }
describe 'default_scope' do
subject { Procedure.all }
it { is_expected.to match_array([procedure]) }
end
end
describe 'validation' do
context 'libelle' do
it { is_expected.not_to allow_value(nil).for(:libelle) }
it { is_expected.not_to allow_value('').for(:libelle) }
it { is_expected.to allow_value('Demande de subvention').for(:libelle) }
end
context 'closing procedure' do
context 'without replacing procedure in DS' do
let(:procedure) { create(:procedure) }
context 'valid' do
before do
procedure.update!(closing_details: "Bonjour,\nLa démarche est désormais hébergée sur une autre plateforme\nCordialement", closing_reason: Procedure.closing_reasons.fetch(:other))
end
it { expect(procedure).to be_valid }
end
end
end
context 'description' do
it { is_expected.not_to allow_value(nil).for(:description) }
it { is_expected.not_to allow_value('').for(:description) }
it { is_expected.to allow_value('Description Demande de subvention').for(:description) }
end
context 'organisation' do
it { is_expected.to allow_value('URRSAF').for(:organisation) }
end
context 'administrateurs' do
it { is_expected.not_to allow_value([]).for(:administrateurs) }
end
context 'before_remove callback for minimal administrator presence' do
let(:procedure) { create(:procedure) }
it 'raises an error when trying to remove the last administrateur' do
expect(procedure.administrateurs.count).to eq(1)
expect {
procedure.administrateurs.destroy(procedure.administrateurs.first)
}.to raise_error(
ActiveRecord::RecordNotDestroyed,
"Cannot remove the last administrateur of procedure #{procedure.libelle} (#{procedure.id})"
)
end
end
context 'juridique' do
it { is_expected.not_to allow_value(nil).on(:publication).for(:cadre_juridique) }
it { is_expected.to allow_value('text').on(:publication).for(:cadre_juridique) }
context 'with deliberation' do
let(:procedure) { build(:procedure, cadre_juridique: nil, revisions: [build(:procedure_revision)]) }
it { expect(procedure.valid?(:publication)).to eq(false) }
context 'when the deliberation is uploaded ' do
before do
procedure.deliberation = fixture_file_upload('spec/fixtures/files/file.pdf', 'application/pdf')
end
it { expect(procedure.valid?(:publication)).to eq(true) }
end
context 'when the deliberation is uploaded with an unauthorized format' do
before do
procedure.deliberation = fixture_file_upload('spec/fixtures/files/french-flag.gif', 'image/gif')
end
it { expect(procedure.valid?(:publication)).to eq(false) }
end
end
context 'when juridique_required is false' do
let(:procedure) { build(:procedure, juridique_required: false, cadre_juridique: nil) }
it { expect(procedure.valid?(:publication)).to eq(true) }
end
end
context 'api_entreprise_token' do
let(:valid_token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
let(:invalid_token) { 'plouf' }
it { is_expected.to allow_value(valid_token).for(:api_entreprise_token) }
it { is_expected.not_to allow_value(invalid_token).for(:api_entreprise_token) }
end
context 'api_particulier_token' do
let(:valid_token) { "3841b13fa8032ed3c31d160d3437a76a" }
let(:invalid_token) { 'jet0n 1nvalide' }
it { is_expected.to allow_value(valid_token).for(:api_particulier_token) }
it { is_expected.not_to allow_value(invalid_token).for(:api_particulier_token) }
end
context 'monavis' do
context 'nil is allowed' do
it { is_expected.to allow_value(nil).for(:monavis_embed) }
it { is_expected.to allow_value('').for(:monavis_embed) }
end
context 'random string is not allowed' do
let(:procedure) { build(:procedure, monavis_embed: "plop") }
it { expect(procedure.valid?).to eq(false) }
end
context 'random html is not allowed' do
let(:procedure) { build(:procedure, monavis_embed: '<img src="http://some.analytics/hello.gif">') }
it { expect(procedure.valid?).to eq(false) }
end
context 'Monavis embed code with white button is allowed' do
monavis_blanc = <<-MSG
<a href="https://monavis.numerique.gouv.fr/Demarches/123?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=cd4a872d475e4045666057f">
<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-blanc.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_blanc) }
it { expect(procedure.valid?).to eq(true) }
end
context 'Monavis embed code with blue button is allowed' do
monavis_bleu = <<-MSG
<a href="https://monavis.numerique.gouv.fr/Demarches/123?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=cd4a872d475e4045666057f">
<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-bleu.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_bleu) }
it { expect(procedure.valid?).to eq(true) }
end
context 'Monavis embed code with voxusages is allowed' do
monavis_issue_phillipe = <<-MSG
<a href="https://voxusagers.numerique.gouv.fr/Demarches/3193?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=58e099a09c02abe629c14905ed2b055d">
<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-bleu.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_issue_phillipe) }
it { expect(procedure.valid?).to eq(true) }
end
context 'Monavis embed code without title allowed' do
monavis_issue_bouchra = <<-MSG
<a href="https://voxusagers.numerique.gouv.fr/Demarches/3193?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=58e099a09c02abe629c14905ed2b055d">
<img src="https://voxusagers.numerique.gouv.fr/static/bouton-bleu.svg" alt="Je donne mon avis" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_issue_bouchra) }
it { expect(procedure.valid?).to eq(true) }
end
context 'Monavis embed code with jedonnemonavis' do
monavis_jedonnemonavis = <<-MSG
<a href="https://jedonnemonavis.numerique.gouv.fr/Demarches/3839?&view-mode=formulaire-avis&nd_source=button&key=6af80846f64fb213abcabaeea7a3ea8c">
<img src="https://jedonnemonavis.numerique.gouv.fr/static/bouton-bleu.svg" alt="Je donne mon avis" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_jedonnemonavis) }
it { expect(procedure.valid?).to eq(true) }
end
context 'Monavis embed code with kthxbye' do
monavis_jedonnemonavis = <<-MSG
<a href="https://kthxbye.fr/?key=6af80846f64fb213abcabaeea7a3ea8c">
<img src="https://kthxbye.fr/static/bouton-bleu.svg" alt="Je donne mon avis" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_jedonnemonavis) }
it { expect(procedure.valid?).to eq(false) }
end
end
describe 'duree de conservation dans ds' do
let(:field_name) { :duree_conservation_dossiers_dans_ds }
context 'by default is caped to 12' do
subject { create(:procedure, duree_conservation_dossiers_dans_ds: 12, max_duree_conservation_dossiers_dans_ds: 12) }
it { is_expected.not_to allow_value(nil).for(field_name) }
it { is_expected.not_to allow_value('').for(field_name) }
it { is_expected.not_to allow_value('trois').for(field_name) }
it { is_expected.to allow_value(3).for(field_name) }
it { is_expected.to validate_numericality_of(field_name).is_less_than_or_equal_to(12) }
end
context 'can be over riden' do
subject { create(:procedure, duree_conservation_dossiers_dans_ds: 60, max_duree_conservation_dossiers_dans_ds: 60) }
it { is_expected.not_to allow_value(nil).for(field_name) }
it { is_expected.not_to allow_value('').for(field_name) }
it { is_expected.not_to allow_value('trois').for(field_name) }
it { is_expected.to allow_value(3).for(field_name) }
it { is_expected.to allow_value(60).for(field_name) }
it { is_expected.to validate_numericality_of(field_name).is_less_than_or_equal_to(60) }
end
end
describe 'draft_types_de_champ validations' do
let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) }
context 'on a draft procedure' do
let(:types_de_champ_private) { [] }
let(:types_de_champ_public) { [{ type: :repetition, libelle: 'Enfants', children: [] }] }
it 'doesnt validate the types de champs' do
procedure.validate
expect(procedure.errors[:draft_types_de_champ_public]).not_to be_present
end
end
context 'when validating for publication' do
let(:types_de_champ_public) do
[
{ type: :repetition, libelle: 'Enfants', children: [] },
{ type: :drop_down_list, libelle: 'Civilité', options: [] }
]
end
let(:types_de_champ_private) { [] }
let(:invalid_repetition_error_message) { "doit comporter au moins un champ répétable" }
let(:invalid_drop_down_error_message) { "doit comporter au moins un choix sélectionnable" }
it 'validates that no repetition type de champ is empty' do
procedure.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_public)).to include(invalid_repetition_error_message)
new_draft = procedure.draft_revision
repetition = procedure.draft_revision.types_de_champ_public.find(&:repetition?)
parent_coordinate = new_draft.revision_types_de_champ.find_by(type_de_champ: repetition)
new_draft.revision_types_de_champ.create(type_de_champ: create(:type_de_champ), position: 0, parent: parent_coordinate)
procedure.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_public)).not_to include(invalid_repetition_error_message)
end
it 'validates that no drop-down type de champ is empty' do
drop_down = procedure.draft_revision.types_de_champ_public.find(&:any_drop_down_list?)
drop_down.update!(drop_down_options: [])
procedure.reload.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_public)).to include(invalid_drop_down_error_message)
drop_down.update!(drop_down_options: ["--title--", "some value"])
procedure.reload.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_public)).not_to include(invalid_drop_down_error_message)
end
end
context 'when the champ is private' do
let(:types_de_champ_private) do
[
{ type: :repetition, libelle: 'Enfants', children: [] },
{ type: :drop_down_list, libelle: 'Civilité', options: [] }
]
end
let(:types_de_champ_public) { [] }
let(:invalid_repetition_error_message) { "doit comporter au moins un champ répétable" }
let(:invalid_drop_down_error_message) { "doit comporter au moins un choix sélectionnable" }
it 'validates that no repetition type de champ is empty' do
procedure.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_private)).to include(invalid_repetition_error_message)
repetition = procedure.draft_revision.types_de_champ_private.find(&:repetition?)
expect(procedure.errors.to_enum.to_a.map { _1.options[:type_de_champ] }).to include(repetition)
end
it 'validates that no drop-down type de champ is empty' do
drop_down = procedure.draft_revision.types_de_champ_private.find(&:any_drop_down_list?)
drop_down.update!(drop_down_options: [])
procedure.reload.validate(:publication)
expect(procedure.errors.messages_for(:draft_types_de_champ_private)).to include(invalid_drop_down_error_message)
expect(procedure.errors.to_enum.to_a.map { _1.options[:type_de_champ] }).to include(drop_down)
end
end
context 'when condition on champ private use public champ' do
include Logic
let(:types_de_champ_public) { [{ type: :decimal_number, stable_id: 1 }] }
let(:types_de_champ_private) { [{ type: :text, condition: ds_eq(champ_value(1), constant(2)), stable_id: 2 }] }
it 'validate without context' do
procedure.validate
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to be_empty
end
it 'validate allows condition' do
procedure.validate(:types_de_champ_private_editor)
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to be_empty
end
end
context 'when condition on champ private use public champ having a position higher than the champ private' do
include Logic
let(:types_de_champ_public) do
[
{ type: :decimal_number, stable_id: 1 },
{ type: :decimal_number, stable_id: 2 }
]
end
let(:types_de_champ_private) do
[
{ type: :text, condition: ds_eq(champ_value(2), constant(2)), stable_id: 3 }
]
end
it 'validate without context' do
procedure.validate
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to be_empty
end
it 'validate allows condition' do
procedure.validate(:types_de_champ_private_editor)
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to be_empty
end
end
context 'when condition on champ public use private champ' do
include Logic
let(:types_de_champ_public) { [{ type: :text, libelle: 'condition', condition: ds_eq(champ_value(1), constant(2)), stable_id: 2 }] }
let(:types_de_champ_private) { [{ type: :decimal_number, stable_id: 1 }] }
let(:error_on_condition) { "Le champ a une logique conditionnelle invalide" }
it 'validate without context' do
procedure.validate
expect(procedure.errors.full_messages_for(:draft_types_de_champ_public)).to be_empty
end
it 'validate prevent condition' do
procedure.validate(:types_de_champ_public_editor)
expect(procedure.errors.full_messages_for(:draft_types_de_champ_public)).to include(error_on_condition)
end
end
end
context 'with auto archive' do
let(:procedure) { create(:procedure, auto_archive_on: 1.day.from_now) }
it { expect(procedure).to be_valid }
context 'when auto_archive_on is in the past' do
it 'validates only when attribute is changed' do
procedure.auto_archive_on = 1.day.ago
expect(procedure).not_to be_valid
procedure.save!(validate: false)
expect(procedure).to be_valid
end
end
end
context 'with sva svr' do
before {
procedure.sva_svr["decision"] = "svr"
}
context 'when procedure is published with sva' do
let(:procedure) { create(:procedure, :published, :sva) }
it 'prevents changes to sva_svr' do
expect(procedure).not_to be_valid
expect(procedure.errors[:sva_svr].join).to include('ne peut plus être modifiée')
end
end
context 'when procedure is published without sva' do
let(:procedure) { create(:procedure, :published) }
it 'allow activation' do
expect(procedure).to be_valid
end
it 'allow activation from disabled value' do
procedure.sva_svr["decision"] = "disabled"
procedure.save!
procedure.sva_svr["decision"] = "svr"
expect(procedure).to be_valid
end
end
context 'brouillon procedure' do
let(:procedure) { create(:procedure, :sva) }
it "can update sva config" do
expect(procedure).to be_valid
end
end
context "with declarative" do
let(:procedure) { create(:procedure, declarative_with_state: "accepte") }
it 'is not valid' do
expect(procedure).not_to be_valid
expect(procedure.errors[:sva_svr].join).to include('incompatible avec une démarche déclarative')
end
end
end
end
describe 'opendata' do
let(:procedure) { create(:procedure) }
it 'is true by default' do
expect(procedure.opendata).to be_truthy
end
end
describe 'publiques' do
let(:draft_procedure) { create(:procedure_with_dossiers, :draft, estimated_dossiers_count: 4, lien_site_web: 'https://monministere.gouv.fr/cparici') }
let(:published_procedure) { create(:procedure_with_dossiers, :published, estimated_dossiers_count: 4, lien_site_web: 'https://monministere.gouv.fr/cparici') }
let(:published_procedure_no_opendata) { create(:procedure_with_dossiers, :published, estimated_dossiers_count: 4, opendata: false) }
let(:published_procedure_with_3_dossiers) { create(:procedure_with_dossiers, :published, estimated_dossiers_count: 3) }
let(:published_procedure_with_mail) { create(:procedure_with_dossiers, :published, estimated_dossiers_count: 4, lien_site_web: 'par mail') }
let(:published_procedure_with_intra) { create(:procedure_with_dossiers, :published, estimated_dossiers_count: 4, lien_site_web: 'https://intra.service-etat.gouv.fr') }
it 'returns published procedure, with opendata flag, with accepted lien_site_web' do
expect(Procedure.publiques).not_to include(published_procedure_no_opendata)
end
it "returns only published or closed procedures" do
expect(Procedure.publiques).not_to include(draft_procedure)
end
it "returns only procedures with opendata flag" do
expect(Procedure.publiques).not_to include(published_procedure_with_mail)
end
it "returns only procedures without mail in lien_site_web" do
expect(Procedure.publiques).not_to include(published_procedure_with_mail)
end
it "returns only procedures without intra in lien_site_web" do
expect(Procedure.publiques).not_to include(published_procedure_with_intra)
end
it "does not return procedures with less than 4 dossiers" do
expect(Procedure.publiques).not_to include(published_procedure_with_3_dossiers)
end
end
describe 'active' do
let(:procedure) { create(:procedure) }
subject { Procedure.active(procedure.id) }
context 'when procedure is in draft status and not closed' do
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end
context 'when procedure is published and not closed' do
let(:procedure) { create(:procedure, :published) }
it { is_expected.to be_truthy }
end
context 'when procedure is published and closed' do
let(:procedure) { create(:procedure, :closed) }
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end
end
describe 'clone' do
let(:service) { create(:service) }
let(:procedure) do
create(:procedure,
received_mail: received_mail,
service: service,
opendata: opendata,
duree_conservation_etendue_par_ds: true,
duree_conservation_dossiers_dans_ds: Procedure::OLD_MAX_DUREE_CONSERVATION,
max_duree_conservation_dossiers_dans_ds: Procedure::OLD_MAX_DUREE_CONSERVATION,
attestation_template: build(:attestation_template, logo: logo, signature: signature),
types_de_champ_public: [{}, {}, { type: :drop_down_list }, { type: :piece_justificative }, { type: :repetition, children: [{}] }],
types_de_champ_private: [{}, {}, { type: :drop_down_list }, { type: :repetition, children: [{}] }],
api_particulier_token: '123456789012345',
api_particulier_scopes: ['cnaf_famille'],
estimated_dossiers_count: 4,
template: true)
end
let(:type_de_champ_repetition) { procedure.draft_revision.types_de_champ_public.last }
let(:type_de_champ_private_repetition) { procedure.draft_revision.types_de_champ_private.last }
let(:received_mail) { build(:received_mail) }
let(:from_library) { false }
let(:opendata) { true }
let(:administrateur) { procedure.administrateurs.first }
let(:logo) { Rack::Test::UploadedFile.new('spec/fixtures/files/white.png', 'image/png') }
let(:signature) { Rack::Test::UploadedFile.new('spec/fixtures/files/black.png', 'image/png') }
let(:groupe_instructeur_1) { create(:groupe_instructeur, procedure: procedure, label: "groupe_1", contact_information: create(:contact_information)) }
let(:instructeur_1) { create(:instructeur) }
let(:instructeur_2) { create(:instructeur) }
let!(:assign_to_1) { create(:assign_to, procedure: procedure, groupe_instructeur: groupe_instructeur_1, instructeur: instructeur_1) }
let!(:assign_to_2) { create(:assign_to, procedure: procedure, groupe_instructeur: groupe_instructeur_1, instructeur: instructeur_2) }
subject do
@procedure = procedure.clone(administrateur, from_library)
@procedure.save
@procedure
end
it { expect(subject.parent_procedure).to eq(procedure) }
it 'the cloned procedure should not be a template anymore' do
expect(subject.template).to be_falsey
end
describe "should keep groupe instructeurs " do
it "should clone groupe instructeurs" do
expect(subject.groupe_instructeurs.size).to eq(2)
expect(subject.groupe_instructeurs.size).to eq(procedure.groupe_instructeurs.size)
expect(subject.groupe_instructeurs.where(label: "groupe_1").first).not_to be nil
expect(subject.defaut_groupe_instructeur_id).to eq(subject.groupe_instructeurs.find_by(label: 'défaut').id)
end
it "should clone instructeurs in the groupe" do
expect(subject.groupe_instructeurs.where(label: "groupe_1").first.instructeurs.map(&:email)).to eq(procedure.groupe_instructeurs.where(label: "groupe_1").first.instructeurs.map(&:email))
end
it 'should clone with success a second group instructeur closed' do
procedure.groupe_instructeurs.last.update(closed: true)
expect { subject }.not_to raise_error
end
it 'should clone groupe instructeur services' do
expect(procedure.groupe_instructeurs.last.contact_information).not_to eq nil
expect(subject.groupe_instructeurs.last.contact_information).not_to eq nil
end
end
it 'should reset duree_conservation_etendue_par_ds' do
expect(subject.duree_conservation_etendue_par_ds).to eq(false)
expect(subject.duree_conservation_dossiers_dans_ds).to eq(Expired::DEFAULT_DOSSIER_RENTENTION_IN_MONTH)
end
it 'should duplicate specific objects with different id' do
expect(subject.id).not_to eq(procedure.id)
expect(subject.draft_revision.types_de_champ_public.size).to eq(procedure.draft_revision.types_de_champ_public.size)
expect(subject.draft_revision.types_de_champ_private.size).to eq(procedure.draft_revision.types_de_champ_private.size)
procedure.draft_revision.types_de_champ_public.zip(subject.draft_revision.types_de_champ_public).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc)
expect(stc.revisions).to include(subject.draft_revision)
end
public_repetition = type_de_champ_repetition
cloned_public_repetition = subject.draft_revision.types_de_champ_public.repetition.first
procedure.draft_revision.children_of(public_repetition).zip(subject.draft_revision.children_of(cloned_public_repetition)).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc)
expect(stc.revisions).to include(subject.draft_revision)
end
procedure.draft_revision.types_de_champ_private.zip(subject.draft_revision.types_de_champ_private).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc)
expect(stc.revisions).to include(subject.draft_revision)
end
private_repetition = type_de_champ_private_repetition
cloned_private_repetition = subject.draft_revision.types_de_champ_private.repetition.first
procedure.draft_revision.children_of(private_repetition).zip(subject.draft_revision.children_of(cloned_private_repetition)).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc)
expect(stc.revisions).to include(subject.draft_revision)
end
expect(subject.attestation_template.title).to eq(procedure.attestation_template.title)
expect(subject.cloned_from_library).to be(false)
cloned_procedure = subject
cloned_procedure.parent_procedure_id = nil
expect(cloned_procedure).to have_same_attributes_as(procedure, except: [
"path", "draft_revision_id", "service_id", 'estimated_dossiers_count',
"duree_conservation_etendue_par_ds", "duree_conservation_dossiers_dans_ds", 'max_duree_conservation_dossiers_dans_ds',
"defaut_groupe_instructeur_id", "template"
])
end
context 'which is opendata' do
let(:opendata) { false }
it 'should keep opendata for same admin' do
expect(subject.opendata).to be_falsy
end
end
context 'when the procedure is cloned from the library' do
let(:from_library) { true }
it 'should set cloned_from_library to true' do
expect(subject.cloned_from_library).to be(true)
end
it 'should set service_id to nil' do
expect(subject.service).to eq(nil)
end
it 'should discard old pj information' do
subject.draft_revision.types_de_champ_public.each do |stc|
expect(stc.old_pj).to be_nil
end
end
it 'should have one administrateur' do
expect(subject.administrateurs).to eq([administrateur])
end
it 'should set ask_birthday to false' do
expect(subject.ask_birthday?).to eq(false)
end
end
context 'when the procedure is cloned from the library' do
let(:procedure) { create(:procedure, received_mail: received_mail, service: service, ask_birthday: true) }
it 'should set ask_birthday to false' do
expect(subject.ask_birthday?).to eq(false)
end
end
it 'should skips service_id' do
expect(subject.service).to eq(nil)
end
context 'when the procedure is cloned to another administrateur' do
let(:administrateur) { create(:administrateur) }
let(:opendata) { false }
context 'and the procedure does not have a groupe with the defaut label' do
before do
procedure.defaut_groupe_instructeur.update!(label: 'another label')
end
it "affects the first groupe as the defaut groupe" do
expect(subject.defaut_groupe_instructeur).to eq(subject.groupe_instructeurs.first)
end
end
it 'should not clone service' do
expect(subject.service).to eq(nil)
end
context 'with groupe instructeur services' do
it 'should not clone groupe instructeur services' do
expect(procedure.groupe_instructeurs.last.contact_information).not_to eq nil
expect(subject.groupe_instructeurs.last.contact_information).to eq nil
end
end
it 'should discard old pj information' do
subject.draft_revision.types_de_champ_public.each do |stc|
expect(stc.old_pj).to be_nil
end
end
it 'should discard specific api_entreprise_token' do
expect(subject.read_attribute(:api_entreprise_token)).to be_nil
end
it 'should reset opendata to true' do
expect(subject.opendata).to be_truthy
end
it 'should have one administrateur' do
expect(subject.administrateurs).to eq([administrateur])
end
it "should discard the existing groupe instructeurs" do
expect(subject.groupe_instructeurs.size).not_to eq(procedure.groupe_instructeurs.size)
expect(subject.groupe_instructeurs.where(label: "groupe_1").first).to be nil
end
it "should discard api_particulier_scopes and token" do
expect(subject.encrypted_api_particulier_token).to be_nil
expect(subject.api_particulier_scopes).to be_empty
end
it 'should not route the procedure' do
expect(subject.routing_enabled).to eq(false)
end
it 'should have a default groupe instructeur' do
expect(subject.groupe_instructeurs.size).to eq(1)
expect(subject.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAUT_LABEL)
expect(subject.groupe_instructeurs.first.instructeurs.size).to eq(0)
end
end
it 'should duplicate existing mail_templates' do
expect(subject.received_mail.attributes.except("id", "procedure_id", "created_at", "updated_at")).to eq procedure.received_mail.attributes.except("id", "procedure_id", "created_at", "updated_at")
expect(subject.received_mail.id).not_to eq procedure.received_mail.id
expect(subject.received_mail.id).not_to be nil
expect(subject.received_mail.procedure_id).not_to eq procedure.received_mail.procedure_id
expect(subject.received_mail.procedure_id).not_to be nil
end
it 'should not duplicate default mail_template' do
expect(subject.passer_en_construction_email_template.attributes).to eq Mails::InitiatedMail.default_for_procedure(subject).attributes
end
it 'should not duplicate specific related objects' do
expect(subject.dossiers).to eq([])
end
it "should reset estimated_dossiers_count" do
expect(subject.estimated_dossiers_count).to eq(0)
end
describe 'should not duplicate lien_notice' do
let(:procedure) { create(:procedure, lien_notice: "http://toto.com") }
it { expect(subject.lien_notice).to be_nil }
end
describe 'procedure status is reset' do
let(:procedure) { create(:procedure, :closed, received_mail: received_mail, service: service, auto_archive_on: 3.weeks.from_now) }
it 'Not published nor closed' do
expect(subject.closed_at).to be_nil
expect(subject.published_at).to be_nil
expect(subject.unpublished_at).to be_nil
expect(subject.auto_archive_on).to be_nil
expect(subject.aasm_state).to eq "brouillon"
expect(subject.path).not_to be_nil
end
end
it 'should keep types_de_champ ids stable' do
expect(subject.draft_revision.types_de_champ_public.first.id).not_to eq(procedure.draft_revision.types_de_champ_public.first.id)
expect(subject.draft_revision.types_de_champ_public.first.stable_id).to eq(procedure.draft_revision.types_de_champ_public.first.id)
end
it 'should duplicate piece_justificative_template on a type_de_champ' do
expect(subject.draft_revision.types_de_champ_public.where(type_champ: "piece_justificative").first.piece_justificative_template.attached?).to be_truthy
end
context 'with a notice attached' do
let(:procedure) { create(:procedure, :with_notice, received_mail: received_mail, service: service) }
it 'should duplicate notice' do
expect(subject.notice.attached?).to be_truthy
expect(subject.notice.attachment).not_to eq(procedure.notice.attachment)
expect(subject.notice.attachment.blob).to eq(procedure.notice.attachment.blob)
subject.notice.attach(logo)
subject.reload
procedure.reload
expect(subject.notice.attached?).to be_truthy
expect(subject.notice.attachment.blob).not_to eq(procedure.notice.attachment.blob)
subject.notice.purge
subject.reload
procedure.reload
expect(subject.notice.attached?).to be_falsey
expect(procedure.notice.attached?).to be_truthy
end
end
context 'with a deliberation attached' do
let(:procedure) { create(:procedure, :with_deliberation, received_mail: received_mail, service: service) }
it 'should duplicate deliberation' do
expect(subject.deliberation.attached?).to be true
end
end
context 'with canonical procedure' do
let(:canonical_procedure) { create(:procedure) }
let(:procedure) { create(:procedure, canonical_procedure: canonical_procedure, received_mail: received_mail, service: service) }
it 'do not clone canonical procedure' do
expect(subject.canonical_procedure).to be_nil
end
end
describe 'feature flag' do
context 'with a feature flag enabled' do
before do
Flipper.enable(:dossier_pdf_vide, procedure)
end
it 'should enable feature' do
expect(subject.feature_enabled?(:dossier_pdf_vide)).to be true
expect(Flipper.feature(:dossier_pdf_vide).enabled_gate_names).to include(:actor)
end
end
context 'with feature flag is fully enabled' do
before do
Flipper.enable(:dossier_pdf_vide)
end
it 'should not clone feature for actor' do
expect(subject.feature_enabled?(:dossier_pdf_vide)).to be true
expect(Flipper.feature(:dossier_pdf_vide).enabled_gate_names).not_to include(:actor)
end
end
context 'with a feature flag disabled' do
before do
Flipper.disable(:dossier_pdf_vide, procedure)
end
it 'should not enable feature' do
expect(subject.feature_enabled?(:dossier_pdf_vide)).to be false
end
end
end
end
describe '#publish!' do
let(:procedure) { create(:procedure, path: 'example-path', zones: [create(:zone)]) }
let(:now) { Time.zone.now.beginning_of_minute }
context 'when publishing a new procedure' do
before do
Timecop.freeze(now) do
procedure.publish!
end
end
it 'no reference to the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to be_nil
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
expect(Procedure.find_by(path: "example-path")).to eq(procedure)
expect(Procedure.find_by(path: "example-path").administrateurs).to eq(procedure.administrateurs)
end
it 'creates a new draft revision' do
expect(procedure.published_revision).not_to be_nil
expect(procedure.draft_revision).not_to be_nil
expect(procedure.revisions.count).to eq(2)
expect(procedure.revisions).to eq([procedure.published_revision, procedure.draft_revision])
end
end
context 'when publishing over a previous canonical procedure' do
let(:canonical_procedure) { create(:procedure, :published) }
before do
Timecop.freeze(now) do
procedure.publish!(canonical_procedure)
end
end
it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
end
end
end
describe "#publish_or_reopen!" do
let(:canonical_procedure) { create(:procedure, :published) }
let(:administrateur) { canonical_procedure.administrateurs.first }
let(:procedure) { create(:procedure, administrateurs: [administrateur], zones: [create(:zone)]) }
let(:now) { Time.zone.now.beginning_of_minute }
context 'when publishing over a previous canonical procedure' do
before do
procedure.path = canonical_procedure.path
Timecop.freeze(now) do
procedure.publish_or_reopen!(administrateur)
end
procedure.reload
canonical_procedure.reload
end
it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
end
it 'unpublishes the canonical procedure' do
expect(canonical_procedure.unpublished_at).to eq(now)
end
it 'creates a new draft revision' do
expect(procedure.published_revision).not_to be_nil
expect(procedure.draft_revision).not_to be_nil
expect(procedure.revisions.count).to eq(2)
expect(procedure.revisions).to eq([procedure.published_revision, procedure.draft_revision])
expect(procedure.published_revision.published_at).to eq(now)
end
end
context 'when publishing over a previous procedure with canonical procedure' do
let(:canonical_procedure) { create(:procedure, :closed) }
let(:parent_procedure) { create(:procedure, :published, administrateurs: [administrateur]) }
before do
parent_procedure.update!(path: canonical_procedure.path, canonical_procedure: canonical_procedure)
procedure.path = canonical_procedure.path
Timecop.freeze(now) do
procedure.publish_or_reopen!(administrateur)
end
parent_procedure.reload
end
it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
expect(procedure.published_revision.published_at).to eq(now)
end
it 'unpublishes parent procedure' do
expect(parent_procedure.unpublished_at).to eq(now)
end
end
context 'when republishing a previously closed procedure' do
let(:procedure) { create(:procedure, :published, administrateurs: [administrateur]) }
before do
procedure.close!
Timecop.freeze(now) do
procedure.publish_or_reopen!(administrateur)
end
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
expect(procedure.published_revision.published_at).not_to eq(now)
end
it "doesn't create a new revision" do
expect(procedure.published_revision).not_to be_nil
expect(procedure.draft_revision).not_to be_nil
expect(procedure.revisions.count).to eq(2)
expect(procedure.revisions).to eq([procedure.published_revision, procedure.draft_revision])
end
end
end
describe "#publish_revision!" do
let(:procedure) { create(:procedure, :published) }
let(:tdc_attributes) { { type_champ: :number, libelle: 'libelle 1' } }
let(:publication_date) { Time.zone.local(2021, 1, 1, 12, 00, 00) }
before do
procedure.draft_revision.add_type_de_champ(tdc_attributes)
end
subject do
Timecop.freeze(publication_date) do
procedure.publish_revision!
end
end
it 'publishes the new revision' do
subject
expect(procedure.published_revision).to be_present
expect(procedure.published_revision.published_at).to eq(publication_date)
expect(procedure.published_revision.types_de_champ_public.first.libelle).to eq('libelle 1')
end
it 'creates a new draft revision' do
expect { subject }.to change(ProcedureRevision, :count).by(1)
expect(procedure.draft_revision).to be_present
expect(procedure.draft_revision.revision_types_de_champ_public).to be_present
expect(procedure.draft_revision.types_de_champ_public).to be_present
expect(procedure.draft_revision.types_de_champ_public.first.libelle).to eq('libelle 1')
end
context 'when the procedure has dossiers' do
let(:dossier_draft) { create(:dossier, :brouillon, procedure: procedure) }
let(:dossier_submitted) { create(:dossier, :en_construction, procedure: procedure) }
let(:dossier_termine) { create(:dossier, :accepte, procedure: procedure) }
before { [dossier_draft, dossier_submitted, dossier_termine] }
it 'enqueues rebase jobs for draft dossiers' do
subject
expect(DossierRebaseJob).to have_been_enqueued.with(dossier_draft)
expect(DossierRebaseJob).to have_been_enqueued.with(dossier_submitted)
expect(DossierRebaseJob).not_to have_been_enqueued.with(dossier_termine)
end
end
end
describe "#reset_draft_revision!" do
let(:procedure) { create(:procedure) }
let(:tdc_attributes) { { type_champ: :number, libelle: 'libelle 1' } }
let(:publication_date) { Time.zone.local(2021, 1, 1, 12, 00, 00) }
context "brouillon procedure" do
it "should not reset draft revision" do
procedure.draft_revision.add_type_de_champ(tdc_attributes)
previous_draft_revision = procedure.draft_revision
procedure.reset_draft_revision!
expect(procedure.draft_revision).to eq(previous_draft_revision)
end
end
context "published procedure" do
let(:procedure) do
create(
:procedure,
:published,
attestation_template: build(:attestation_template),
dossier_submitted_message: create(:dossier_submitted_message),
types_de_champ_public: [{ type: :text, libelle: 'published tdc' }]
)
end
it "should reset draft revision" do
procedure.draft_revision.add_type_de_champ(tdc_attributes)
previous_draft_revision = procedure.draft_revision
previous_attestation_template = procedure.attestation_template
previous_dossier_submitted_message = previous_draft_revision.dossier_submitted_message
expect(procedure.draft_changed?).to be_truthy
procedure.reset_draft_revision!
expect(procedure.draft_changed?).to be_falsey
expect(procedure.draft_revision).not_to eq(previous_draft_revision)
expect { previous_draft_revision.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(procedure.attestation_template).to eq(previous_attestation_template)
expect(procedure.draft_revision.dossier_submitted_message).to eq(previous_dossier_submitted_message)
end
it "should erase orphan tdc" do
published_tdc = procedure.published_revision.types_de_champ.first
draft_tdc = procedure.draft_revision.add_type_de_champ(tdc_attributes)
procedure.reset_draft_revision!
expect { published_tdc.reload }.not_to raise_error
expect { draft_tdc.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe "#unpublish!" do
let(:procedure) { create(:procedure, :published) }
let(:now) { Time.zone.now.beginning_of_minute }
before do
Timecop.freeze(now) do
procedure.unpublish!
end
end
it {
expect(procedure.closed_at).to eq(nil)
expect(procedure.published_at).not_to be_nil
expect(procedure.unpublished_at).to eq(now)
}
it 'sets published revision' do
expect(procedure.published_revision).not_to be_nil
expect(procedure.draft_revision).not_to be_nil
expect(procedure.revisions.count).to eq(2)
expect(procedure.revisions).to eq([procedure.published_revision, procedure.draft_revision])
end
end
describe "#brouillon?" do
let(:procedure_brouillon) { build(:procedure) }
let(:procedure_publiee) { build(:procedure, :published) }
let(:procedure_close) { build(:procedure, :closed) }
let(:procedure_depubliee) { build(:procedure, :unpublished) }
it { expect(procedure_brouillon.brouillon?).to be_truthy }
it { expect(procedure_publiee.brouillon?).to be_falsey }
it { expect(procedure_close.brouillon?).to be_falsey }
it { expect(procedure_depubliee.brouillon?).to be_falsey }
end
describe "#publiee?" do
let(:procedure_brouillon) { build(:procedure) }
let(:procedure_publiee) { build(:procedure, :published) }
let(:procedure_close) { build(:procedure, :closed) }
let(:procedure_depubliee) { build(:procedure, :unpublished) }
it { expect(procedure_brouillon.publiee?).to be_falsey }
it { expect(procedure_publiee.publiee?).to be_truthy }
it { expect(procedure_close.publiee?).to be_falsey }
it { expect(procedure_depubliee.publiee?).to be_falsey }
end
describe "#close?" do
let(:procedure_brouillon) { build(:procedure) }
let(:procedure_publiee) { build(:procedure, :published) }
let(:procedure_close) { build(:procedure, :closed) }
let(:procedure_depubliee) { build(:procedure, :unpublished) }
it { expect(procedure_brouillon.close?).to be_falsey }
it { expect(procedure_publiee.close?).to be_falsey }
it { expect(procedure_close.close?).to be_truthy }
it { expect(procedure_depubliee.close?).to be_falsey }
end
describe "#depubliee?" do
let(:procedure_brouillon) { build(:procedure) }
let(:procedure_publiee) { build(:procedure, :published) }
let(:procedure_close) { build(:procedure, :closed) }
let(:procedure_depubliee) { build(:procedure, :unpublished) }
it { expect(procedure_brouillon.depubliee?).to be_falsey }
it { expect(procedure_publiee.depubliee?).to be_falsey }
it { expect(procedure_close.depubliee?).to be_falsey }
it { expect(procedure_depubliee.depubliee?).to be_truthy }
end
describe "#locked?" do
let(:procedure_brouillon) { build(:procedure) }
let(:procedure_publiee) { build(:procedure, :published) }
let(:procedure_close) { build(:procedure, :closed) }
let(:procedure_depubliee) { build(:procedure, :unpublished) }
it { expect(procedure_brouillon.locked?).to be_falsey }
it { expect(procedure_publiee.locked?).to be_truthy }
it { expect(procedure_close.locked?).to be_truthy }
it { expect(procedure_depubliee.locked?).to be_truthy }
end
describe 'close' do
let(:procedure) { create(:procedure, :published) }
let(:now) { Time.zone.now.beginning_of_minute }
before do
Timecop.freeze(now) do
procedure.close!
end
procedure.reload
end
it { expect(procedure.close?).to be_truthy }
it { expect(procedure.closed_at).to eq(now) }
it 'sets published revision' do
expect(procedure.published_revision).not_to be_nil
expect(procedure.draft_revision).not_to be_nil
expect(procedure.revisions.count).to eq(2)
expect(procedure.revisions).to eq([procedure.published_revision, procedure.draft_revision])
end
end
describe 'path_customized?' do
let(:procedure) { create :procedure }
subject { procedure.path_customized? }
context 'when the path is still the default' do
it { is_expected.to be_falsey }
end
context 'when the path has been changed' do
before { procedure.path = 'custom_path' }
it { is_expected.to be_truthy }
end
end
describe 'total_dossier' do
let(:procedure) { create :procedure }
before do
create :dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)
create :dossier, procedure: procedure, state: Dossier.states.fetch(:brouillon)
create :dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)
end
subject { procedure.total_dossier }
it { is_expected.to eq 2 }
end
describe 'suggested_path' do
let(:procedure) { create(:procedure, aasm_state: :publiee, libelle: 'Inscription au Collège', zones: [create(:zone)]) }
subject { procedure.suggested_path(procedure.administrateurs.first) }
context 'when the path has been customized' do
before { procedure.path = 'custom_path' }
it { is_expected.to eq 'custom_path' }
end
context 'when the suggestion does not conflict' do
it { is_expected.to eq 'inscription-au-college' }
end
context 'when the suggestion conflicts with one procedure' do
before do
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college', zones: [create(:zone)])
end
it { is_expected.to eq 'inscription-au-college-2' }
end
context 'when the suggestion conflicts with several procedures' do
before do
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college', zones: [create(:zone)])
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college-2', zones: [create(:zone)])
end
it { is_expected.to eq 'inscription-au-college-3' }
end
context 'when the suggestion conflicts with another procedure of the same admin' do
before do
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college', administrateurs: procedure.administrateurs, zones: [create(:zone)])
end
it { is_expected.to eq 'inscription-au-college-2' }
end
end
describe ".default_scope" do
let!(:procedure) { create(:procedure, hidden_at: hidden_at) }
context "when hidden_at is nil" do
let(:hidden_at) { nil }
it { expect(Procedure.count).to eq(1) }
it { expect(Procedure.all).to include(procedure) }
end
context "when hidden_at is not nil" do
let(:hidden_at) { 2.days.ago }
it { expect(Procedure.count).to eq(0) }
it { expect { Procedure.find(procedure.id) }.to raise_error(ActiveRecord::RecordNotFound) }
end
end
describe "#discard_and_keep_track!" do
let(:super_admin) { create(:super_admin) }
let(:procedure) { create(:procedure) }
let!(:dossier) { create(:dossier, procedure: procedure) }
let!(:dossier2) { create(:dossier, procedure: procedure) }
let(:instructeur) { create(:instructeur) }
it { expect(Dossier.count).to eq(2) }
it { expect(Dossier.all).to include(dossier, dossier2) }
context "when discarding procedure" do
before do
instructeur.followed_dossiers << dossier
procedure.discard_and_keep_track!(super_admin)
instructeur.reload
end
it { expect(procedure.dossiers.count).to eq(0) }
it { expect(Dossier.count).to eq(0) }
it { expect(instructeur.followed_dossiers).not_to include(dossier) }
end
end
describe "#organisation_name" do
subject { procedure.organisation_name }
context 'when the procedure has a service (and no organization)' do
let(:procedure) { create(:procedure, :with_service, organisation: nil) }
it { is_expected.to eq procedure.service.nom }
end
context 'when the procedure has an organization (and no service)' do
let(:procedure) { create(:procedure, organisation: 'DDT des Vosges', service: nil) }
it { is_expected.to eq procedure.organisation }
end
end
describe '#juridique_required' do
it 'automatically jumps to true once cadre_juridique or deliberation have been set' do
p = create(
:procedure,
juridique_required: false,
cadre_juridique: nil
)
expect(p.juridique_required).to be_falsey
p.update(cadre_juridique: 'cadre')
expect(p.juridique_required).to be_truthy
p.update(cadre_juridique: nil)
expect(p.juridique_required).to be_truthy
p.update_columns(cadre_juridique: nil, juridique_required: false)
p.reload
expect(p.juridique_required).to be_falsey
@deliberation = fixture_file_upload('spec/fixtures/files/file.pdf', 'application/pdf')
p.update(deliberation: @deliberation)
p.reload
expect(p.juridique_required).to be_truthy
end
end
describe '.ensure_a_groupe_instructeur_exists' do
let(:procedure) { create(:procedure, groupe_instructeurs: []) }
it do
expect(procedure.groupe_instructeurs.count).to eq(1)
expect(procedure.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAUT_LABEL)
expect(procedure.defaut_groupe_instructeur_id).not_to be_nil
end
end
describe '.missing_instructeurs?' do
let!(:procedure) { create(:procedure) }
subject { procedure.missing_instructeurs? }
it { is_expected.to be true }
context 'when an instructeur is assign to this procedure' do
let!(:instructeur) { create(:instructeur) }
before { instructeur.assign_to_procedure(procedure) }
it { is_expected.to be false }
end
end
describe '.missing_zones?' do
before do
Rails.application.config.ds_zonage_enabled = true
end
let(:procedure) { create(:procedure, zones: []) }
subject { procedure.missing_zones? }
it { is_expected.to be true }
context 'when a procedure has zones' do
let(:zone) { create(:zone) }
before { procedure.zones << zone }
it { is_expected.to be false }
end
end
describe '.missing_steps' do
before do
Flipper.enable :zonage
end
subject { procedure.missing_steps.include?(step) }
context 'without zone' do
let(:procedure) { create(:procedure, zones: []) }
let(:step) { :zones }
it { is_expected.to be_truthy }
end
context 'with zone' do
let(:procedure) { create(:procedure, zones: [create(:zone)]) }
let(:step) { :zones }
it { is_expected.to be_falsey }
end
context 'without service' do
let(:procedure) { create(:procedure, service: nil) }
let(:step) { :service }
it { is_expected.to be_truthy }
end
context 'with service' do
let(:procedure) { create(:procedure) }
let(:step) { :service }
it { is_expected.to be_truthy }
end
end
describe "#destroy" do
let(:procedure) { create(:procedure, :closed, :with_type_de_champ, :with_bulk_message) }
before do
procedure.discard!
end
it "can destroy procedure" do
expect(procedure.revisions.count).to eq(2)
expect(procedure.destroy).to be_truthy
end
end
describe '#average_dossier_weight' do
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :piece_justificative }]) }
before do
create(:dossier, :accepte, :with_populated_champs, procedure:)
create(:dossier, :accepte, :with_populated_champs, procedure:)
create(:dossier, :accepte, :with_populated_champs, procedure:)
ActiveStorage::Blob.first.update!(byte_size: 4)
ActiveStorage::Blob.second.update!(byte_size: 5)
ActiveStorage::Blob.third.update!(byte_size: 6)
end
it 'estimates average dossier weight' do
expect(procedure.reload.average_dossier_weight).to eq(5 + Procedure::MIN_WEIGHT)
end
end
describe 'lien_dpo' do
it { expect(build(:procedure).valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'dpo@ministere.amere').valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'https://legal.fr/contact_dpo').valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'askjdlad l akdj asd ').valid?).to be(false) }
end
describe 'factory' do
let(:types_de_champ) { [{ type: :yes_no }, { type: :integer_number }] }
context 'create' do
let(:types_de_champ) { [{ type: :yes_no }, { type: :repetition, children: [{ type: :integer_number }] }] }
let(:procedure) { create(:procedure, types_de_champ_public: types_de_champ) }
context 'with brouillon procedure' do
it do
expect(procedure.draft_revision.types_de_champ_public.count).to eq(2)
expect(procedure.draft_revision.types_de_champ.count).to eq(3)
end
end
context 'with published procedure' do
let(:procedure) { create(:procedure, :published, types_de_champ_public: types_de_champ) }
it do
expect(procedure.draft_revision.types_de_champ_public.count).to eq(2)
expect(procedure.draft_revision.types_de_champ.count).to eq(3)
expect(procedure.published_revision.types_de_champ_public.count).to eq(2)
expect(procedure.published_revision.types_de_champ.count).to eq(3)
end
end
end
context 'with bouillon procedure' do
let(:procedure) { build(:procedure, types_de_champ_public: types_de_champ, types_de_champ_private: types_de_champ) }
it do
expect(procedure.revisions.size).to eq(1)
expect(procedure.draft_revision.types_de_champ.size).to eq(4)
expect(procedure.draft_revision.types_de_champ_public.size).to eq(2)
expect(procedure.published_revision).to be_nil
end
end
context 'with published procedure' do
let(:procedure) { build(:procedure, :published, types_de_champ_public: types_de_champ, types_de_champ_private: types_de_champ) }
it do
expect(procedure.revisions.size).to eq(2)
expect(procedure.draft_revision.types_de_champ.size).to eq(4)
expect(procedure.draft_revision.types_de_champ_public.size).to eq(2)
expect(procedure.published_revision.types_de_champ.size).to eq(4)
expect(procedure.published_revision.types_de_champ_public.size).to eq(2)
end
end
context 'repetition' do
let(:types_de_champ) do
[
{ type: :yes_no },
{
type: :repetition,
children: [
{ libelle: 'Nom', mandatory: true },
{ libelle: 'Prénom', mandatory: true },
{ libelle: 'Age', type: :integer_number, mandatory: false }
]
}
]
end
let(:revision) { procedure.draft_revision }
let(:repetition) { revision.revision_types_de_champ_public.last }
context 'with bouillon procedure' do
let(:procedure) { build(:procedure, types_de_champ_public: types_de_champ) }
it do
expect(revision.types_de_champ.size).to eq(5)
expect(revision.types_de_champ_public.size).to eq(2)
expect(revision.types_de_champ_public.map(&:type_champ)).to eq(['yes_no', 'repetition'])
expect(repetition.revision_types_de_champ.size).to eq(3)
expect(repetition.revision_types_de_champ.map(&:type_champ)).to eq(['text', 'text', 'integer_number'])
expect(repetition.revision_types_de_champ.map(&:mandatory?)).to eq([true, true, false])
end
end
context 'with published procedure' do
let(:procedure) { build(:procedure, :published, types_de_champ_public: types_de_champ) }
context 'draft revision' do
it do
expect(revision.types_de_champ.size).to eq(5)
expect(revision.types_de_champ_public.size).to eq(2)
expect(revision.types_de_champ_public.map(&:type_champ)).to eq(['yes_no', 'repetition'])
expect(repetition.revision_types_de_champ.size).to eq(3)
expect(repetition.revision_types_de_champ.map(&:type_champ)).to eq(['text', 'text', 'integer_number'])
expect(repetition.revision_types_de_champ.map(&:mandatory?)).to eq([true, true, false])
end
end
context 'published revision' do
let(:revision) { procedure.published_revision }
it do
expect(revision.types_de_champ.size).to eq(5)
expect(revision.types_de_champ_public.size).to eq(2)
expect(revision.types_de_champ_public.map(&:type_champ)).to eq(['yes_no', 'repetition'])
expect(repetition.revision_types_de_champ.size).to eq(3)
expect(repetition.revision_types_de_champ.map(&:type_champ)).to eq(['text', 'text', 'integer_number'])
expect(repetition.revision_types_de_champ.map(&:mandatory?)).to eq([true, true, false])
end
end
end
end
end
describe 'lien_notice' do
let(:procedure) { build(:procedure, lien_notice:) }
context 'when empty' do
let(:lien_notice) { '' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid link' do
let(:lien_notice) { 'https://www.demarches-simplifiees.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid link with accents' do
let(:lien_notice) { 'https://www.démarches-simplifiées.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when not a valid link' do
let(:lien_notice) { 'www.démarches-simplifiées.fr' }
it { expect(procedure.valid?).to be_falsey }
end
context 'when an email' do
let(:lien_notice) { 'test@demarches-simplifiees.fr' }
it { expect(procedure.valid?).to be_falsey }
end
end
describe 'lien_dpo' do
let(:procedure) { build(:procedure, lien_dpo:) }
context 'when empty' do
let(:lien_dpo) { '' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid link' do
let(:lien_dpo) { 'https://www.demarches-simplifiees.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid link with accents' do
let(:lien_dpo) { 'https://www.démarches-simplifiées.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid email' do
let(:lien_dpo) { 'test@demarches-simplifiees.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when valid email with accents' do
let(:lien_dpo) { 'test@démarches-simplifiées.fr' }
it { expect(procedure.valid?).to be_truthy }
end
context 'when not a valid link' do
let(:lien_dpo) { 'www.démarches-simplifiées.fr' }
it { expect(procedure.valid?).to be_falsey }
end
end
describe 'extend_conservation_for_dossiers' do
let(:duree_conservation_dossiers_dans_ds) { 2 }
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds:) }
let(:expiring_dossier_brouillon) { create(:dossier, :brouillon, procedure: procedure, brouillon_close_to_expiration_notice_sent_at: duree_conservation_dossiers_dans_ds.months.ago) }
let(:expiring_dossier_en_construction) { create(:dossier, :en_construction, procedure: procedure, en_construction_close_to_expiration_notice_sent_at: duree_conservation_dossiers_dans_ds.months.ago) }
let(:expiring_dossier_en_termine) { create(:dossier, :accepte, procedure: procedure, termine_close_to_expiration_notice_sent_at: duree_conservation_dossiers_dans_ds.months.ago) }
let(:not_expiring_dossie) { create(:dossier, :accepte, procedure: procedure, created_at: duree_conservation_dossiers_dans_ds.months.ago) }
before do
procedure
expiring_dossier_brouillon
expiring_dossier_en_construction
expiring_dossier_en_termine
not_expiring_dossie
end
context 'when duree_conservation_dossiers_dans_ds does not changes' do
it 'does not enqueues any job' do
expect(ResetExpiringDossiersJob).not_to receive(:perform_later)
procedure.update!(libelle: 'does not change duree_conservation_dossiers_dans_ds')
end
end
context 'when duree_conservation_dossiers_dans_ds decreases' do
it 'calls extend_conservation_for_dossiers' do
expect(ResetExpiringDossiersJob).not_to receive(:perform_later)
procedure.update(duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds - 1)
end
end
context 'when duree_conservation_dossiers_dans_ds increases' do
it 'calls extend_conservation_for_dossiers' do
expect(ResetExpiringDossiersJob).to receive(:perform_later)
procedure.update(duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds + 1)
end
end
end
describe "#attestation_template" do
let(:procedure) { create(:procedure) }
subject { procedure.reload }
context "when there is a v2 draft and a v1" do
before do
create(:attestation_template, procedure: procedure)
create(:attestation_template, :v2, :draft, procedure: procedure)
end
it { expect(subject.attestation_template.version).to eq(1) }
end
context "when there is only a v1" do
before do
create(:attestation_template, procedure: procedure)
end
it { expect(subject.attestation_template.version).to eq(1) }
end
context "when there is only a v2" do
before do
create(:attestation_template, :v2, procedure: procedure)
end
it { expect(subject.attestation_template.version).to eq(2) }
end
context "when there is a v2 draft" do
before do
create(:attestation_template, :v2, :draft, procedure: procedure)
end
it { expect(subject.attestation_template).to be_nil }
context "and a published" do
before do
create(:attestation_template, :v2, :published, procedure: procedure)
end
it { expect(subject.attestation_template).to be_published }
end
end
end
describe "#parsed_latest_zone_labels" do
let!(:draft_procedure) { create(:procedure) }
let!(:published_procedure) { create(:procedure_with_dossiers, :published, dossiers_count: 2) }
let!(:closed_procedure) { create(:procedure, :closed) }
let!(:procedure_detail_draft) { ProcedureDetail.new(id: draft_procedure.id, latest_zone_labels: '{ "zone1", "zone2" }') }
let!(:procedure_detail_published) { ProcedureDetail.new(id: published_procedure.id, latest_zone_labels: '{ "zone3", "zone4" }') }
let!(:procedure_detail_closed) { ProcedureDetail.new(id: closed_procedure.id, latest_zone_labels: '{ "zone5", "zone6" }') }
context 'with parsed latest zone labels' do
it 'parses the latest zone labels correctly' do
expect(procedure_detail_draft.parsed_latest_zone_labels).to eq(["zone1", "zone2"])
expect(procedure_detail_published.parsed_latest_zone_labels).to eq(["zone3", "zone4"])
expect(procedure_detail_closed.parsed_latest_zone_labels).to eq(["zone5", "zone6"])
end
it 'returns an empty array for invalid JSON' do
procedure_detail_draft.latest_zone_labels = '{ invalid json }'
expect(procedure_detail_draft.parsed_latest_zone_labels).to eq([])
end
it 'returns an empty array when latest_zone_labels is nil' do
procedure_detail_draft.latest_zone_labels = nil
expect(procedure_detail_draft.parsed_latest_zone_labels).to eq([])
end
it 'returns an empty array when latest_zone_labels is empty' do
procedure_detail_draft.latest_zone_labels = ''
expect(procedure_detail_draft.parsed_latest_zone_labels).to eq([])
end
end
end
describe '#all_revisions_types_de_champ' do
let(:types_de_champ_public) do
[
{ type: :text },
{ type: :header_section }
]
end
context 'when procedure brouillon' do
let(:procedure) { create(:procedure, types_de_champ_public:) }
it 'returns one type de champ' do
expect(procedure.all_revisions_types_de_champ.size).to eq 1
end
it 'returns also section type de champ' do
expect(procedure.all_revisions_types_de_champ(with_header_section: true).size).to eq 2
end
it "returns types de champ on draft revision" do
procedure.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'onemorechamp')
expect(procedure.reload.all_revisions_types_de_champ.size).to eq 2
end
end
context 'when procedure is published' do
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
it 'returns one type de champ' do
expect(procedure.all_revisions_types_de_champ.size).to eq 1
end
it 'returns also section type de champ' do
expect(procedure.all_revisions_types_de_champ(with_header_section: true).size).to eq 2
end
it "doesn't return types de champ on draft revision" do
procedure.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'onemorechamp')
expect(procedure.reload.all_revisions_types_de_champ.size).to eq 1
end
end
end
private
def create_dossier_with_pj_of_size(size, procedure)
dossier = create(:dossier, :accepte, procedure: procedure)
create(:champ_piece_justificative, size: size, dossier: dossier)
end
end