describe BatchOperationProcessOneJob, type: :job do describe 'perform' do let(:batch_operation) do create(:batch_operation, :archiver, options.merge(instructeur: create(:instructeur))) end let(:dossier_job) { batch_operation.dossiers.first } subject { BatchOperationProcessOneJob.new(batch_operation, dossier_job) } let(:options) { {} } it 'when it works' do allow_any_instance_of(BatchOperation).to receive(:process_one).with(dossier_job).and_return(true) expect { subject.perform_now } .to change { batch_operation.dossier_operations.success.pluck(:dossier_id) } .from([]) .to([dossier_job.id]) end it 'when it fails for an "unknown" reason' do allow_any_instance_of(BatchOperation).to receive(:process_one).with(dossier_job).and_raise("boom") expect { subject.perform_now }.to raise_error('boom') expect(batch_operation.dossier_operations.error.pluck(:dossier_id)).to eq([dossier_job.id]) end context 'when operation is "archiver"' do it 'archives the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.archived? } .from(false) .to(true) end end context 'when operation is "desarchiver"' do let(:batch_operation) do create(:batch_operation, :desarchiver, options.merge(instructeur: create(:instructeur))) end it 'archives the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.archived? } .from(true) .to(false) end end context 'when operation is "passer_en_instruction"' do let(:batch_operation) do create(:batch_operation, :passer_en_instruction, options.merge(instructeur: create(:instructeur))) end it 'changes the dossier to en_instruction in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.en_instruction? } .from(false) .to(true) end end context 'when operation is "repousser_expiration"' do let(:batch_operation) do create(:batch_operation, :repousser_expiration, options.merge(instructeur: create(:instructeur))) end it 'archives the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.conservation_extension } .from(dossier_job.conservation_extension) .to(dossier_job.conservation_extension + 1.month) end end context 'when operation is "follow"' do let(:batch_operation) do create(:batch_operation, :follow, options.merge(instructeur: create(:instructeur))) end it 'adds a follower to the dossier' do expect { subject.perform_now } .to change { dossier_job.reload.follows } .from([]) .to(anything) end end context 'when operation is "unfollow"' do let(:batch_operation) do create(:batch_operation, :unfollow, options.merge(instructeur: create(:instructeur))) end it 'removes a follower to the dossier' do expect { subject.perform_now } .to change { dossier_job.reload.follows.count } .from(1) .to(0) end end context 'when operation is "repasser en construction"' do let(:batch_operation) do create(:batch_operation, :repasser_en_construction, options.merge(instructeur: create(:instructeur))) end it 'changed the dossier to en construction' do expect { subject.perform_now } .to change { dossier_job.reload.en_construction? } .from(false) .to(true) end end context 'when operation is "accepter"' do let(:batch_operation) do create(:batch_operation, :accepter, options.merge(instructeur: create(:instructeur), motivation: 'motivation')) end it 'accepts the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.accepte? } .from(false) .to(true) end it 'accepts the dossier in the batch with a motivation' do expect { subject.perform_now } .to change { dossier_job.reload.motivation } .from(nil) .to('motivation') end context 'when it raises a ActiveRecord::StaleObjectError' do before { allow_any_instance_of(Dossier).to receive(:after_accepter).and_raise(ActiveRecord::StaleObjectError) } it 'with invalid dossier (ex: ActiveRecord::StaleObjectError), unlink dossier/batch_operation with update_column' do scope = double expect(scope).to receive(:find).with(dossier_job.id).and_return(dossier_job) expect_any_instance_of(BatchOperation).to receive(:dossiers_safe_scope).and_return(scope) dossier_job.errors.add('KC') expect do begin subject.perform_now rescue ActiveRecord::StaleObjectError # noop, juste want to catch existing error but not others end end.to change { dossier_job.reload.batch_operation }.from(batch_operation).to(nil) end it 'does not change dossier state' do expect do begin subject.perform_now rescue ActiveRecord::StaleObjectError # noop, juste want to catch existing error but not others end end.not_to change { dossier_job.reload.accepte? } end end end context 'when operation is "accepter" with justificatif' do let(:fake_justificatif) { ActiveStorage::Blob.create_and_upload!(io: StringIO.new("ma justification"), filename: 'piece_justificative_0.pdf', content_type: 'application/pdf') } let(:batch_operation) do create(:batch_operation, :accepter, options.merge(instructeur: create(:instructeur), motivation: 'motivation', justificatif_motivation: fake_justificatif.signed_id)) end it 'accepts the dossier in the batch with a justificatif' do expect { subject.perform_now } .to change { dossier_job.reload.justificatif_motivation.filename } .from(nil) .to(fake_justificatif.filename) end end context 'when operation is "refuser"' do let(:batch_operation) do create(:batch_operation, :refuser, options.merge(instructeur: create(:instructeur), motivation: 'motivation')) end it 'refuses the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.refuse? } .from(false) .to(true) end it 'refuses the dossier in the batch with a motivation' do expect { subject.perform_now } .to change { dossier_job.reload.motivation } .from(nil) .to('motivation') end end context 'when operation is "restaurer"' do let(:batch_operation) do create(:batch_operation, :restaurer, options.merge(instructeur: create(:instructeur))) end it 'changed the dossier to en construction' do expect { subject.perform_now } .to change { dossier_job.reload.hidden_by_administration? } .from(true) .to(false) end end context 'when operation is "classer_sans_suite"' do let(:batch_operation) do create(:batch_operation, :classer_sans_suite, options.merge(instructeur: create(:instructeur), motivation: 'motivation')) end it 'closes without continuation the dossier in the batch' do expect { subject.perform_now } .to change { dossier_job.reload.sans_suite? } .from(false) .to(true) end it 'closes without continuation the dossier in the batch with a motivation' do expect { subject.perform_now } .to change { dossier_job.reload.motivation } .from(nil) .to('motivation') end end context 'when operation is "supprimer"' do let(:batch_operation) do create(:batch_operation, :supprimer, options.merge(instructeur: create(:instructeur))) end it 'changed the dossier to en construction' do expect { subject.perform_now } .to change { dossier_job.reload.hidden_by_administration? } .from(false) .to(true) end end context 'when the dossier is out of sync (ie: someone applied a transition somewhere we do not know)' do let(:instructeur) { create(:instructeur) } let(:procedure) { create(:simple_procedure, instructeurs: [instructeur]) } let(:dossier) { create(:dossier, :accepte, :with_individual, archived: true, procedure: procedure) } let(:batch_operation) { create(:batch_operation, operation: :archiver, instructeur: instructeur, dossiers: [dossier]) } it 'does run process_one' do allow(batch_operation).to receive(:process_one).and_raise("should have been prevented") subject.perform_now end it 'when it fails from dossiers_safe_scope.find' do scope = double expect(scope).to receive(:find).with(dossier_job.id).and_raise(ActiveRecord::RecordNotFound) expect_any_instance_of(BatchOperation).to receive(:dossiers_safe_scope).and_return(scope) subject.perform_now expect(batch_operation.reload.failed_dossier_ids).to eq([]) expect(batch_operation.dossiers).not_to include(dossier_job) end end end end