Merge pull request #3900 from betagouv/dev

2019-05-28-01
This commit is contained in:
Paul Chavard 2019-05-28 12:09:19 +02:00 committed by GitHub
commit 6fe326ef82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 388 additions and 107 deletions

View file

@ -69,6 +69,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
GestionnaireEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
PurgeUnattachedBlobsJob.set("0 0 * * *").perform_later
### Voir les emails envoyés en local

View file

@ -231,7 +231,9 @@ class ApplicationController < ActionController::Base
DS_SIGN_IN_COUNT: current_administrateur&.sign_in_count,
DS_CREATED_AT: current_administrateur&.created_at,
DS_ACTIVE: current_administrateur&.active,
DS_ID: current_administrateur&.id
DS_ID: current_administrateur&.id,
DS_GESTIONNAIRE_ID: current_gestionnaire&.id,
DS_ROLES: logged_user_roles
}
}
}

View file

@ -0,0 +1,9 @@
class AttachmentsController < ApplicationController
before_action :authenticate_logged_user!
include ActiveStorage::SetBlob
def show
@attachment = @blob.attachments.find(params[:id])
@user_can_upload = params[:user_can_upload]
end
end

View file

@ -143,7 +143,7 @@ module Gestionnaires
def purge_champ_piece_justificative
@champ = dossier.champs_private.find(params[:champ_id])
@champ.piece_justificative_file.purge
@champ.piece_justificative_file.purge_later
flash.notice = 'La pièce jointe a bien été supprimée.'
end

View file

@ -239,7 +239,7 @@ module Users
def purge_champ_piece_justificative
@champ = dossier.champs.find(params[:champ_id])
@champ.piece_justificative_file.purge
@champ.piece_justificative_file.purge_later
flash.notice = 'La pièce jointe a bien été supprimée.'
end

View file

@ -9,6 +9,7 @@ import ReactUJS from '../shared/react-ujs';
import reactComponents from '../shared/react-components';
import '../shared/activestorage/ujs';
import '../shared/activestorage/attachment-checker';
import '../shared/rails-ujs-fix';
import '../shared/safari-11-file-xhr-workaround';
import '../shared/autocomplete';

View file

@ -0,0 +1,77 @@
import { ajax, delegate } from '@utils';
addEventListener('turbolinks:load', () => {
checker.deactivate();
const attachments = document.querySelectorAll('[data-attachment-check-url]');
for (let attachment of attachments) {
checker.add(attachment.dataset.attachmentCheckUrl);
}
});
addEventListener('attachment:update', ({ detail: { url } }) => {
checker.add(url);
});
delegate('click', '[data-attachment-refresh]', event => {
event.preventDefault();
checker.check();
});
class AttachmentChecker {
urls = new Set();
timeout;
checks = 0;
constructor(settings = {}) {
this.interval = settings.interval || 5000;
this.maxChecks = settings.maxChecks || 5;
}
get isEnabled() {
return this.checks <= this.maxChecks;
}
get isActive() {
return this.timeout !== undefined;
}
add(url) {
if (this.isEnabled) {
if (!this.isActive) {
this.activate();
}
this.urls.add(url);
}
}
check() {
let urls = this.urls;
this.reset();
for (let url of urls) {
ajax({ url, type: 'get' });
}
}
activate() {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.checks++;
this.check();
}, this.interval);
}
deactivate() {
this.checks = 0;
this.reset();
}
reset() {
clearTimeout(this.timeout);
this.urls = new Set();
this.timeout = undefined;
}
}
const checker = new AttachmentChecker();

View file

