specs: migrate from features to system specs

System specs have been available since Rails 5.1, and are better
integrated with the Rails framework.

- Rename `spec/features` to `spec/system`
- Rename `feature do` to `describe do`
- Configure Capybara for system specs

Steps mostly taken from https://medium.com/table-xi/a-quick-guide-to-rails-system-tests-in-rspec-b6e9e8a8b5f6
This commit is contained in:
Pierre de La Morinerie 2021-10-26 09:44:53 +00:00
parent df9fa258ae
commit 9fd38cae5e
42 changed files with 58 additions and 53 deletions

View file

@ -0,0 +1,371 @@
describe 'The user' do
let(:password) { 'my-s3cure-p4ssword' }
let!(:user) { create(:user, password: password) }
let!(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs_mandatory) }
let(:user_dossier) { user.dossiers.first }
scenario 'fill a dossier', js: true do
log_in(user, procedure)
fill_individual
# fill data
fill_in('text', with: 'super texte')
fill_in('textarea', with: 'super textarea')
fill_in('date', with: '12-12-2012')
select_date_and_time(Time.zone.parse('06/01/2030 7h05'), form_id_for_datetime('datetime'))
fill_in('number', with: '42')
find_field('checkbox').scroll_to(:center)
check('checkbox')
choose('Madame')
fill_in('email', with: 'loulou@yopmail.com')
fill_in('phone', with: '0123456789')
choose('Non')
choose('val2')
check('val1')
check('val3')
select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select_multi_combobox('multiple_choice_drop_down_list_long', 'alp', 'alpha')
select_multi_combobox('multiple_choice_drop_down_list_long', 'cha', 'charly')
select_combobox('pays', 'aust', 'Australie')
select_combobox('regions', 'Ma', 'Martinique')
select_combobox('departements', 'Ai', '02 - Aisne')
select_combobox('communes', 'Ambl', 'Ambléon (01300)')
check('engagement')
fill_in('dossier_link', with: '123')
find('.editable-champ-piece_justificative input[type=file]').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
blur
sleep(0.7)
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
# check data on the dossier
expect(user_dossier.brouillon?).to be true
expect(champ_value_for('text')).to eq('super texte')
expect(champ_value_for('textarea')).to eq('super textarea')
expect(champ_value_for('date')).to eq('2012-12-12')
expect(champ_value_for('datetime')).to eq('06/01/2030 07:05')
expect(champ_value_for('number')).to eq('42')
expect(champ_value_for('checkbox')).to eq('on')
expect(champ_value_for('civilite')).to eq('Mme')
expect(champ_value_for('email')).to eq('loulou@yopmail.com')
expect(champ_value_for('phone')).to eq('0123456789')
expect(champ_value_for('yes_no')).to eq('false')
expect(champ_value_for('simple_drop_down_list')).to eq('val2')
expect(champ_value_for('simple_choice_drop_down_list_long')).to eq('bravo')
expect(JSON.parse(champ_value_for('multiple_choice_drop_down_list_long'))).to match(['alpha', 'charly'])
expect(JSON.parse(champ_value_for('multiple_drop_down_list'))).to match(['val1', 'val3'])
expect(champ_value_for('pays')).to eq('Australie')
expect(champ_value_for('regions')).to eq('Martinique')
expect(champ_value_for('departements')).to eq('02 - Aisne')
expect(champ_value_for('communes')).to eq('Ambléon (01300)')
expect(champ_value_for('engagement')).to eq('on')
expect(champ_value_for('dossier_link')).to eq('123')
expect(champ_value_for('piece_justificative')).to be_nil # antivirus hasn't approved the file yet
## check data on the gui
expect(page).to have_field('text', with: 'super texte')
expect(page).to have_field('textarea', with: 'super textarea')
expect(page).to have_field('date', with: '2012-12-12')
check_date_and_time(Time.zone.parse('06/01/2030 7:05'), form_id_for_datetime('datetime'))
expect(page).to have_field('number', with: '42')
expect(page).to have_checked_field('checkbox')
expect(page).to have_checked_field('Madame')
expect(page).to have_field('email', with: 'loulou@yopmail.com')
expect(page).to have_field('phone', with: '0123456789')
expect(page).to have_checked_field('Non')
expect(page).to have_checked_field('val2')
expect(page).to have_checked_field('val1')
expect(page).to have_checked_field('val3')
expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo')
check_selected_values('multiple_choice_drop_down_list_long', ['alpha', 'charly'])
expect(page).to have_hidden_field('pays', with: 'Australie')
expect(page).to have_hidden_field('regions', with: 'Martinique')
expect(page).to have_hidden_field('departements', with: '02 - Aisne')
expect(page).to have_hidden_field('communes', with: 'Ambléon (01300)')
expect(page).to have_checked_field('engagement')
expect(page).to have_field('dossier_link', with: '123')
expect(page).to have_text('file.pdf')
expect(page).to have_text('analyse antivirus en cours')
end
let(:procedure_with_repetition) do
create(:procedure, :published, :for_individual, :with_repetition)
end
scenario 'fill a dossier with repetition', js: true do
log_in(user, procedure_with_repetition)
fill_individual
fill_in('sub type de champ', with: 'super texte')
expect(page).to have_field('sub type de champ', with: 'super texte')
click_on 'Ajouter un élément pour'
within '.row-1' do
fill_in('sub type de champ', with: 'un autre texte')
end
expect(page).to have_content('Supprimer', count: 2)
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
expect(page).to have_content('Supprimer', count: 2)
within '.row-1' do
click_on 'Supprimer lélément'
end
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
expect(page).to have_content('Supprimer', count: 1)
end
let(:simple_procedure) do
tdcs = [
build(:type_de_champ, mandatory: true, libelle: 'texte obligatoire'),
build(:type_de_champ, mandatory: false, libelle: 'texte optionnel')
]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
scenario 'save an incomplete dossier as draft but cannot not submit it', js: true do
log_in(user, simple_procedure)
fill_individual
# Check an incomplete dossier can be saved as a draft, even when mandatory fields are missing
fill_in('texte optionnel', with: 'ça ne suffira pas')
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
# Check an incomplete dossier cannot be submitted when mandatory fields are missing
click_on 'Déposer le dossier'
expect(user_dossier.reload.brouillon?).to be(true)
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
# Check a dossier can be submitted when all mandatory fields are filled
fill_in('texte obligatoire', with: 'super texte')
click_on 'Déposer le dossier'
expect(user_dossier.reload.en_construction?).to be(true)
expect(champ_value_for('texte obligatoire')).to eq('super texte')
expect(page).to have_current_path(merci_dossier_path(user_dossier))
end
let(:procedure_with_pj) do
tdcs = [build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative')]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
let(:procedure_with_pjs) do
tdcs = [
build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', position: 1),
build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 2', position: 2)
]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
let(:old_procedure_with_disabled_pj_validation) do
tdcs = [
create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', order_place: 1, skip_pj_validation: true)
]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
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('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
scenario 'add an invalid attachment on an old procedure where pj validation is disabled', js: true do
log_in(user, old_procedure_with_disabled_pj_validation)
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_no_text('La pièce justificative nest pas dun type accepté')
expect(page).to have_text('analyse antivirus en cours', count: 1)
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 sest produite pendant lenvoi du fichier')
expect(page).to have_button('Ré-essayer', visible: true)
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('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
context 'draft autosave' do
scenario 'autosave a draft', js: true do
log_in(user, simple_procedure)
fill_individual
expect(page).not_to have_button('Enregistrer le brouillon')
expect(page).to have_content('Votre brouillon est automatiquement enregistré')
fill_in('texte obligatoire', with: 'a valid user input')
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
visit current_path
expect(page).to have_field('texte obligatoire', with: 'a valid user input')
end
scenario 'retry on autosave error', :capybara_ignore_server_errors, js: true do
log_in(user, simple_procedure)
fill_individual
# Test autosave failure
allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_raise("Server is busy")
fill_in('texte obligatoire', with: 'a valid user input')
blur
expect(page).to have_css('span', text: 'Impossible denregistrer le brouillon', visible: true)
# Test that retrying after a failure works
allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_call_original
click_on 'réessayer'
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
visit current_path
expect(page).to have_field('texte obligatoire', with: 'a valid user input')
end
scenario 'autosave redirects to sign-in after being disconnected', js: true do
log_in(user, simple_procedure)
fill_individual
# When the user is disconnected
# (either because signing-out in another tab, or because the session cookie expired)
logout(:user)
fill_in('texte obligatoire', with: 'a valid user input')
blur
# … they are redirected to the sign-in page.
expect(page).to have_current_path(new_user_session_path)
# After sign-in, they are redirected back to their brouillon
sign_in_with(user.email, password)
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
fill_in('texte obligatoire', with: 'a valid user input')
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
end
end
private
def log_in(user, procedure)
login_as user, scope: :user
visit "/commencer/#{procedure.path}"
click_on 'Commencer la démarche'
expect(page).to have_content("Données didentité")
expect(page).to have_current_path(identite_dossier_path(user_dossier))
end
def form_id_for(libelle)
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
end
def form_id_for_datetime(libelle)
# The HTML for datetime is a bit specific since it has 5 selects, below here is a sample HTML
# So, we want to find the partial id of a datetime (partial because there are 5 ids:
# dossier_champs_attributes_3_value_1i, 2i, ... 5i) ; we are interested in the 'dossier_champs_attributes_3_value' part
# which is then completed in select_date_and_time and check_date_and_time
#
# We find the H2, find the first select in the next .datetime div, then strip the last 3 characters
#
# <h4 class="form-label">
# libelle
# </h4>
# <div class="datetime">
# <span class="hidden">
# <label for="dossier_champs_attributes_3_value_3i">Jour</label></span>
# <select id="dossier_champs_attributes_3_value_3i" name="dossier[champs_attributes][3][value(3i)]">
# <option value=""></option>
# <option value="1">1</option>
# <option value="2">2</option>
# <!-- … -->
# </select>
# <!-- … 4 other selects for month, year, minute and seconds -->
# </div>
e = find(:xpath, ".//div[contains(text()[normalize-space()], '#{libelle}')]")
e.sibling('.datetime').first('select')[:id][0..-4]
end
def champ_value_for(libelle)
champs = user_dossier.champs
champs.find { |c| c.libelle == libelle }.value
end
def fill_individual
choose 'Monsieur'
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
click_on 'Continuer'
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
def select_date_and_time(date, field)
select date.strftime('%Y'), from: "#{field}_1i" # year
select I18n.l(date, format: '%B'), from: "#{field}_2i" # month
select date.strftime('%-d'), from: "#{field}_3i" # day
select date.strftime('%H'), from: "#{field}_4i" # hour
select date.strftime('%M'), from: "#{field}_5i" # minute
end
def check_date_and_time(date, field)
expect(page).to have_selected_value("#{field}_1i", selected: date.strftime('%Y'))
expect(page).to have_selected_value("#{field}_2i", selected: I18n.l(date, format: '%B'))
expect(page).to have_selected_value("#{field}_3i", selected: date.strftime('%-d'))
expect(page).to have_selected_value("#{field}_4i", selected: date.strftime('%H'))
expect(page).to have_selected_value("#{field}_5i", selected: date.strftime('%M'))
end
end

View file

@ -0,0 +1,31 @@
describe 'Changing an email' do
let(:old_email) { 'old@email.com' }
let(:user) { create(:user, email: old_email) }
before do
login_as user, scope: :user
end
scenario 'is easy' do
new_email = 'new@email.com'
visit '/profil'
fill_in :user_email, with: new_email
perform_enqueued_jobs do
click_button 'Changer mon adresse'
end
expect(page).to have_content(I18n.t('devise.registrations.update_needs_confirmation'))
expect(page).to have_content(old_email)
expect(page).to have_content(new_email)
click_confirmation_link_for(new_email)
expect(page).to have_content(I18n.t('devise.confirmations.confirmed'))
expect(page).not_to have_content(old_email)
expect(page).to have_content(new_email)
expect(user.reload.email).to eq(new_email)
end
end

View file

@ -0,0 +1,132 @@
describe 'Creating a new dossier:' do
let(:user) { create(:user) }
let(:siret) { '41816609600051' }
let(:siren) { siret[0...9] }
context 'when the user is already signed in' do
before do
login_as user, scope: :user
end
context 'when the procedure has identification by individual' do
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, ask_birthday: ask_birthday) }
let(:ask_birthday) { false }
let(:expected_birthday) { nil }
before do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
expect(page).to have_procedure_description(procedure)
choose 'Monsieur'
fill_in 'individual_nom', with: 'Nom'
fill_in 'individual_prenom', with: 'Prenom'
end
shared_examples 'the user can create a new draft' do
it do
click_button('Continuer')
expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last))
expect(user.dossiers.first.individual.birthdate).to eq(expected_birthday)
end
end
context 'when the birthday is asked' do
let(:ask_birthday) { true }
let(:expected_birthday) { Date.new(1987, 10, 14) }
before do
fill_in 'individual_birthdate', with: birthday_format
end
context 'when the browser supports `type=date` input fields' do
let(:birthday_format) { '1987-10-14' }
it_behaves_like 'the user can create a new draft'
end
context 'when the browser does not support `type=date` input fields' do
let(:birthday_format) { '1987-10-14' }
it_behaves_like 'the user can create a new draft'
end
end
context 'when the birthday is not asked' do
let(:ask_birthday) { false }
let(:expected_birthday) { nil }
it_behaves_like 'the user can create a new draft'
end
end
context 'when identifying through SIRET' do
let(:procedure) { create(:procedure, :published, :with_service, :with_type_de_champ) }
let(:dossier) { procedure.dossiers.last }
before do
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/etablissements\/#{siret}/)
.to_return(status: 200, body: File.read('spec/fixtures/files/api_entreprise/etablissements.json'))
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/entreprises\/#{siren}/)
.to_return(status: 200, body: File.read('spec/fixtures/files/api_entreprise/entreprises.json'))
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/exercices\/#{siret}/)
.to_return(status: 200, body: File.read('spec/fixtures/files/api_entreprise/exercices.json'))
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/associations\/#{siret}/)
.to_return(status: 404, body: '')
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_mensuels_acoss_covid\/2020\/02\/entreprise\/#{siren}/)
.to_return(status: 404, body: '')
stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_annuels_acoss_covid\/#{siren}/)
.to_return(status: 404, body: '')
allow_any_instance_of(APIEntrepriseToken).to receive(:roles).and_return([])
allow_any_instance_of(APIEntrepriseToken).to receive(:expired?).and_return(false)
end
before { Timecop.freeze(Time.zone.local(2020, 3, 14)) }
after { Timecop.return }
scenario 'the user can enter the SIRET of its etablissement and create a new draft' do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_current_path siret_dossier_path(dossier)
expect(page).to have_procedure_description(procedure)
fill_in 'Numéro SIRET', with: siret
click_on 'Valider'
expect(page).to have_current_path(etablissement_dossier_path(dossier))
expect(page).to have_content('OCTO TECHNOLOGY')
click_on 'Continuer avec ces informations'
expect(page).to have_current_path(brouillon_dossier_path(dossier))
end
scenario 'the user is notified when its SIRET is invalid' do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_current_path(siret_dossier_path(dossier))
expect(page).to have_procedure_description(procedure)
fill_in 'Numéro SIRET', with: '0000'
click_on 'Valider'
expect(page).to have_current_path(siret_dossier_path(dossier))
expect(page).to have_content('Le numéro SIRET doit comporter 14 chiffres')
expect(page).to have_field('Numéro SIRET', with: '0000')
end
end
end
context 'when the user is not signed in' do
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published) }
scenario 'the user is an instructeur with untrusted device' do
visit commencer_path(path: procedure.path)
click_on "Jai déjà un compte"
sign_in_with(instructeur.email, instructeur.user.password, true)
expect(page).to have_current_path(commencer_path(path: procedure.path))
end
end
end

