diff --git a/app/jobs/anti_virus_job.rb b/app/jobs/anti_virus_job.rb new file mode 100644 index 000000000..c53a246bc --- /dev/null +++ b/app/jobs/anti_virus_job.rb @@ -0,0 +1,20 @@ +class AntiVirusJob < ApplicationJob + include ActiveStorage::Downloading + + attr_reader :blob + + def perform(virus_scan) + @blob = ActiveStorage::Blob.find_by(key: virus_scan.blob_key) + + if @blob.present? + download_blob_to_tempfile do |file| + if ClamavService.safe_file?(file.path) + status = "safe" + else + status = "infected" + end + virus_scan.update(scanned_at: Time.now, status: status) + end + end + end +end diff --git a/app/models/champs/piece_justificative_champ.rb b/app/models/champs/piece_justificative_champ.rb index 83718ab29..4c4270609 100644 --- a/app/models/champs/piece_justificative_champ.rb +++ b/app/models/champs/piece_justificative_champ.rb @@ -1,2 +1,12 @@ class Champs::PieceJustificativeChamp < Champ + after_commit :create_virus_scan + + def create_virus_scan + if self.piece_justificative_file&.attachment&.blob.present? + VirusScan.where(champ: self).where.not(blob_key: self.piece_justificative_file.blob.key).delete_all + VirusScan.find_or_create_by!(champ: self, blob_key: self.piece_justificative_file.blob.key) do |virus_scan| + virus_scan.status = "pending" + end + end + end end diff --git a/config/environments/test.rb b/config/environments/test.rb index 380b51502..5043e3677 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -47,6 +47,7 @@ Rails.application.configure do } config.active_job.queue_adapter = :test + config.active_storage.service = :test # Raises error for missing translations # config.action_view.raise_on_missing_translations = true diff --git a/config/storage.yml b/config/storage.yml index 2c6762e0d..1f93f7323 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -1,3 +1,7 @@ local: service: Disk root: <%= Rails.root.join("storage") %> + +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index 45b2637e6..41553b5b6 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -17,5 +17,15 @@ FactoryBot.define do trait :dossier_link do type_de_champ { create(:type_de_champ_dossier_link) } end + + trait :piece_justificative do + type_de_champ { create(:type_de_champ_piece_justificative) } + end + + trait :with_piece_justificative_file do + after(:create) do |champ, evaluator| + champ.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain") + end + end end end diff --git a/spec/factories/virus_scan.rb b/spec/factories/virus_scan.rb new file mode 100644 index 000000000..198f63ee8 --- /dev/null +++ b/spec/factories/virus_scan.rb @@ -0,0 +1,4 @@ +FactoryBot.define do + factory :virus_scan do + end +end diff --git a/spec/jobs/anti_virus_job_spec.rb b/spec/jobs/anti_virus_job_spec.rb new file mode 100644 index 000000000..4c2f1756f --- /dev/null +++ b/spec/jobs/anti_virus_job_spec.rb @@ -0,0 +1,32 @@ +RSpec.describe AntiVirusJob, type: :job do + 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 + end + let(:virus_scan) { create(:virus_scan, status: "pending", champ: champ, blob_key: champ.piece_justificative_file.blob.key) } + + subject { AntiVirusJob.new.perform(virus_scan) } + + context "when no virus is found" do + let(:virus_found?) { true } + + before do + allow(ClamavService).to receive(:safe_file?).and_return(virus_found?) + subject + end + + it { expect(virus_scan.reload.status).to eq("safe") } + end + + context "when a virus is found" do + let(:virus_found?) { false } + + before do + allow(ClamavService).to receive(:safe_file?).and_return(virus_found?) + subject + end + + it { expect(virus_scan.reload.status).to eq("infected") } + end +end diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index 2d7767e9c..dcc51b45a 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -127,4 +127,28 @@ describe Champ do it { expect(champ.for_export).to eq('Crétinier, Mousserie') } end end + + describe '#enqueue_virus_check' do + let(:champ) { type_de_champ.champ.build(value: nil) } + + context 'when type_champ is type_de_champ_piece_justificative' do + let(:type_de_champ) { create(:type_de_champ_piece_justificative) } + + context 'and there is a blob' do + before { champ.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain") } + + it { expect{ champ.save }.to change(VirusScan, :count).by(1) } + end + + context 'and there is no blob' do + it { expect{ champ.save }.to_not change(VirusScan, :count) } + end + end + + context 'when type_champ is not type_de_champ_piece_justificative' do + let(:type_de_champ) { create(:type_de_champ_textarea) } + + it { expect{ champ.save }.to_not change(VirusScan, :count) } + end + end end