@ -3,7 +3,7 @@ import $ from 'jquery';
import debounce from 'debounce';
export { debounce };
export const { fire } = Rails;
export const { fire, ajax } = Rails;
export function show({ classList }) {
classList.remove('hidden');

View file

@ -0,0 +1,9 @@
class PurgeUnattachedBlobsJob < ApplicationJob
queue_as :cron
def perform(*args)
ActiveStorage::Blob.unattached
.where('active_storage_blobs.created_at < ?', 24.hours.ago)
.find_each(&:purge_later)
end
end

View file

@ -40,7 +40,7 @@ class Champs::PieceJustificativeChamp < Champ
end
if errors.present?
piece_justificative_file.purge
piece_justificative_file.purge_later
end
errors

View file

@ -1,3 +1,7 @@
# TODO: once we're using Rails 6, use the hooks on attachments creation
# (rather than on blob creation).
# This will help to avoid cloberring metadata accidentally (as metadata
# are more stable on attachment creation than on blob creation).
module BlobVirusScanner
extend ActiveSupport::Concern

View file

@ -75,14 +75,15 @@ class CarrierwaveActiveStorageMigrationService
# but when the first attachment that references this blob is created.
def make_blob(uploader, created_at, filename: nil, identify: false)
content_type = uploader.content_type
identified = content_type.present? && !identify
ActiveStorage::Blob.create(
filename: filename || uploader.filename,
content_type: uploader.content_type,
identified: content_type.present? && !identify,
byte_size: uploader.size,
checksum: checksum(uploader),
created_at: created_at
created_at: created_at,
metadata: { identified: identified, virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end

View file

@ -38,7 +38,8 @@ class DossierSearchService
end
def self.to_tsquery(search_terms)
search_terms.strip
(search_terms || "")
.strip
.gsub(/['?\\:&|!<>\(\)]/, "") # drop disallowed characters
.split(/\s+/) # split words
.map { |x| "#{x}:*" } # enable prefix matching

View file

@ -9,9 +9,18 @@ class PieceJustificativeToChampPieceJointeMigrationService
storage_service.ensure_openstack_copy_possible!(PieceJustificativeUploader)
end
def convert_procedure_pjs_to_champ_pjs(procedure)
def procedures_with_pjs_in_range(ids_range)
procedures_with_pj = Procedure.unscope(where: :hidden_at).joins(:types_de_piece_justificative).distinct
procedures_with_pj.where(id: ids_range)
end
def number_of_champs_to_migrate(procedure)
(procedure.types_de_piece_justificative.count + 1) * procedure.dossiers.unscope(where: :hidden_at).count
end
def convert_procedure_pjs_to_champ_pjs(procedure, &progress)
types_de_champ_pj = PiecesJustificativesService.types_pj_as_types_de_champ(procedure)
populate_champs_pjs!(procedure, types_de_champ_pj)
populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
# Only destroy the old types PJ once everything has been safely migrated to
# champs PJs. Destroying the types PJ will cascade and destroy the PJs,
@ -24,7 +33,7 @@ class PieceJustificativeToChampPieceJointeMigrationService
@storage_service ||= CarrierwaveActiveStorageMigrationService.new
end
def populate_champs_pjs!(procedure, types_de_champ_pj)
def populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
procedure.types_de_champ += types_de_champ_pj
# Unscope to make sure all dossiers are migrated, even the soft-deleted ones
@ -49,6 +58,8 @@ class PieceJustificativeToChampPieceJointeMigrationService
created_at: dossier.created_at
)
end
yield if block_given?
end
end
rescue

View file

@ -67,7 +67,24 @@
%p
Toute personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers de test sur votre démarche.
%br
%h4 Protection des Données personnelles
%p
À ce moment du processus de création, vous devez informer votre Délégué à la Protection des Données personnelles (DPD).
(
%a{ href:'https://www.cnil.fr/fr/protection-des-donnees-les-bons-reflexes', target:'_blank' }
https://www.cnil.fr/fr/protection-des-donnees-les-bons-reflexes
)
Si votre démarche propose de collecter des données personnelles, vous devez informer votre DPD. Chaque organisme en a un.
%p
Ce dernier pourra vous aider dans la finalisation de votre démarche, et vous inviter à vous interroger sur les données collectées, et sur la pertinence de ses dernières.
N'oubliez pas : toutes les démarches qui contiennent des données personnelles doivent être consignées dans un registre des traitements :
%a{ href:'https://www.cnil.fr/fr/RGDP-le-registre-des-activites-de-traitement', target:'_blank' }
https://www.cnil.fr/fr/RGDP-le-registre-des-activites-de-traitement
%p
Comment faire :
vous pouvez soit lui communiquer par email le lien vers la démarche en test, ou bien le nommer « administrateur ». Dans tous les cas, ne publiez votre démarche quaprès avoir eu son avis.
%br
%h4 Ce que vous pouvez faire lorsque vous êtes en test
%p
Profitez de la phase de test pour tester la saisie de dossiers, ainsi que toutes les fonctionnalités associées (instruction, emails automatiques, attestations, etc.).

View file

@ -0,0 +1,8 @@
<%= render_to_element(".pj-link[data-attachment-id=\"#{@attachment.id}\"]",
partial: 'shared/attachment/show',
outer: true,
locals: { attachment: @attachment, user_can_upload: @user_can_upload }) %>
<% if @attachment.virus_scanner.pending? %>
<%= fire_event('attachment:update', { url: attachment_url(@attachment.id, { signed_id: @attachment.blob.signed_id, user_can_upload: @user_can_upload }) }.to_json ) %>
<% end %>

View file

@ -13,7 +13,7 @@
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
= render partial: "shared/piece_jointe/pj_upload_field", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
= render partial: "shared/attachment/update", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
.flex.justify-between.align-baseline
%p.confidentiel.flex

View file

@ -60,7 +60,7 @@
- if dossier.motivation.present?
%h4 Motivation
%p.dossier-motivation= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
%h4 Attestation

View file

@ -1,2 +1,7 @@
<%= render_flash %>
<%= render_to_element('.state-button', partial: "state_button", locals: { dossier: dossier }) %>
<% attachment = dossier.justificatif_motivation.attachment %>
<% if attachment && attachment.virus_scanner.pending? %>
<%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }) }.to_json ) %>
<% end %>

