# 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 doesn’t 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 doesn’t contain a lien attestation tag' do
let(:closed_mail) { build(:closed_mail) }
context 'when the procedure doesn’t 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 doesn’t 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: '') }
it { expect(procedure.valid?).to eq(false) }
end
context 'Monavis embed code with white button is allowed' do
monavis_blanc = <<-MSG
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
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
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
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
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
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 'doesn’t 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(&: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(&: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
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