View file

@ -0,0 +1,61 @@
require 'system/users/dossier_shared_examples.rb'
describe 'Dossier details:' do
let(:user) { create(:user) }
let(:procedure) { create(:simple_procedure) }
let(:dossier) { create(:dossier, :en_construction, :with_individual, :with_commentaires, user: user, procedure: procedure) }
before do
visit_dossier dossier
end
scenario 'the user can see the summary of the dossier status' do
expect(page).to have_current_path(dossier_path(dossier))
expect(page).to have_content(dossier.id)
expect(page).to have_selector('.status-explanation')
expect(page).to have_text(dossier.commentaires.last.body)
end
describe "the user can see the mean time they are expected to wait" do
let(:other_dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure, en_construction_at: 10.days.ago, en_instruction_at: 9.days.ago, processed_at: Time.zone.now) }
context "when the dossier is in construction" do
it "displays the estimated wait duration" do
other_dossier
visit dossier_path(dossier)
expect(page).to have_text("Habituellement, les dossiers de cette démarche sont traités dans un délai de 10 jours.")
end
end
context "when the dossier is in instruction" do
let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_commentaires, user: user, procedure: procedure) }
it "displays the estimated wait duration" do
other_dossier
visit dossier_path(dossier)
expect(page).to have_text("Habituellement, les dossiers de cette démarche sont traités dans un délai de 10 jours.")
end
end
end
scenario 'the user is redirected from old URLs' do
visit "/users/dossiers/#{dossier.id}/recapitulatif"
expect(page).to have_current_path(dossier_path(dossier))
end
it_behaves_like 'the user can edit the submitted demande'
it_behaves_like 'the user can send messages to the instructeur'
private
def visit_dossier(dossier)
visit dossier_path(dossier)
expect(page).to have_current_path(new_user_session_path)
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_on 'Se connecter'
expect(page).to have_current_path(dossier_path(dossier))
end
end