View file

@ -28,6 +28,7 @@
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
- else
%span.waiting En attente de réponse
= render partial: 'shared/piece_jointe/pj_link', locals: { pj: avis.piece_justificative_file, user_can_upload: false }
- if avis.piece_justificative_file.attached?
= render partial: 'shared/attachment/show', locals: { attachment: avis.piece_justificative_file.attachment }
.answer-body
= simple_format(avis.answer)

View file

@ -36,7 +36,7 @@ as well as a link to its edit page.
<% if page.resource.invitation_expired? %>
<%= link_to "renvoyer l'invitation", reinvite_manager_administrateur_path(page.resource), method: :post, class: "button" %>
<% end %>
<div>
</div>
</header>

View file

@ -34,7 +34,7 @@ as well as a link to its edit page.
<% if dossier.hidden_at.nil? %>
<%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
<% end %>
<div>
</div>
</header>
<section class="main-content__body">

View file

@ -1,12 +0,0 @@
- if dossier.present?
- justificatif = dossier.justificatif_motivation
- if justificatif.attached?
- if justificatif.virus_scanner.safe?
.action
= link_to (justificatif), target: '_blank', class: 'button primary' do
%span.icon.download
Télécharger le justificatif
- elsif justificatif.virus_scanner.pending?
%p
Un justificatif a été joint. L'analyse antivirus de ce document est en cours.
= link_to "rafraichir", request.path

View file

@ -0,0 +1,26 @@
- should_display_link = attachment.virus_scanner.safe? || !attachment.virus_scanner.started?
- user_can_upload = defined?(user_can_upload) ? user_can_upload : false
- if should_display_link
- attachment_check_url = false
- else
- attachment_check_url = attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: user_can_upload })
.pj-link{ 'data-attachment-id': attachment.id, 'data-attachment-check-url': attachment_check_url }
- if should_display_link
= link_to url_for(attachment.blob), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
%span.icon.attachment
= attachment.filename.to_s
- if !attachment.virus_scanner.started?
(ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution)
- else
= attachment.filename.to_s
- if attachment.virus_scanner.pending?
(analyse antivirus en cours
= link_to "rafraichir", request.path, data: { 'attachment-refresh': true }
)
- elsif attachment.virus_scanner.infected?
- if user_can_upload
(virus détecté, merci denvoyer un autre fichier)
- else
(virus détecté, le téléchargement de ce fichier est bloqué)

