Merge pull request #5979 from betagouv/refactor-active-storage-concerns
Jobs : refactor de la méthode utilisée pour déclencher les jobs de scan anti-virus et de filigranage (#5979)
This commit is contained in:
commit
5ab7ea1d79
14 changed files with 139 additions and 115 deletions
|
@ -1,9 +1,23 @@
|
||||||
class TitreIdentiteWatermarkJob < ApplicationJob
|
class TitreIdentiteWatermarkJob < ApplicationJob
|
||||||
|
class FileNotScannedYetError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
# If by the time the job runs the blob has been deleted, ignore the error
|
||||||
|
discard_on ActiveRecord::RecordNotFound
|
||||||
|
# If the file is deleted during the scan, ignore the error
|
||||||
|
discard_on ActiveStorage::FileNotFoundError
|
||||||
|
# If the file is not analyzed or scanned for viruses yet, retry later
|
||||||
|
# (to avoid modifying the file while it is being scanned).
|
||||||
|
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
||||||
|
|
||||||
MAX_IMAGE_SIZE = 1500
|
MAX_IMAGE_SIZE = 1500
|
||||||
SCALE = 0.9
|
SCALE = 0.9
|
||||||
WATERMARK = Rails.root.join("app/assets/images/#{WATERMARK_FILE}")
|
WATERMARK = Rails.root.join("app/assets/images/#{WATERMARK_FILE}")
|
||||||
|
|
||||||
def perform(blob)
|
def perform(blob)
|
||||||
|
if blob.virus_scanner.pending? then raise FileNotScannedYetError end
|
||||||
|
if blob.watermark_done? then return end
|
||||||
|
|
||||||
blob.open do |file|
|
blob.open do |file|
|
||||||
watermark = resize_watermark(file)
|
watermark = resize_watermark(file)
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
class VirusScannerJob < ApplicationJob
|
class VirusScannerJob < ApplicationJob
|
||||||
|
class FileNotAnalyzedYetError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
queue_as :active_storage_analysis
|
queue_as :active_storage_analysis
|
||||||
|
|
||||||
# If by the time the job runs the blob has been deleted, ignore the error
|
# If by the time the job runs the blob has been deleted, ignore the error
|
||||||
discard_on ActiveRecord::RecordNotFound
|
discard_on ActiveRecord::RecordNotFound
|
||||||
# If the file is deleted during the scan, ignore the error
|
# If the file is deleted during the scan, ignore the error
|
||||||
discard_on ActiveStorage::FileNotFoundError
|
discard_on ActiveStorage::FileNotFoundError
|
||||||
|
# If the file is not analyzed yet, retry later (to avoid clobbering metadata)
|
||||||
|
retry_on FileNotAnalyzedYetError, wait: :exponentially_longer, attempts: 10
|
||||||
# If for some reason the file appears invalid, retry for a while
|
# If for some reason the file appears invalid, retry for a while
|
||||||
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: 5.seconds
|
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: 5.seconds
|
||||||
|
|
||||||
def perform(blob)
|
def perform(blob)
|
||||||
|
if !blob.analyzed? then raise FileNotAnalyzedYetError end
|
||||||
|
if blob.virus_scanner.done? then return end
|
||||||
|
|
||||||
metadata = extract_metadata_via_virus_scanner(blob)
|
metadata = extract_metadata_via_virus_scanner(blob)
|
||||||
blob.update!(metadata: blob.metadata.merge(metadata))
|
blob.update!(metadata: blob.metadata.merge(metadata))
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Request a watermark on files attached to a `Champs::TitreIdentiteChamp`.
|
||||||
|
#
|
||||||
|
# We're using a class extension here, but we could as well have a periodic
|
||||||
|
# job that watermarks relevant attachments.
|
||||||
|
module AttachmentTitreIdentiteWatermarkConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
after_create_commit :watermark_later
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def watermark_later
|
||||||
|
blob&.watermark_later
|
||||||
|
end
|
||||||
|
end
|
20
app/models/concerns/attachment_virus_scanner_concern.rb
Normal file
20
app/models/concerns/attachment_virus_scanner_concern.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Run a virus scan on all attachments after they are analyzed.
|
||||||
|
#
|
||||||
|
# We're using a class extension to ensure that all attachments get scanned,
|
||||||
|
# regardless on how they were created. This could be an ActiveStorage::Analyzer,
|
||||||
|
# but as of Rails 6.1 only the first matching analyzer is ever run on
|
||||||
|
# a blob (and we may want to analyze the dimension of a picture as well
|
||||||
|
# as scanning it).
|
||||||
|
module AttachmentVirusScannerConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
after_create_commit :scan_for_virus_later
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def scan_for_virus_later
|
||||||
|
blob&.scan_for_virus_later
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,38 +1,21 @@
|
||||||
# Request a watermark on blobs attached to a `Champs::TitreIdentiteChamp`
|
|
||||||
# after the virus scan has run.
|
|
||||||
#
|
|
||||||
# We're using a class extension here, but we could as well have a periodic
|
|
||||||
# job that watermarks relevant attachments.
|
|
||||||
#
|
|
||||||
# The `after_commit` hook is triggered, among other cases, when
|
|
||||||
# the analyzer or virus scan updates the blob metadata. When both the analyzer
|
|
||||||
# and the virus scan have run, it is now safe to start the watermarking,
|
|
||||||
# without risking to replace the picture while it is being scanned in a
|
|
||||||
# concurrent job.
|
|
||||||
module BlobTitreIdentiteWatermarkConcern
|
module BlobTitreIdentiteWatermarkConcern
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
after_commit :enqueue_watermark_job
|
|
||||||
end
|
|
||||||
|
|
||||||
def watermark_pending?
|
def watermark_pending?
|
||||||
watermark_required? && !watermark_done?
|
watermark_required? && !watermark_done?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def watermark_done?
|
||||||
|
metadata[:watermark]
|
||||||
|
end
|
||||||
|
|
||||||
|
def watermark_later
|
||||||
|
if watermark_required?
|
||||||
|
TitreIdentiteWatermarkJob.perform_later(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def watermark_required?
|
def watermark_required?
|
||||||
attachments.any? { |attachment| attachment.record.class.name == 'Champs::TitreIdentiteChamp' }
|
attachments.any? { |attachment| attachment.record.class.name == 'Champs::TitreIdentiteChamp' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def watermark_done?
|
|
||||||
metadata[:watermark]
|
|
||||||
end
|
|
||||||
|
|
||||||
def enqueue_watermark_job
|
|
||||||
if analyzed? && virus_scanner.done? && watermark_pending?
|
|
||||||
TitreIdentiteWatermarkJob.perform_later(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,36 +1,21 @@
|
||||||
# Run a virus scan on all blobs after they are analyzed.
|
|
||||||
#
|
|
||||||
# We're using a class extension to ensure that all blobs get scanned,
|
|
||||||
# regardless on how they were created. This could be an ActiveStorage::Analyzer,
|
|
||||||
# but as of Rails 6.1 only the first matching analyzer is ever run on
|
|
||||||
# a blob (and we may want to analyze the dimension of a picture as well
|
|
||||||
# as scanning it).
|
|
||||||
#
|
|
||||||
# The `after_commit` hook is triggered, among other cases, when
|
|
||||||
# the analyzer updates the blob metadata. When the analyzer has run,
|
|
||||||
# it is now safe to start our own scanning, without risking to have
|
|
||||||
# two concurrent jobs overwriting the metadata of the blob.
|
|
||||||
module BlobVirusScannerConcern
|
module BlobVirusScannerConcern
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
before_create :set_pending
|
before_create :set_pending
|
||||||
after_commit :enqueue_virus_scan
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def virus_scanner
|
def virus_scanner
|
||||||
ActiveStorage::VirusScanner.new(self)
|
ActiveStorage::VirusScanner.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scan_for_virus_later
|
||||||
|
VirusScannerJob.perform_later(self)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_pending
|
def set_pending
|
||||||
self.metadata[:virus_scan_result] ||= ActiveStorage::VirusScanner::PENDING
|
metadata[:virus_scan_result] ||= ActiveStorage::VirusScanner::PENDING
|
||||||
end
|
|
||||||
|
|
||||||
def enqueue_virus_scan
|
|
||||||
if analyzed? && !virus_scanner.done?
|
|
||||||
VirusScannerJob.perform_later(self)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,14 @@ Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer
|
||||||
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
|
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
|
||||||
|
|
||||||
ActiveSupport.on_load(:active_storage_blob) do
|
ActiveSupport.on_load(:active_storage_blob) do
|
||||||
include BlobSignedIdConcern
|
|
||||||
include BlobVirusScannerConcern
|
|
||||||
include BlobTitreIdentiteWatermarkConcern
|
include BlobTitreIdentiteWatermarkConcern
|
||||||
|
include BlobVirusScannerConcern
|
||||||
|
include BlobSignedIdConcern
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveSupport.on_load(:active_storage_attachment) do
|
||||||
|
include AttachmentTitreIdentiteWatermarkConcern
|
||||||
|
include AttachmentVirusScannerConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
# When an OpenStack service is initialized it makes a request to fetch
|
# When an OpenStack service is initialized it makes a request to fetch
|
||||||
|
|
|
@ -98,14 +98,10 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with attachment' do
|
context 'with attachment' do
|
||||||
include ActiveJob::TestHelper
|
|
||||||
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
|
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect(ClamavService).to receive(:safe_file?).and_return(true)
|
post :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer', piece_justificative_file: file } }
|
||||||
perform_enqueued_jobs do
|
|
||||||
post :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer', piece_justificative_file: file } }
|
|
||||||
end
|
|
||||||
avis_without_answer.reload
|
avis_without_answer.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,7 +122,6 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
subject { post :create_commentaire, params: { id: avis_without_answer.id, procedure_id: procedure.id, commentaire: { body: 'commentaire body', piece_jointe: file } } }
|
subject { post :create_commentaire, params: { id: avis_without_answer.id, procedure_id: procedure.id, commentaire: { body: 'commentaire body', piece_jointe: file } } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(ClamavService).to receive(:safe_file?).and_return(scan_result)
|
|
||||||
Timecop.freeze(now)
|
Timecop.freeze(now)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,8 @@ feature 'Inviting an expert:' do
|
||||||
check 'avis_invite_linked_dossiers'
|
check 'avis_invite_linked_dossiers'
|
||||||
page.select 'confidentiel', from: 'avis_confidentiel'
|
page.select 'confidentiel', from: 'avis_confidentiel'
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
click_on 'Demander un avis'
|
||||||
click_on 'Demander un avis'
|
perform_enqueued_jobs
|
||||||
end
|
|
||||||
|
|
||||||
expect(page).to have_content('Une demande d\'avis a été envoyée')
|
expect(page).to have_content('Une demande d\'avis a été envoyée')
|
||||||
expect(page).to have_content('Avis des invités')
|
expect(page).to have_content('Avis des invités')
|
||||||
|
@ -38,7 +37,8 @@ feature 'Inviting an expert:' do
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(Avis.count).to eq(4)
|
expect(Avis.count).to eq(4)
|
||||||
expect(all_emails.size).to eq(2)
|
expect(emails_sent_to('expert1@exemple.fr').size).to eq(1)
|
||||||
|
expect(emails_sent_to('expert2@exemple.fr').size).to eq(1)
|
||||||
|
|
||||||
invitation_email = open_email('expert2@exemple.fr')
|
invitation_email = open_email('expert2@exemple.fr')
|
||||||
avis = Avis.find_by(email: 'expert2@exemple.fr', dossier: dossier)
|
avis = Avis.find_by(email: 'expert2@exemple.fr', dossier: dossier)
|
||||||
|
|
|
@ -1,48 +1,49 @@
|
||||||
RSpec.describe VirusScannerJob, type: :job do
|
describe VirusScannerJob, type: :job do
|
||||||
include ActiveJob::TestHelper
|
let(:blob) do
|
||||||
|
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
|
||||||
let(:champ) do
|
|
||||||
champ = create(:champ_piece_justificative)
|
|
||||||
champ.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
|
|
||||||
champ.save
|
|
||||||
champ
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
perform_enqueued_jobs do
|
VirusScannerJob.perform_now(blob)
|
||||||
VirusScannerJob.perform_later(champ.piece_justificative_file.blob)
|
end
|
||||||
|
|
||||||
|
context "when the blob is not analyzed yet" do
|
||||||
|
it "retries the job later" do
|
||||||
|
expect { subject }.to have_enqueued_job(VirusScannerJob)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when no virus is found" do
|
context "when the blob has been analyzed" do
|
||||||
let(:virus_found?) { true }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(ClamavService).to receive(:safe_file?).and_return(virus_found?)
|
blob.analyze
|
||||||
subject
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(champ.reload.piece_justificative_file.virus_scanner.safe?).to be_truthy }
|
context "when no virus is found" do
|
||||||
end
|
before do
|
||||||
|
allow(ClamavService).to receive(:safe_file?).and_return(true)
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
context "when a virus is found" do
|
it { expect(blob.virus_scanner.safe?).to be_truthy }
|
||||||
let(:virus_found?) { false }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(ClamavService).to receive(:safe_file?).and_return(virus_found?)
|
|
||||||
subject
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(champ.reload.piece_justificative_file.virus_scanner.infected?).to be_truthy }
|
context "when a virus is found" do
|
||||||
end
|
before do
|
||||||
|
allow(ClamavService).to receive(:safe_file?).and_return(false)
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
context "when the blob has been deleted" do
|
it { expect(blob.virus_scanner.infected?).to be_truthy }
|
||||||
before do
|
|
||||||
Champ.find(champ.id).piece_justificative_file.purge
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ignores the error" do
|
context "when the blob has been deleted" do
|
||||||
expect { subject }.not_to raise_error
|
before do
|
||||||
|
ActiveStorage::Blob.find(blob.id).purge
|
||||||
|
end
|
||||||
|
|
||||||
|
it "ignores the error" do
|
||||||
|
expect { subject }.not_to raise_error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -458,7 +458,8 @@ describe Champ do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'marks the file as safe once the scan completes' do
|
it 'marks the file as safe once the scan completes' do
|
||||||
perform_enqueued_jobs { subject }
|
subject
|
||||||
|
perform_enqueued_jobs
|
||||||
expect(champ.reload.piece_justificative_file.virus_scanner.safe?).to be_truthy
|
expect(champ.reload.piece_justificative_file.virus_scanner.safe?).to be_truthy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -480,13 +481,15 @@ describe Champ do
|
||||||
champ
|
champ
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'enqueues a watermark job on file attachment' do
|
it 'marks the file as needing watermarking' do
|
||||||
expect(subject.piece_justificative_file.watermark_pending?).to be_truthy
|
expect(subject.piece_justificative_file.watermark_pending?).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'watermarks the file' do
|
it 'watermarks the file' do
|
||||||
perform_enqueued_jobs { subject }
|
subject
|
||||||
expect(champ.reload.piece_justificative_file.blob.metadata[:watermark]).to be_truthy
|
perform_enqueued_jobs
|
||||||
|
expect(champ.reload.piece_justificative_file.watermark_pending?).to be_falsy
|
||||||
|
expect(champ.reload.piece_justificative_file.blob.watermark_done?).to be_truthy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1386,27 +1386,21 @@ describe Dossier do
|
||||||
it "clean up titres identite on accepter" do
|
it "clean up titres identite on accepter" do
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
||||||
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
||||||
perform_enqueued_jobs do
|
dossier.accepter!(dossier.followers_instructeurs.first, "yolo!")
|
||||||
dossier.accepter!(dossier.followers_instructeurs.first, "yolo!")
|
|
||||||
end
|
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it "clean up titres identite on refuser" do
|
it "clean up titres identite on refuser" do
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
||||||
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
||||||
perform_enqueued_jobs do
|
dossier.refuser!(dossier.followers_instructeurs.first, "yolo!")
|
||||||
dossier.refuser!(dossier.followers_instructeurs.first, "yolo!")
|
|
||||||
end
|
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it "clean up titres identite on classer_sans_suite" do
|
it "clean up titres identite on classer_sans_suite" do
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
||||||
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
||||||
perform_enqueued_jobs do
|
dossier.classer_sans_suite!(dossier.followers_instructeurs.first, "yolo!")
|
||||||
dossier.classer_sans_suite!(dossier.followers_instructeurs.first, "yolo!")
|
|
||||||
end
|
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1416,9 +1410,7 @@ describe Dossier do
|
||||||
it "clean up titres identite on accepter_automatiquement" do
|
it "clean up titres identite on accepter_automatiquement" do
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy
|
||||||
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite_vide.piece_justificative_file.attached?).to be_falsey
|
||||||
perform_enqueued_jobs do
|
dossier.accepter_automatiquement!
|
||||||
dossier.accepter_automatiquement!
|
|
||||||
end
|
|
||||||
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
expect(champ_titre_identite.piece_justificative_file.attached?).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,15 +29,8 @@ describe CommentaireService do
|
||||||
context 'when it has a file' do
|
context 'when it has a file' do
|
||||||
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
|
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
|
||||||
|
|
||||||
before do
|
it 'attaches the file' do
|
||||||
expect(ClamavService).to receive(:safe_file?).and_return(true)
|
expect(commentaire.piece_jointe.attached?).to be_truthy
|
||||||
end
|
|
||||||
|
|
||||||
it 'saves the attached file' do
|
|
||||||
perform_enqueued_jobs do
|
|
||||||
commentaire.save
|
|
||||||
expect(commentaire.piece_jointe.attached?).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
9
spec/support/active_job.rb
Normal file
9
spec/support/active_job.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.include ActiveJob::TestHelper
|
||||||
|
|
||||||
|
config.before(:each) do
|
||||||
|
clear_enqueued_jobs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveJob::Base.queue_adapter = :test
|
Loading…
Reference in a new issue