View file

@ -0,0 +1,41 @@
RSpec.shared_examples 'the user can edit the submitted demande' do
scenario js: true do
visit dossier_path(dossier)
expect(page).to have_current_path(dossier_path(dossier))
click_on 'Demande'
expect(page).to have_current_path(demande_dossier_path(dossier))
click_on 'Modifier le dossier'
expect(page).to have_current_path(modifier_dossier_path(dossier))
fill_in('Texte obligatoire', with: 'Nouveau texte')
click_on 'Enregistrer les modifications du dossier'
expect(page).to have_current_path(demande_dossier_path(dossier))
expect(page).to have_content('Nouveau texte')
end
end
RSpec.shared_examples 'the user can send messages to the instructeur' do
let!(:commentaire) { create(:commentaire, dossier: dossier, email: 'instructeur@exemple.fr', body: 'Message envoyé à lusager') }
let(:message_body) { 'Message envoyé à linstructeur' }
scenario js: true do
visit dossier_path(dossier)
expect(page).to have_current_path(dossier_path(dossier))
click_on 'Messagerie'
expect(page).to have_current_path(messagerie_dossier_path(dossier))
expect(page).to have_content(commentaire.body)
fill_in 'commentaire_body', with: message_body
click_on 'Envoyer le message'
expect(page).to have_current_path(messagerie_dossier_path(dossier))
expect(page).to have_content('Message envoyé')
expect(page).to have_content(commentaire.body)
expect(page).to have_content(message_body)
end
end