View file

@ -2,7 +2,7 @@
- if pj.attached?
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
.piece-justificative-action
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
.piece-justificative-action
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }

View file

@ -1,5 +1,5 @@
- pj = champ.piece_justificative_file
- if pj.attached?
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: false }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment }
- else
Pièce justificative non fournie

View file

@ -8,4 +8,4 @@
%th.libelle Justificatif :
%td
.action
= render partial: 'shared/piece_jointe/pj_link', locals: { object: dossier, pj: dossier.justificatif_motivation }
= render partial: 'shared/attachment/show', locals: { attachment: dossier.justificatif_motivation.attachment }

View file

@ -9,7 +9,7 @@
- if pj.attached?
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
.piece-justificative-action
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
.piece-justificative-action
- if champ.private?
= link_to 'Supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger'

View file

@ -1,20 +0,0 @@
- if pj.attached?
.pj-link
- if pj.virus_scanner.safe? || !pj.virus_scanner.started?
= link_to url_for(pj), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
%span.icon.attachment
= pj.filename.to_s
- if !pj.virus_scanner.started?
(ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution)
- else
= pj.filename.to_s
- if pj.virus_scanner.pending?
(analyse antivirus en cours
= link_to "rafraichir", request.path
)
- elsif pj.virus_scanner.infected?
- if user_can_upload
(virus détecté, merci denvoyer un autre fichier)
- else
(virus détecté, le téléchargement de ce fichier est bloqué)

View file

@ -0,0 +1,2 @@
- if dossier.present? && dossier.justificatif_motivation.attached?
= render partial: "shared/attachment/show", locals: { attachment: dossier.justificatif_motivation.attachment }

View file

@ -50,7 +50,7 @@
%h3 Motif de lacceptation
%blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
.action
@ -71,7 +71,7 @@
%h3 Motif du refus
%blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
.action
= link_to 'Envoyer un message à ladministration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
@ -83,7 +83,7 @@
= succeed '.' do
%strong sans suite
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.motivation.present?
%h3 Motif du classement sans suite

View file

@ -17,4 +17,6 @@
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', target: '_blank', rel: 'noopener' }
%br
%br
En cas de difficultés, nous restons joignables sur #{link_to CONTACT_EMAIL, "mailto:#{CONTACT_EMAIL}"}.
En cas de difficultés, nous restons joignables
= succeed '.' do
= link_to("via ce formulaire", contact_admin_url)

View file

@ -1,21 +1,9 @@
ActiveStorage::Service.url_expires_in = 1.hour
# We want to run the virus scan on every ActiveStorage attachment,
# regardless of its type (user-uploaded document, instructeur-uploaded attestation, form template, etc.)
# In Rails 5.2, we have to hook at `on_load` on the blob themeselves, which is
# not ideal.
#
# To do this, the best place to work on is the ActiveStorage::Attachment
# objects themselves.
#
# We have to monkey patch ActiveStorage in order to always run an analyzer.
# The way analyzable blob interface work is by running the first accepted analyzer.
# This is not what we want for the virus scan. Using analyzer interface is still beneficial
# as it takes care of downloading the blob.
ActiveStorage::Attached::One.class_eval do
def virus_scanner
if attached?
ActiveStorage::VirusScanner.new(attachment.blob)
end
end
end
# Rails 6 adds support for `.on_load(:active_storage_attachment)`, which is
# cleaner (as it allows to enqueue the virus scan on attachment creation, rather
# than on blob creation).
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }

View file

@ -133,6 +133,8 @@ Rails.application.routes.draw do
post ':position/repetition', to: 'repetition#show', as: :repetition
end
get 'attachments/:id', to: 'attachments#show', as: :attachment
get 'tour-de-france' => 'root#tour_de_france'
get "patron" => "root#patron"
get "accessibilite" => "root#accessibilite"

View file

