Merge pull request #4918 from betagouv/auto-upload
Usager : envoi automatique des pièces jointes au dossier (désactivé pour l'instant)
This commit is contained in:
commit
8dcbe5f47c
25 changed files with 485 additions and 10 deletions
1
app/assets/images/icons/retry.svg
Normal file
1
app/assets/images/icons/retry.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><switch transform="translate(0 -101)"><g fill="#0069cc"><path d="M21.406 118.453a1.422 1.422 0 0 0-.56-1.888c-.69-.372-1.298.088-1.67.778-.836 1.592-2.266 2.919-4.049 3.76-3.765 1.769-8.693.51-11.063-2.9-2.87-4.143-1.409-10.218 2.871-12.7 3.98-2.302 9.506-.862 11.925 3.05l-1.96 1.162c-.508.302-.502 1.049.036 1.323l5.798 2.966a.755.755 0 0 0 1.11-.659l.156-6.473c.016-.604-.637-.967-1.146-.664l-1.78 1.057c-3.384-5.589-10.81-7.243-16.272-3.434-4.541 3.2-6.107 9.354-3.633 14.358 2.977 5.956 10.364 8.071 16.004 4.72a11.39 11.39 0 0 0 4.233-4.456z"/></g></switch></svg>
|
After Width: | Height: | Size: 632 B |
|
@ -10,6 +10,7 @@ $dark-red: #A10005;
|
|||
$medium-red: rgba(161, 0, 5, 0.9);
|
||||
$light-red: #ED1C24;
|
||||
$lighter-red: #F52A2A;
|
||||
$background-red: #FFDFDF;
|
||||
$green: #15AD70;
|
||||
$lighter-green: lighten($green, 30%);
|
||||
$light-green: lighten($green, 25%);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.attachment-actions {
|
||||
|
@ -13,6 +14,38 @@
|
|||
}
|
||||
}
|
||||
|
||||
.attachment-error {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
margin-bottom: $default-padding;
|
||||
padding: $default-padding;
|
||||
background: $background-red;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-error-message {
|
||||
display: inline-block;
|
||||
margin-right: $default-padding;
|
||||
color: $medium-red;
|
||||
}
|
||||
|
||||
.attachment-error-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.attachment-error-retry {
|
||||
white-space: nowrap;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-input.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
border-color: red;
|
||||
}
|
||||
|
||||
input[type=file][data-direct-upload-url][disabled] {
|
||||
input[type=file][data-direct-upload-url][disabled],
|
||||
input[type=file][data-auto-attach-url][disabled] {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,10 @@
|
|||
background-image: image-url("icons/preview.svg");
|
||||
}
|
||||
|
||||
&.retry {
|
||||
background-image: image-url("icons/retry.svg");
|
||||
}
|
||||
|
||||
&.download {
|
||||
background-image: image-url("icons/download.svg");
|
||||
}
|
||||
|
|
21
app/controllers/champs/piece_justificative_controller.rb
Normal file
21
app/controllers/champs/piece_justificative_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Champs::PieceJustificativeController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
def update
|
||||
@champ = policy_scope(Champ).find(params[:champ_id])
|
||||
|
||||
@champ.piece_justificative_file.attach(params[:blob_signed_id])
|
||||
if @champ.save
|
||||
render :show
|
||||
else
|
||||
errors = @champ.errors.full_messages
|
||||
|
||||
# Before Rails 6, the attachment was persisted to database
|
||||
# by 'attach', even before calling save.
|
||||
# So until we're on Rails 6, we need to purge the file explicitely.
|
||||
@champ.piece_justificative_file.purge_later
|
||||
|
||||
render :json => { errors: errors }, :status => 422
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,4 +31,10 @@ module ChampHelper
|
|||
"desc-#{champ.type_de_champ.id}-#{champ.row}"
|
||||
end
|
||||
end
|
||||
|
||||
def auto_attach_url(form, object)
|
||||
if feature_enabled?(:autoupload_dossier_attachments) && object.is_a?(Champ) && object.public?
|
||||
champs_piece_justificative_url(form.index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
141
app/javascript/new_design/dossiers/auto-upload-controller.js
Normal file
141
app/javascript/new_design/dossiers/auto-upload-controller.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
import Uploader from '../../shared/activestorage/uploader';
|
||||
import ProgressBar from '../../shared/activestorage/progress-bar';
|
||||
import { ajax, show, hide, toggle } from '@utils';
|
||||
|
||||
// Given a file input in a champ with a selected file, upload a file,
|
||||
// then attach it to the dossier.
|
||||
//
|
||||
// On success, the champ is replaced by an HTML fragment describing the attachment.
|
||||
// On error, a error message is displayed above the input.
|
||||
export default class AutoUploadController {
|
||||
constructor(input, file) {
|
||||
this.input = input;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this._begin();
|
||||
|
||||
// Sanity checks
|
||||
const autoAttachUrl = this.input.dataset.autoAttachUrl;
|
||||
if (!autoAttachUrl) {
|
||||
throw new Error('L’attribut "data-auto-attach-url" est manquant');
|
||||
}
|
||||
|
||||
const champ = this.input.closest('.editable-champ[data-champ-id]');
|
||||
if (!champ) {
|
||||
throw new Error('Impossible de trouver l’élément ".editable-champ"');
|
||||
}
|
||||
const champId = champ.dataset.champId;
|
||||
|
||||
// Upload the file (using Direct Upload)
|
||||
let blobSignedId = await this._upload();
|
||||
|
||||
// Attach the blob to the champ
|
||||
// (The request responds with Javascript, which displays the attachment HTML fragment).
|
||||
await this._attach(champId, blobSignedId, autoAttachUrl);
|
||||
|
||||
// Everything good: clear the original file input value
|
||||
this.input.value = null;
|
||||
} catch (error) {
|
||||
this._failed(error);
|
||||
throw error;
|
||||
} finally {
|
||||
this._done();
|
||||
}
|
||||
}
|
||||
|
||||
_begin() {
|
||||
this.input.disabled = true;
|
||||
this._hideErrorMessage();
|
||||
}
|
||||
|
||||
async _upload() {
|
||||
const uploader = new Uploader(
|
||||
this.input,
|
||||
this.file,
|
||||
this.input.dataset.directUploadUrl
|
||||
);
|
||||
return await uploader.start();
|
||||
}
|
||||
|
||||
async _attach(champId, blobSignedId, autoAttachUrl) {
|
||||
// Now that the upload is done, display a new progress bar
|
||||
// to show that the attachment request is still pending.
|
||||
const progressBar = new ProgressBar(this.input, champId, this.file);
|
||||
progressBar.progress(100);
|
||||
progressBar.end();
|
||||
|
||||
const attachmentRequest = {
|
||||
url: autoAttachUrl,
|
||||
type: 'PUT',
|
||||
data: `champ_id=${champId}&blob_signed_id=${blobSignedId}`
|
||||
};
|
||||
await ajax(attachmentRequest);
|
||||
|
||||
// The progress bar has been destroyed by the attachment HTML fragment that replaced the input,
|
||||
// so no further cleanup is needed.
|
||||
}
|
||||
|
||||
_failed(error) {
|
||||
if (!document.body.contains(this.input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let progressBar = this.input.parentElement.querySelector('.direct-upload');
|
||||
if (progressBar) {
|
||||
progressBar.remove();
|
||||
}
|
||||
|
||||
this._displayErrorMessage(error);
|
||||
}
|
||||
|
||||
_done() {
|
||||
this.input.disabled = false;
|
||||
}
|
||||
|
||||
_messageFromError(error) {
|
||||
if (
|
||||
error.xhr &&
|
||||
error.xhr.status == 422 &&
|
||||
error.response &&
|
||||
error.response.errors &&
|
||||
error.response.errors[0]
|
||||
) {
|
||||
return {
|
||||
title: error.response.errors[0],
|
||||
description: '',
|
||||
retry: false
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
title: 'Une erreur s’est produite pendant l’envoi du fichier.',
|
||||
description: error.message || error.toString(),
|
||||
retry: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_displayErrorMessage(error) {
|
||||
let errorNode = this.input.parentElement.querySelector('.attachment-error');
|
||||
if (errorNode) {
|
||||
show(errorNode);
|
||||
let message = this._messageFromError(error);
|
||||
errorNode.querySelector('.attachment-error-title').textContent =
|
||||
message.title || '';
|
||||
errorNode.querySelector('.attachment-error-description').textContent =
|
||||
message.description || '';
|
||||
toggle(errorNode.querySelector('.attachment-error-retry'), message.retry);
|
||||
}
|
||||
}
|
||||
|
||||
_hideErrorMessage() {
|
||||
let errorElement = this.input.parentElement.querySelector(
|
||||
'.attachment-error'
|
||||
);
|
||||
if (errorElement) {
|
||||
hide(errorElement);
|
||||
}
|
||||
}
|
||||
}
|
23
app/javascript/new_design/dossiers/auto-upload.js
Normal file
23
app/javascript/new_design/dossiers/auto-upload.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import AutoUploadsControllers from './auto-uploads-controllers.js';
|
||||
import { delegate } from '@utils';
|
||||
|
||||
// Create a controller responsible for managing several concurrent uploads.
|
||||
const autoUploadsControllers = new AutoUploadsControllers();
|
||||
|
||||
function startUpload(input) {
|
||||
Array.from(input.files).forEach(file => {
|
||||
autoUploadsControllers.upload(input, file);
|
||||
});
|
||||
}
|
||||
|
||||
const fileInputSelector = `input[type=file][data-direct-upload-url][data-auto-attach-url]:not([disabled])`;
|
||||
delegate('change', fileInputSelector, event => {
|
||||
startUpload(event.target);
|
||||
});
|
||||
|
||||
const retryButtonSelector = `button.attachment-error-retry`;
|
||||
delegate('click', retryButtonSelector, event => {
|
||||
const inputSelector = event.target.dataset.inputTarget;
|
||||
const input = document.querySelector(inputSelector);
|
||||
startUpload(input);
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
import Rails from '@rails/ujs';
|
||||
import AutoUploadController from './auto-upload-controller.js';
|
||||
|
||||
// Manage multiple concurrent uploads.
|
||||
//
|
||||
// When the first upload starts, all the form "Submit" buttons are disabled.
|
||||
// They are enabled again when the last upload ends.
|
||||
export default class AutoUploadsControllers {
|
||||
constructor() {
|
||||
this.inFlightUploadsCount = 0;
|
||||
}
|
||||
|
||||
async upload(input, file) {
|
||||
let form = input.form;
|
||||
this._incrementInFlightUploads(form);
|
||||
|
||||
try {
|
||||
let controller = new AutoUploadController(input, file);
|
||||
await controller.start();
|
||||
} finally {
|
||||
this._decrementInFlightUploads(form);
|
||||
}
|
||||
}
|
||||
|
||||
_incrementInFlightUploads(form) {
|
||||
this.inFlightUploadsCount += 1;
|
||||
|
||||
if (form) {
|
||||
form
|
||||
.querySelectorAll('button[type=submit]')
|
||||
.forEach(submitButton => Rails.disableElement(submitButton));
|
||||
}
|
||||
}
|
||||
|
||||
_decrementInFlightUploads(form) {
|
||||
if (this.inFlightUploadsCount > 0) {
|
||||
this.inFlightUploadsCount -= 1;
|
||||
}
|
||||
|
||||
if (this.inFlightUploadsCount == 0 && form) {
|
||||
form
|
||||
.querySelectorAll('button[type=submit]')
|
||||
.forEach(submitButton => Rails.enableElement(submitButton));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import '../new_design/select2';
|
|||
import '../new_design/spinner';
|
||||
import '../new_design/support';
|
||||
import '../new_design/dossiers/auto-save';
|
||||
import '../new_design/dossiers/auto-upload';
|
||||
|
||||
import '../new_design/champs/carte';
|
||||
import '../new_design/champs/linked-drop-down-list';
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class ProgressBar {
|
|||
}
|
||||
|
||||
static render(id, filename) {
|
||||
return `<div id="direct-upload-${id}" class="direct-upload ${PENDING_CLASS}">
|
||||
return `<div id="direct-upload-${id}" class="direct-upload ${PENDING_CLASS}" data-direct-upload-id="${id}">
|
||||
<div class="direct-upload__progress" style="width: 0%"></div>
|
||||
<span class="direct-upload__filename">${filename}</span>
|
||||
</div>`;
|
||||
|
|
|
@ -27,6 +27,8 @@ addUploadEventListener(INITIALIZE_EVENT, ({ target, detail: { id, file } }) => {
|
|||
|
||||
addUploadEventListener(START_EVENT, ({ target, detail: { id } }) => {
|
||||
ProgressBar.start(id);
|
||||
// At the end of the upload, the form will be submitted again.
|
||||
// Avoid the confirm dialog to be presented again then.
|
||||
const button = target.form.querySelector('button.primary');
|
||||
if (button) {
|
||||
button.removeAttribute('data-confirm');
|
||||
|
|
|
@ -3,7 +3,7 @@ import ProgressBar from './progress-bar';
|
|||
|
||||
/**
|
||||
Uploader class is a delegate for DirectUpload instance
|
||||
used to track lifecycle and progress of un upload.
|
||||
used to track lifecycle and progress of an upload.
|
||||
*/
|
||||
export default class Uploader {
|
||||
constructor(input, file, directUploadUrl) {
|
||||
|
|
|
@ -29,6 +29,13 @@ delegate('click', '[data-attachment-refresh]', event => {
|
|||
attachementPoller.check();
|
||||
});
|
||||
|
||||
// Periodically check the state of a set of URLs.
|
||||
//
|
||||
// Each time the given URL is requested, the matching `show.js.erb` view is rendered,
|
||||
// causing the state to be refreshed.
|
||||
//
|
||||
// This is used mainly to refresh attachments during the anti-virus check,
|
||||
// but also to refresh the state of a pending spreadsheet export.
|
||||
class RemotePoller {
|
||||
urls = new Set();
|
||||
timeout;
|
||||
|
|
|
@ -13,8 +13,14 @@ export function hide(el) {
|
|||
el && el.classList.add('hidden');
|
||||
}
|
||||
|
||||
export function toggle(el) {
|
||||
el && el.classList.toggle('hidden');
|
||||
export function toggle(el, force) {
|
||||
if (force == undefined) {
|
||||
el & el.classList.toggle('hidden');
|
||||
} else if (force) {
|
||||
el && el.classList.remove('hidden');
|
||||
} else {
|
||||
el && el.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
export function enable(el) {
|
||||
|
|
17
app/views/champs/piece_justificative/show.js.erb
Normal file
17
app/views/champs/piece_justificative/show.js.erb
Normal file
|
@ -0,0 +1,17 @@
|
|||
<% dossier = @champ.dossier %>
|
||||
|
||||
<%= fields_for dossier do |form| %>
|
||||
<%= form.fields_for :champs, dossier.champs.where(id: @champ.id), include_id: false do |champ_form| %>
|
||||
<% render_to_element(".editable-champ[data-champ-id=\"#{@champ.id}\"]",
|
||||
partial: 'shared/dossiers/editable_champs/editable_champ',
|
||||
locals: {
|
||||
champ: @champ,
|
||||
form: champ_form
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% attachment = @champ.piece_justificative_file.attachment %>
|
||||
<% if attachment.virus_scanner.pending? %>
|
||||
<%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: true }) }.to_json ) %>
|
||||
<% end %>
|
|
@ -32,6 +32,7 @@
|
|||
%span.icon.phone
|
||||
%span.icon.clock
|
||||
%span.icon.preview
|
||||
%span.icon.retry
|
||||
%span.icon.download
|
||||
%span.icon.download-white
|
||||
%span.icon.move-handle
|
||||
|
|
|
@ -22,7 +22,17 @@
|
|||
.attachment-action
|
||||
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': ".attachment-input-#{attachment_id}" }
|
||||
|
||||
.attachment-error.hidden
|
||||
.attachment-error-message
|
||||
%p.attachment-error-title
|
||||
Une erreur s’est produite pendant l’envoi du fichier.
|
||||
%p.attachment-error-description
|
||||
= button_tag type: 'button', class: 'button attachment-error-retry', data: { 'input-target': ".attachment-input-#{attachment_id}" } do
|
||||
%span.icon.retry
|
||||
Ré-essayer
|
||||
|
||||
= form.file_field attached_file.name,
|
||||
class: "attachment-input attachment-input-#{attachment_id} #{'hidden' if persisted}",
|
||||
accept: accept,
|
||||
direct_upload: true
|
||||
direct_upload: true,
|
||||
data: { 'auto-attach-url': auto_attach_url(form, form.object) }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.editable-champ{ class: "editable-champ-#{champ.type_champ}" }
|
||||
.editable-champ{ class: "editable-champ-#{champ.type_champ}", data: { 'champ-id': champ.id } }
|
||||
- if champ.repetition?
|
||||
%h3.header-subsection= champ.libelle
|
||||
- if champ.description.present?
|
||||
|
|
|
@ -31,6 +31,7 @@ features = [
|
|||
:insee_api_v3,
|
||||
:instructeur_bypass_email_login_token,
|
||||
:autosave_dossier_draft,
|
||||
:autoupload_dossier_attachments,
|
||||
:maintenance_mode,
|
||||
:mini_profiler,
|
||||
:operation_log_serialize_subject,
|
||||
|
|
|
@ -119,6 +119,7 @@ Rails.application.routes.draw do
|
|||
get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link
|
||||
post ':position/carte', to: 'carte#show', as: :carte
|
||||
post ':position/repetition', to: 'repetition#show', as: :repetition
|
||||
put ':position/piece_justificative', to: 'piece_justificative#update', as: :piece_justificative
|
||||
end
|
||||
|
||||
get 'attachments/:id', to: 'attachments#show', as: :attachment
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
describe Champs::PieceJustificativeController, type: :controller do
|
||||
let(:user) { create(:user) }
|
||||
let(:procedure) { create(:procedure, :published, :with_piece_justificative) }
|
||||
let(:dossier) { create(:dossier, user: user, procedure: procedure) }
|
||||
let(:champ) { dossier.champs.first }
|
||||
|
||||
describe '#update' do
|
||||
render_views
|
||||
before { sign_in user }
|
||||
|
||||
subject do
|
||||
put :update, params: {
|
||||
position: '1',
|
||||
champ_id: champ.id,
|
||||
blob_signed_id: file
|
||||
}, format: 'js'
|
||||
end
|
||||
|
||||
context 'when the file is valid' do
|
||||
let(:file) { Rack::Test::UploadedFile.new('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
|
||||
|
||||
it 'attach the file' do
|
||||
subject
|
||||
champ.reload
|
||||
expect(champ.piece_justificative_file.attached?).to be true
|
||||
expect(champ.piece_justificative_file.filename).to eq('piece_justificative_0.pdf')
|
||||
end
|
||||
|
||||
it 'renders the attachment template as Javascript' do
|
||||
subject
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).to include("editable-champ[data-champ-id=\"#{champ.id}\"]")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file is invalid' do
|
||||
let(:file) { Rack::Test::UploadedFile.new('spec/fixtures/files/invalid_file_format.json', 'application/json') }
|
||||
|
||||
# TODO: for now there are no validators on the champ piece_justificative_file,
|
||||
# so we have to mock a failing validation.
|
||||
# Once the validators will be enabled, remove those mocks, and let the usual
|
||||
# validation fail naturally.
|
||||
#
|
||||
# See https://github.com/betagouv/demarches-simplifiees.fr/issues/4926
|
||||
before do
|
||||
champ
|
||||
expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:save).and_return(false)
|
||||
expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:errors)
|
||||
.and_return(double(full_messages: ['La pièce justificative n’est pas d’un type accepté']))
|
||||
end
|
||||
|
||||
it 'doesn’t attach the file' do
|
||||
subject
|
||||
expect(champ.reload.piece_justificative_file.attached?).to be false
|
||||
end
|
||||
|
||||
it 'renders an error' do
|
||||
subject
|
||||
expect(response.status).to eq(422)
|
||||
expect(response.header['Content-Type']).to include('application/json')
|
||||
expect(JSON.parse(response.body)).to eq({ 'errors' => ['La pièce justificative n’est pas d’un type accepté'] })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,9 +5,6 @@ feature 'The user' do
|
|||
let!(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs_mandatory) }
|
||||
let(:user_dossier) { user.dossiers.first }
|
||||
|
||||
# TODO: check
|
||||
# the order
|
||||
# there are no extraneous input
|
||||
scenario 'fill a dossier', js: true, vcr: { cassette_name: 'api_geo_departements_regions_et_communes' } do
|
||||
log_in(user, procedure)
|
||||
|
||||
|
@ -160,6 +157,14 @@ feature 'The user' do
|
|||
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
|
||||
end
|
||||
|
||||
let(:procedure_with_pjs) do
|
||||
tdcs = [
|
||||
create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', order_place: 1),
|
||||
create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 2', order_place: 2)
|
||||
]
|
||||
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
|
||||
end
|
||||
|
||||
scenario 'adding, replacing and removing attachments', js: true do
|
||||
log_in(user, procedure_with_pj)
|
||||
fill_individual
|
||||
|
@ -191,6 +196,85 @@ feature 'The user' do
|
|||
expect(page).to have_no_text('RIB.pdf')
|
||||
end
|
||||
|
||||
context 'when the auto-uploads of attachments is enabled' do
|
||||
before do
|
||||
Flipper.enable_actor(:autoupload_dossier_attachments, user)
|
||||
end
|
||||
|
||||
scenario 'add an attachment', js: true do
|
||||
log_in(user, procedure_with_pjs)
|
||||
fill_individual
|
||||
|
||||
# Add attachments
|
||||
find_field('Pièce justificative 1').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
|
||||
find_field('Pièce justificative 2').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf')
|
||||
|
||||
# Expect the files to be uploaded immediately
|
||||
expect(page).to have_text('analyse antivirus en cours', count: 2)
|
||||
expect(page).to have_text('file.pdf')
|
||||
expect(page).to have_text('RIB.pdf')
|
||||
|
||||
# Expect the submit buttons to be enabled
|
||||
expect(page).to have_button('Enregistrer le brouillon', disabled: false)
|
||||
expect(page).to have_button('Déposer le dossier', disabled: false)
|
||||
|
||||
# Reload the current page
|
||||
visit current_path
|
||||
|
||||
# Expect the files to have been saved on the dossier
|
||||
expect(page).to have_text('file.pdf')
|
||||
expect(page).to have_text('RIB.pdf')
|
||||
end
|
||||
|
||||
# TODO: once we're running on Rails 6, re-enable the validator on PieceJustificativeChamp,
|
||||
# and unmark this spec as pending.
|
||||
#
|
||||
# See piece_justificative_champ.rb
|
||||
# See https://github.com/betagouv/demarches-simplifiees.fr/issues/4926
|
||||
scenario 'add an invalid attachment', js: true, pending: true do
|
||||
log_in(user, procedure_with_pjs)
|
||||
fill_individual
|
||||
|
||||
# Test invalid file type
|
||||
attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/invalid_file_format.json')
|
||||
expect(page).to have_text('La pièce justificative n’est pas d’un type accepté')
|
||||
expect(page).to have_no_button('Ré-essayer', visible: true)
|
||||
|
||||
# Replace the file by another with a valid type
|
||||
attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/piece_justificative_0.pdf')
|
||||
expect(page).to have_no_text('La pièce justificative n’est pas d’un type accepté')
|
||||
expect(page).to have_text('analyse antivirus en cours')
|
||||
expect(page).to have_text('piece_justificative_0.pdf')
|
||||
end
|
||||
|
||||
scenario 'retry on transcient upload error', js: true do
|
||||
log_in(user, procedure_with_pjs)
|
||||
fill_individual
|
||||
|
||||
# Test auto-upload failure
|
||||
logout(:user) # Make the subsequent auto-upload request fail
|
||||
attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/file.pdf')
|
||||
expect(page).to have_text('Une erreur s’est produite pendant l’envoi du fichier')
|
||||
expect(page).to have_button('Ré-essayer', visible: true)
|
||||
expect(page).to have_button('Enregistrer le brouillon', disabled: false)
|
||||
expect(page).to have_button('Déposer le dossier', disabled: false)
|
||||
|
||||
# Test that retrying after a failure works
|
||||
login_as(user, scope: :user) # Make the auto-upload request work again
|
||||
click_on('Ré-essayer', visible: true)
|
||||
expect(page).to have_text('analyse antivirus en cours')
|
||||
expect(page).to have_text('file.pdf')
|
||||
expect(page).to have_button('Enregistrer le brouillon', disabled: false)
|
||||
expect(page).to have_button('Déposer le dossier', disabled: false)
|
||||
|
||||
# Reload the current page
|
||||
visit current_path
|
||||
|
||||
# Expect the file to have been saved on the dossier
|
||||
expect(page).to have_text('file.pdf')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the draft autosave is enabled' do
|
||||
before do
|
||||
Flipper.enable_actor(:autosave_dossier_draft, user)
|
||||
|
|
3
spec/fixtures/files/invalid_file_format.json
vendored
Normal file
3
spec/fixtures/files/invalid_file_format.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"text": "The format of this attachment is rejected by most uploaders."
|
||||
}
|
Loading…
Reference in a new issue