View file

@ -0,0 +1,128 @@
require 'system/users/dossier_shared_examples.rb'
describe 'Invitations' do
let(:owner) { create(:user) }
let(:invited_user) { create(:user, email: 'user_invite@exemple.fr') }
let(:procedure) { create(:simple_procedure) }
let(:invite) { create(:invite, user: invited_user, dossier: dossier) }
context 'when the dossier is a brouillon' do
let!(:dossier) { create(:dossier, :with_individual, state: Dossier.states.fetch(:brouillon), user: owner, procedure: procedure) }
scenario 'on the form, the owner of a dossier can invite another user to collaborate on the dossier', js: true do
log_in(owner)
navigate_to_brouillon(dossier)
fill_in 'Texte obligatoire', with: 'Some edited value'
send_invite_to "user_invite@exemple.fr"
expect(page).to have_current_path(brouillon_dossier_path(dossier))
expect(page).to have_text("Une invitation a été envoyée à user_invite@exemple.fr.")
expect(page).to have_text("user_invite@exemple.fr")
# Ensure unsaved edits to the form are not lost
expect(page).to have_field('Texte obligatoire', with: 'Some edited value')
end
context 'when inviting someone without an existing account' do
let(:invite) { create(:invite, dossier: dossier, user: nil) }
let(:user_password) { 'my-s3cure-p4ssword' }
scenario 'an invited user can register using the registration link sent in the invitation email' do
# Click the invitation link
visit invite_path(invite, params: { email: invite.email })
expect(page).to have_current_path(new_user_registration_path, ignore_query: true)
expect(page).to have_field('user_email', with: invite.email)
# Create the account
sign_up_with invite.email, user_password
expect(page).to have_content('lien dactivation')
# Confirm the account
# (The user should be redirected to the dossier they was invited on)
click_confirmation_link_for invite.email
expect(page).to have_content('Votre compte a bien été confirmé.')
expect(page).to have_current_path(brouillon_dossier_path(dossier))
end
end
scenario 'an invited user can see and edit the draft', js: true do
navigate_to_invited_dossier(invite)
expect(page).to have_current_path(brouillon_dossier_path(dossier))
expect(page).to have_no_selector('.button.invite-user-action')
fill_in 'Texte obligatoire', with: 'Some edited value'
blur
expect(page).to have_field('Texte obligatoire', with: 'Some edited value')
end
scenario 'an invited user cannot submit the draft' do
navigate_to_invited_dossier(invite)
expect(page).to have_current_path(brouillon_dossier_path(dossier))
expect(page).to have_button('Déposer le dossier', disabled: true)
expect(page).to have_selector('.invite-cannot-submit')
end
end
context 'when the dossier is en_construction' do
let!(:dossier) { create(:dossier, :with_individual, :en_construction, user: owner, procedure: procedure) }
scenario 'on dossier details, the owner of a dossier can invite another user to collaborate on the dossier', js: true do
log_in(owner)
navigate_to_dossier(dossier)
send_invite_to "user_invite@exemple.fr"
expect(page).to have_current_path(dossier_path(dossier))
expect(page).to have_text("Une invitation a été envoyée à user_invite@exemple.fr.")
expect(page).to have_text("user_invite@exemple.fr")
end
context 'as an invited user' do
before do
navigate_to_invited_dossier(invite)
expect(page).to have_current_path(dossier_path(invite.dossier))
end
it_behaves_like 'the user can edit the submitted demande'
it_behaves_like 'the user can send messages to the instructeur'
end
end
private
def log_in(user)
visit '/'
click_on 'Connexion'
sign_in_with(user.email, user.password)
expect(page).to have_current_path(dossiers_path)
end
def navigate_to_brouillon(dossier)
expect(page).to have_current_path(dossiers_path)
click_on(dossier.id.to_s)
expect(page).to have_current_path(brouillon_dossier_path(dossier))
end
def navigate_to_dossier(dossier)
expect(page).to have_current_path(dossiers_path)
click_on(dossier.id.to_s)
expect(page).to have_current_path(dossier_path(dossier))
end
def navigate_to_invited_dossier(invite)
visit invite_path(invite)
expect(page).to have_current_path(new_user_session_path)
sign_in_with(invited_user.email, invited_user.password)
end
def send_invite_to(invited_email)
click_on "Inviter une personne à modifier ce dossier"
expect(page).to have_button("Envoyer une invitation", visible: true)
fill_in 'invite_email', with: invited_email
click_on "Envoyer une invitation"
end
end