@ -1,8 +0,0 @@
namespace :'2019_03_13_migrate_pjs_to_champs' do
task run: :environment do
procedure_id = ENV['PROCEDURE_ID']
service = PieceJustificativeToChampPieceJointeMigrationService.new
service.ensure_correct_storage_configuration!
service.convert_procedure_pjs_to_champ_pjs(Procedure.find(procedure_id))
end
end

View file

@ -0,0 +1,46 @@
require Rails.root.join("lib", "tasks", "task_helper")
namespace :pieces_justificatives do
task migrate_procedure_to_champs: :environment do
procedure_id = ENV['PROCEDURE_ID']
procedure = Procedure.find(procedure_id)
service = PieceJustificativeToChampPieceJointeMigrationService.new
service.ensure_correct_storage_configuration!
progress = ProgressReport.new(service.number_of_champs_to_migrate(procedure))
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress.inc
end
progress.finish
end
task migrate_procedures_range_to_champs: :environment do
if ENV['RANGE_START'].nil? || ENV['RANGE_END'].nil?
fail "RANGE_START and RANGE_END must be specified"
end
procedures_range = ENV['RANGE_START']..ENV['RANGE_END']
service = PieceJustificativeToChampPieceJointeMigrationService.new
service.ensure_correct_storage_configuration!
procedures_to_migrate = service.procedures_with_pjs_in_range(procedures_range)
total_number_of_champs_to_migrate = procedures_to_migrate
.map { |p| service.number_of_champs_to_migrate(p) }
.sum
progress = ProgressReport.new(total_number_of_champs_to_migrate)
procedures_to_migrate.find_each do |procedure|
rake_puts ''
rake_puts "Migrating procedure #{procedure.id}"
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress.inc
end
end
progress.finish
end
end

View file

@ -48,5 +48,16 @@ describe Gestionnaires::RechercheController, type: :controller do
end
end
end
context 'with no query param it does not crash' do
subject { get :index, params: {} }
it { is_expected.to have_http_status(200) }
it 'returns 0 dossier' do
subject
expect(assigns(:dossiers).count).to eq(0)
end
end
end
end

View file

@ -0,0 +1,51 @@
describe 'pieces_justificatives' do
describe 'migrate_procedure_to_champs' do
let(:rake_task) { Rake::Task['pieces_justificatives:migrate_procedure_to_champs'] }
let(:procedure) { create(:procedure, :with_two_type_de_piece_justificative) }
before do
ENV['PROCEDURE_ID'] = procedure.id.to_s
allow_any_instance_of(PieceJustificativeToChampPieceJointeMigrationService).to receive(:ensure_correct_storage_configuration!)
rake_task.invoke
end
after { rake_task.reenable }
it 'migrates the procedure' do
expect(procedure.reload.types_de_piece_justificative).to be_empty
end
end
describe 'migrate_procedures_range_to_champs' do
let(:rake_task) { Rake::Task['pieces_justificatives:migrate_procedures_range_to_champs'] }
let(:procedure_in_range_1) { create(:procedure, :with_two_type_de_piece_justificative) }
let(:procedure_in_range_2) { create(:procedure, :with_two_type_de_piece_justificative) }
let(:procedure_out_of_range) { create(:procedure, :with_two_type_de_piece_justificative) }
before do
procedure_in_range_1
procedure_in_range_2
procedure_out_of_range
ENV['RANGE_START'] = procedure_in_range_1.id.to_s
ENV['RANGE_END'] = procedure_in_range_2.id.to_s
allow_any_instance_of(PieceJustificativeToChampPieceJointeMigrationService).to receive(:ensure_correct_storage_configuration!)
rake_task.invoke
end
after { rake_task.reenable }
it 'migrates procedures in the ids range' do
expect(procedure_in_range_1.reload.types_de_piece_justificative).to be_empty
expect(procedure_in_range_2.reload.types_de_piece_justificative).to be_empty
end
it 'doesnt migrate procedures not in the range' do
expect(procedure_out_of_range.reload.types_de_piece_justificative).to be_present
end
end
end

View file

@ -390,12 +390,6 @@ describe Champ do
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
end
context 'and there is no blob' do
before { champ.save }
it { expect(champ.piece_justificative_file.virus_scanner).to be_nil }
end
end
end