View file

@ -0,0 +1,77 @@
describe 'linked dropdown lists' do
let(:password) { 'my-s3cure-p4ssword' }
let!(:user) { create(:user, password: password) }
let(:list_items) do
<<~END_OF_LIST
--Primary 1--
Secondary 1.1
Secondary 1.2
--Primary 2--
Secondary 2.1
Secondary 2.2
Secondary 2.3
END_OF_LIST
end
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, libelle: 'linked dropdown', drop_down_list_value: list_items) }
let!(:procedure) do
create(:procedure, :published, :for_individual, types_de_champ: [type_de_champ])
end
let(:user_dossier) { user.dossiers.first }
scenario 'change primary value, secondary options are updated', js: true do
log_in(user.email, password, procedure)
fill_individual
# Select a primary value
select('Primary 2', from: primary_id_for('linked dropdown'))
# Secondary menu reflects chosen primary value
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 2.1', 'Secondary 2.2', 'Secondary 2.3'])
# Select another primary value
select('Primary 1', from: primary_id_for('linked dropdown'))
# Secondary menu gets updated
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 1.1', 'Secondary 1.2'])
end
private
def log_in(email, password, procedure)
visit "/commencer/#{procedure.path}"
click_on 'Jai déjà un compte'
expect(page).to have_current_path(new_user_session_path)
sign_in_with(email, password)
expect(page).to have_current_path(commencer_path(path: procedure.path))
click_on 'Commencer la démarche'
expect(page).to have_content("Données didentité")
expect(page).to have_current_path(identite_dossier_path(user_dossier))
end
def fill_individual
choose 'Monsieur'
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
click_on 'Continuer'
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
def primary_id_for(libelle)
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
end
def secondary_id_for(libelle)
primary_id = primary_id_for(libelle)
find("\##{primary_id}")
.ancestor('.editable-champ')
.find("[data-secondary]")['id']
end
end