View file

@ -1,9 +1,37 @@
require 'spec_helper'
describe CarrierwaveActiveStorageMigrationService do
describe '#hex_to_base64' do
let(:service) { CarrierwaveActiveStorageMigrationService.new }
describe '#hex_to_base64' do
it { expect(service.hex_to_base64('deadbeef')).to eq('3q2+7w==') }
end
describe '.make_blob' do
let(:pj) { create(:piece_justificative, :rib) }
let(:identify) { false }
before do
allow(service).to receive(:checksum).and_return('cafe')
end
subject(:blob) { service.make_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename, identify: identify) }
it 'marks the blob as already scanned by the antivirus' do
expect(blob.metadata[:virus_scan_result]).to eq(ActiveStorage::VirusScanner::SAFE)
end
it 'sets the blob MIME type from the file' do
expect(blob.identified).to be true
expect(blob.content_type).to eq 'application/pdf'
end
context 'when asking for explicit MIME type identification' do
let(:identify) { true }
it 'marks the file as needing MIME type detection' do
expect(blob.identified).to be false
end
end
end
end

View file

@ -9,16 +9,17 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
let(:procedure) { create(:procedure, types_de_piece_justificative: types_pj) }
let(:types_pj) { [create(:type_de_piece_justificative)] }
let!(:dossier) do
create(
:dossier,
procedure: procedure,
pieces_justificatives: pjs
)
end
let!(:dossier) { make_dossier }
let(:pjs) { [] }
def make_dossier(hidden: false)
create(:dossier,
procedure: procedure,
pieces_justificatives: pjs,
hidden_at: hidden ? Time.zone.now : nil)
end
def make_pjs
types_pj.map do |tpj|
create(:piece_justificative, :contrat, type_de_piece_justificative: tpj)
@ -31,6 +32,22 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
expect(storage_service).to receive(:make_attachment)
end
describe '.number_of_champs_to_migrate' do
let!(:other_dossier) { make_dossier }
it 'reports the numbers of champs to be migrated' do
expect(service.number_of_champs_to_migrate(procedure)).to eq(4)
end
context 'when the procedure has hidden dossiers' do
let!(:hidden_dossier) { make_dossier(hidden: true) }
it 'reports the numbers of champs including those of hidden dossiers' do
expect(service.number_of_champs_to_migrate(procedure)).to eq(6)
end
end
end
context 'when conversion succeeds' do
context 'for the procedure' do
it 'types de champ are created for the "pièces jointes" header and for each PJ' do
@ -114,19 +131,26 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
.to change { dossier.pieces_justificatives.count }
.to(0)
end
context 'when the procedure has several dossiers' do
let!(:other_dossier) { make_dossier }
it 'sends progress callback for each migrated champ' do
number_of_champs_to_migrate = service.number_of_champs_to_migrate(procedure)
progress_count = 0
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress_count += 1
end
expect(progress_count).to eq(number_of_champs_to_migrate)
end
end
end
context 'when the dossier is soft-deleted it still gets converted' do
let(:pjs) { make_pjs }
let!(:dossier) do
create(
:dossier,
procedure: procedure,
pieces_justificatives: pjs,
hidden_at: Time.zone.now
)
end
let!(:dossier) { make_dossier(hidden: true) }
before { expect_storage_service_to_convert_object }

View file

@ -1,6 +1,6 @@
require 'rails_helper'
describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
describe 'shared/attachment/_show.html.haml', type: :view do
let(:champ) { create(:champ_piece_justificative) }
let(:virus_scan_result) { nil }
@ -8,7 +8,7 @@ describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
champ.piece_justificative_file.blob.update(metadata: champ.piece_justificative_file.blob.metadata.merge(virus_scan_result: virus_scan_result))
end
subject { render 'shared/piece_jointe/pj_link', pj: champ.piece_justificative_file, user_can_upload: false }
subject { render 'shared/attachment/show', attachment: champ.piece_justificative_file.attachment }
context 'when there is no anti-virus scan' do
let(:virus_scan_result) { nil }