View file

@ -0,0 +1,152 @@
describe 'user access to the list of their dossiers' do
let(:user) { create(:user) }
let!(:dossier_brouillon) { create(:dossier, user: user) }
let!(:dossier_en_construction) { create(:dossier, :with_all_champs, :en_construction, user: user) }
let!(:dossier_en_instruction) { create(:dossier, :en_instruction, user: user) }
let!(:dossier_archived) { create(:dossier, :en_instruction, :archived, user: user) }
let(:dossiers_per_page) { 25 }
let(:last_updated_dossier) { dossier_en_construction }
before do
@default_per_page = Dossier.default_per_page
Dossier.paginates_per dossiers_per_page
last_updated_dossier.update_column(:updated_at, "19/07/2052 15:35".to_time)
login_as user, scope: :user
visit dossiers_path
end
after do
Dossier.paginates_per @default_per_page
end
it 'the list of dossier is displayed' do
expect(page).to have_content(dossier_brouillon.procedure.libelle)
expect(page).to have_content(dossier_en_construction.procedure.libelle)
expect(page).to have_content(dossier_en_instruction.procedure.libelle)
expect(page).to have_content(dossier_archived.procedure.libelle)
end
it 'the list must be ordered by last updated' do
expect(page.body).to match(/#{last_updated_dossier.procedure.libelle}.*#{dossier_en_instruction.procedure.libelle}/m)
end
context 'when there are dossiers from other users' do
let!(:dossier_other_user) { create(:dossier) }
it 'doesnt display dossiers belonging to other users' do
expect(page).not_to have_content(dossier_other_user.procedure.libelle)
end
end
context 'when there is more than one page' do
let(:dossiers_per_page) { 2 }
scenario 'the user can navigate through the other pages' do
expect(page).not_to have_content(dossier_en_instruction.procedure.libelle)
page.click_link("Suivant")
expect(page).to have_content(dossier_en_instruction.procedure.libelle)
end
end
context 'when user clicks on a projet in list' do
before do
page.click_on(dossier_en_construction.procedure.libelle)
end
scenario 'user is redirected to dossier page' do
expect(page).to have_current_path(dossier_path(dossier_en_construction))
end
end
describe 'deletion' do
it 'should have links to delete dossiers' do
expect(page).to have_link(nil, href: ask_deletion_dossier_path(dossier_brouillon))
expect(page).to have_link(nil, href: ask_deletion_dossier_path(dossier_en_construction))
expect(page).not_to have_link(nil, href: ask_deletion_dossier_path(dossier_en_instruction))
end
context 'when user clicks on delete button', js: true do
scenario 'the dossier is deleted' do
within(:css, "tr[data-dossier-id=\"#{dossier_brouillon.id}\"]") do
click_on 'Actions'
page.accept_alert('Confirmer la suppression ?') do
click_on 'Supprimer le dossier'
end
end
expect(page).to have_content('Votre dossier a bien été supprimé.')
expect(page).not_to have_content(dossier_brouillon.procedure.libelle)
end
end
end
describe "recherche" do
context "when the dossier does not exist" do
before do
page.find_by_id('q').set(10000000)
click_button("Rechercher")
end
it "shows an error message on the dossiers page" do
expect(current_path).to eq(dossiers_path)
expect(page).to have_content("Vous navez pas de dossiers contenant « 10000000 ».")
end
end
context "when the dossier does not belong to the user" do
let!(:dossier_other_user) { create(:dossier) }
before do
page.find_by_id('q').set(dossier_other_user.id)
click_button("Rechercher")
end
it "shows an error message on the dossiers page" do
expect(current_path).to eq(dossiers_path)
expect(page).to have_content("Vous navez pas de dossiers contenant « #{dossier_other_user.id} ».")
end
end
context "when the dossier belongs to the user" do
before do
page.find_by_id('q').set(dossier_en_construction.id)
click_button("Rechercher")
end
it "redirects to the dossier page" do
expect(current_path).to eq(dossier_path(dossier_en_construction))
end
end
context "when user search for something inside the dossier" do
let(:dossier_en_construction2) { create(:dossier, :with_all_champs, :en_construction, user: user) }
before do
page.find_by_id('q').set(dossier_en_construction.champs.first.value)
end
context 'when it only matches one dossier' do
before do
click_button("Rechercher")
end
it "redirects to the dossier page" do
expect(current_path).to eq(dossier_path(dossier_en_construction))
end
end
context 'when it matches multiple dossier' do
before do
dossier_en_construction2.champs.first.update(value: dossier_en_construction.champs.first.value)
click_button("Rechercher")
end
it "redirects to the search results" do
expect(current_path).to eq(recherche_dossiers_path)
expect(page).to have_content(dossier_en_construction.id)
expect(page).to have_content(dossier_en_construction2.id)
end
end
end
end
end

View file

@ -0,0 +1,111 @@
describe 'Managing password:' do
context 'for simple users' do
let(:user) { create(:user) }
let(:new_password) { 'a simple password' }
scenario 'a simple user can reset their password' do
visit root_path
click_on 'Connexion'
click_on 'Mot de passe oublié ?'
expect(page).to have_current_path(new_user_password_path)
fill_in 'Email', with: user.email
perform_enqueued_jobs do
click_on 'Demander un nouveau mot de passe'
end
expect(page).to have_text 'Nous vous avons envoyé un email'
expect(page).to have_text user.email
click_reset_password_link_for user.email
expect(page).to have_content 'Changement de mot de passe'
fill_in 'user_password', with: new_password
fill_in 'user_password_confirmation', with: new_password
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a bien été modifié.')
end
end
context 'for admins' do
let(:administrateur) { create(:administrateur) }
let(:user) { administrateur.user }
let(:weak_password) { '12345678' }
let(:strong_password) { 'a new, long, and complicated password!' }
scenario 'an admin can reset their password', js: true do
visit root_path
click_on 'Connexion'
click_on 'Mot de passe oublié ?'
expect(page).to have_current_path(new_user_password_path)
fill_in 'Email', with: user.email
perform_enqueued_jobs do
click_on 'Demander un nouveau mot de passe'
end
expect(page).to have_text 'Nous vous avons envoyé un email'
expect(page).to have_text user.email
click_reset_password_link_for user.email
expect(page).to have_content 'Changement de mot de passe'
fill_in 'user_password', with: weak_password
fill_in 'user_password_confirmation', with: weak_password
expect(page).to have_text('Mot de passe très vulnérable')
expect(page).to have_button('Changer le mot de passe', disabled: true)
fill_in 'user_password', with: strong_password
fill_in 'user_password_confirmation', with: strong_password
expect(page).to have_text('Mot de passe suffisamment fort et sécurisé')
expect(page).to have_button('Changer le mot de passe', disabled: false)
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a bien été modifié.')
end
end
context 'for super-admins' do
let(:super_admin) { create(:super_admin) }
let(:weak_password) { '12345678' }
let(:strong_password) { 'a new, long, and complicated password!' }
scenario 'a super-admin can reset their password', js: true do
visit manager_root_path
click_on 'Mot de passe oublié'
expect(page).to have_current_path(new_super_admin_password_path)
fill_in 'Email', with: super_admin.email
perform_enqueued_jobs do
click_on 'Demander un nouveau mot de passe'
end
expect(page).to have_text 'vous recevrez un lien vous permettant de récupérer votre mot de passe'
click_reset_password_link_for super_admin.email
expect(page).to have_content 'Changement de mot de passe'
fill_in 'super_admin_password', with: weak_password
fill_in 'super_admin_password_confirmation', with: weak_password
expect(page).to have_text('Mot de passe très vulnérable')
expect(page).to have_button('Changer le mot de passe', disabled: true)
fill_in 'super_admin_password', with: strong_password
fill_in 'super_admin_password_confirmation', with: strong_password
expect(page).to have_text('Mot de passe suffisamment fort et sécurisé')
expect(page).to have_button('Changer le mot de passe', disabled: false)
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a bien été modifié.')
end
end
scenario 'the password reset token has expired' do
visit edit_user_password_path(reset_password_token: 'invalid-password-token')
expect(page).to have_content 'Changement de mot de passe'
fill_in 'user_password', with: 'SomePassword'
fill_in 'user_password_confirmation', with: 'SomePassword'
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre lien de nouveau mot de passe a expiré')
end
end

View file

@ -0,0 +1,15 @@
describe 'Sign out' do
context 'when a user is logged in' do
let(:user) { create(:administrateur).user }
before { login_as user, scope: :user }
scenario 'he can sign out' do
visit dossiers_path
click_on 'Se déconnecter'
expect(page).to have_current_path(root_path)
end
end
end

View file

@ -0,0 +1,147 @@
describe 'Signing up:' do
let(:user_email) { generate :user_email }
let(:user_password) { 'my-s3cure-p4ssword' }
let(:procedure) { create :simple_procedure, :with_service }
scenario 'a new user can sign-up from scratch' do
visit new_user_registration_path
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
expect(page).to have_content('Votre compte a bien été confirmé.')
expect(page).to have_current_path dossiers_path
end
context 'when the user makes a typo in their email address' do
let(:procedure) { create :simple_procedure, :with_service }
before do
visit commencer_path(path: procedure.path)
click_on "Créer un compte #{APPLICATION_NAME}"
expect(page).to have_selector('.suspect-email', visible: false)
fill_in 'Email', with: 'bidou@yahoo.rf'
fill_in 'Mot de passe', with: '12345'
end
scenario 'they can accept the suggestion', js: true do
expect(page).to have_selector('.suspect-email', visible: true)
click_on 'Oui'
expect(page).to have_field("Email", :with => 'bidou@yahoo.fr')
expect(page).to have_selector('.suspect-email', visible: false)
end
scenario 'they can discard the suggestion', js: true do
expect(page).to have_selector('.suspect-email', visible: true)
click_on 'Non'
expect(page).to have_field("Email", :with => 'bidou@yahoo.rf')
expect(page).to have_selector('.suspect-email', visible: false)
end
scenario 'they can fix the typo themselves', js: true do
expect(page).to have_selector('.suspect-email', visible: true)
fill_in 'Email', with: 'bidou@yahoo.fr'
blur
expect(page).to have_selector('.suspect-email', visible: false)
end
end
scenario 'a new user cant sign-up with too short password when visiting a procedure' do
visit commencer_path(path: procedure.path)
click_on "Créer un compte #{APPLICATION_NAME}"
expect(page).to have_current_path new_user_registration_path
sign_up_with user_email, '1234567'
expect(page).to have_current_path user_registration_path
expect(page).to have_content 'Le mot de passe est trop court'
# Then with a good password
sign_up_with user_email, user_password
expect(page).to have_current_path new_user_confirmation_path user: { email: user_email }
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
end
context 'when visiting a procedure' do
let(:procedure) { create :simple_procedure, :with_service }
scenario 'a new user can sign-up and fill the procedure' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte'
expect(page).to have_current_path new_user_registration_path
expect(page).to have_procedure_description(procedure)
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for(user_email, in_another_browser: true)
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content I18n.t('devise.confirmations.confirmed')
click_on 'Commencer la démarche'
expect(page).to have_current_path identite_dossier_path(procedure.reload.dossiers.last)
expect(page).to have_procedure_description(procedure)
end
end
context 'when the user is not confirmed yet' do
before do
create(:user, :unconfirmed, email: user_email, password: user_password)
end
scenario 'the email confirmation page is displayed' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte'
sign_up_with user_email, user_password
# The same page than for initial sign-ups is displayed, to avoid leaking informations
# about the account existence.
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
# The confirmation email is sent again
confirmation_email = open_email(user_email)
expect(confirmation_email.body).to have_text('Pour activer votre compte')
click_confirmation_link_for(user_email, in_another_browser: true)
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content I18n.t('devise.confirmations.confirmed')
expect(page).to have_content 'Commencer la démarche'
end
end
context 'when the user already has a confirmed account' do
before do
create(:user, email: user_email, password: user_password)
end
scenario 'they get a warning email, containing a link to the procedure' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte'
sign_up_with user_email, user_password
# The same page than for initial sign-ups is displayed, to avoid leaking informations
# about the accound existence.
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
# A warning email is sent
warning_email = open_email(user_email)
expect(warning_email.body).to have_text('Votre compte existe déjà')
# When clicking the main button, the user is redirected directly to
# the sign-in page for the procedure they were initially starting.
click_procedure_sign_in_link_for user_email
expect(page).to have_current_path new_user_session_path
expect(page).to have_procedure_description(procedure)
end
end
end

View file

@ -0,0 +1,32 @@
describe 'Transfer dossier:' do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:procedure) { create(:simple_procedure) }
let(:dossier) { create(:dossier, :en_construction, :with_individual, :with_commentaires, user: user, procedure: procedure) }
before do
dossier
login_as user, scope: :user
visit dossiers_path
end
scenario 'the user can transfer dossier to another user' do
within(:css, "tr[data-dossier-id=\"#{dossier.id}\"]") do
click_on 'Actions'
click_on 'Transferer le dossier'
end
expect(page).to have_current_path(transferer_dossier_path(dossier))
expect(page).to have_content("Transferer le dossier en construction nº #{dossier.id}")
fill_in 'Email du compte destinataire', with: other_user.email
click_on 'Envoyer la demande de transfert'
logout
login_as other_user, scope: :user
visit dossiers_path
expect(page).to have_content("Demande de transfert Nº #{dossier.reload.transfer.id} envoyé par #{user.email}")
click_on 'Accepter'
expect(page).to have_current_path(dossiers_path)
end
end