commit
f2f85f0d8f
20 changed files with 507 additions and 14 deletions
|
@ -1,7 +1,9 @@
|
|||
require:
|
||||
- rubocop/rspec/focused
|
||||
- ./lib/cops/unscoped.rb
|
||||
- ./lib/cops/add_concurrent_index.rb
|
||||
- ./lib/cops/application_name.rb
|
||||
- ./lib/cops/unscoped.rb
|
||||
|
||||
inherit_gem:
|
||||
rubocop-rails_config:
|
||||
- config/rails.yml
|
||||
|
@ -14,7 +16,7 @@ AllCops:
|
|||
- "node_modules/**/*"
|
||||
- "vendor/**/*"
|
||||
|
||||
DS/Unscoped:
|
||||
DS/AddConcurrentIndex:
|
||||
Enabled: true
|
||||
|
||||
DS/ApplicationName:
|
||||
|
@ -25,6 +27,9 @@ DS/ApplicationName:
|
|||
- './lib/linters/application_name_linter.rb'
|
||||
- "./spec/**/*"
|
||||
|
||||
DS/Unscoped:
|
||||
Enabled: true
|
||||
|
||||
Bundler/DuplicatedGem:
|
||||
Enabled: true
|
||||
|
||||
|
|
4
app/controllers/manager/super_admins_controller.rb
Normal file
4
app/controllers/manager/super_admins_controller.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module Manager
|
||||
class SuperAdminsController < Manager::ApplicationController
|
||||
end
|
||||
end
|
39
app/dashboards/super_admin_dashboard.rb
Normal file
39
app/dashboards/super_admin_dashboard.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class SuperAdminDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
email: Field::String,
|
||||
encrypted_password: Field::String,
|
||||
reset_password_sent_at: Field::DateTime,
|
||||
remember_created_at: Field::DateTime,
|
||||
sign_in_count: Field::Number,
|
||||
current_sign_in_at: Field::DateTime,
|
||||
last_sign_in_at: Field::DateTime,
|
||||
current_sign_in_ip: Field::String,
|
||||
last_sign_in_ip: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
failed_attempts: Field::Number,
|
||||
locked_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = [:id, :email, :created_at, :updated_at, :reset_password_sent_at, :failed_attempts, :locked_at].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = [:id, :email, :created_at, :updated_at, :reset_password_sent_at, :failed_attempts, :locked_at].freeze
|
||||
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
end
|
161
app/lib/database/migration_helpers.rb
Normal file
161
app/lib/database/migration_helpers.rb
Normal file
|
@ -0,0 +1,161 @@
|
|||
# Some of this file is lifted from Gitlab's `lib/gitlab/database/migration_helpers.rb``
|
||||
|
||||
# Copyright (c) 2011-present GitLab B.V.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
module Database::MigrationHelpers
|
||||
# Given a combination of columns, return the records that appear twice of more
|
||||
# with the same values.
|
||||
#
|
||||
# Returns tuples of ids.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# find_duplicates :tags, [:post_id, :label]
|
||||
# # [[7, 3], [1, 9, 4]]
|
||||
def find_duplicates(table_name, column_names)
|
||||
str_column_names = column_names.map(&:to_s)
|
||||
columns = str_column_names.join(', ')
|
||||
t_columns = str_column_names.map { |c| "t.#{c}" }.join(', ')
|
||||
|
||||
duplicates = execute <<-SQL.squish
|
||||
SELECT t.id, #{t_columns}
|
||||
FROM #{table_name} t
|
||||
INNER JOIN (
|
||||
SELECT #{columns}, COUNT(*)
|
||||
FROM #{table_name}
|
||||
GROUP BY #{columns}
|
||||
HAVING COUNT(*) > 1
|
||||
) dt
|
||||
ON #{column_names.map { |c| "t.#{c} = dt.#{c}" }.join(' AND ')}
|
||||
SQL
|
||||
|
||||
grouped_duplicates = duplicates
|
||||
.group_by { |r| r.values_at(*str_column_names) }
|
||||
.values
|
||||
|
||||
# Return the duplicate ids only (instead of a heavier record)
|
||||
grouped_duplicates.map do |records|
|
||||
records.map { |r| r["id"] }
|
||||
end
|
||||
end
|
||||
|
||||
# Given a combination of columns, delete the records that appear twice of more
|
||||
# with the same values.
|
||||
#
|
||||
# The first record found is kept, and the other are discarded.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# delete_duplicates :tags, [:post_id, :label]
|
||||
def delete_duplicates(table_name, column_names)
|
||||
duplicates = nil
|
||||
disable_statement_timeout do
|
||||
duplicates = find_duplicates(table_name, column_names)
|
||||
end
|
||||
|
||||
duplicates.each do |ids|
|
||||
duplicate_ids = ids.drop(1) # drop all records except the first
|
||||
execute "DELETE FROM #{table_name} WHERE (#{table_name}.id IN (#{duplicate_ids.join(', ')}))"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new index, concurrently
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# add_concurrent_index :users, :some_column
|
||||
#
|
||||
# See Rails' `add_index` for more info on the available arguments.
|
||||
def add_concurrent_index(table_name, column_name, **options)
|
||||
if transaction_open?
|
||||
raise 'add_concurrent_index can not be run inside a transaction, ' \
|
||||
'you can disable transactions by calling disable_ddl_transaction! ' \
|
||||
'in the body of your migration class'
|
||||
end
|
||||
|
||||
options = options.merge({ algorithm: :concurrently })
|
||||
|
||||
if index_exists?(table_name, column_name, **options)
|
||||
Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
|
||||
return
|
||||
end
|
||||
|
||||
disable_statement_timeout do
|
||||
add_index(table_name, column_name, **options)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def statement_timeout_disabled?
|
||||
# This is a string of the form "100ms" or "0" when disabled
|
||||
connection.select_value('SHOW statement_timeout') == "0"
|
||||
end
|
||||
|
||||
# Long-running migrations may take more than the timeout allowed by
|
||||
# the database. Disable the session's statement timeout to ensure
|
||||
# migrations don't get killed prematurely.
|
||||
#
|
||||
# There are two possible ways to disable the statement timeout:
|
||||
#
|
||||
# - Per transaction (this is the preferred and default mode)
|
||||
# - Per connection (requires a cleanup after the execution)
|
||||
#
|
||||
# When using a per connection disable statement, code must be inside
|
||||
# a block so we can automatically execute `RESET ALL` after block finishes
|
||||
# otherwise the statement will still be disabled until connection is dropped
|
||||
# or `RESET ALL` is executed
|
||||
def disable_statement_timeout
|
||||
if block_given?
|
||||
if statement_timeout_disabled?
|
||||
# Don't do anything if the statement_timeout is already disabled
|
||||
# Allows for nested calls of disable_statement_timeout without
|
||||
# resetting the timeout too early (before the outer call ends)
|
||||
yield
|
||||
else
|
||||
begin
|
||||
execute('SET statement_timeout TO 0')
|
||||
|
||||
yield
|
||||
ensure
|
||||
execute('RESET ALL')
|
||||
end
|
||||
end
|
||||
else
|
||||
unless transaction_open?
|
||||
raise <<~ERROR
|
||||
Cannot call disable_statement_timeout() without a transaction open or outside of a transaction block.
|
||||
If you don't want to use a transaction wrap your code in a block call:
|
||||
|
||||
disable_statement_timeout { # code that requires disabled statement here }
|
||||
|
||||
This will make sure statement_timeout is disabled before and reset after the block execution is finished.
|
||||
ERROR
|
||||
end
|
||||
|
||||
execute('SET LOCAL statement_timeout TO 0')
|
||||
end
|
||||
end
|
||||
|
||||
def execute(sql)
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
end
|
24
app/mailers/expert_mailer.rb
Normal file
24
app/mailers/expert_mailer.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class ExpertMailer < ApplicationMailer
|
||||
helper MailerHelper
|
||||
layout 'mailers/layout'
|
||||
|
||||
def send_dossier_decision(avis_id)
|
||||
@avis = Avis.eager_load(:dossier).find(avis_id)
|
||||
@dossier = @avis.dossier
|
||||
email = @avis.expert.email
|
||||
@decision = decision_dossier(@dossier)
|
||||
subject = "Dossier n° #{@dossier.id} a été #{@decision} - #{@dossier.procedure.libelle}"
|
||||
|
||||
mail(to: email, subject: subject)
|
||||
end
|
||||
end
|
||||
|
||||
def decision_dossier(dossier)
|
||||
if dossier.accepte?
|
||||
'accepté'
|
||||
elsif dossier.sans_suite?
|
||||
'classé sans suite'
|
||||
elsif dossier.refuse?
|
||||
'refusé'
|
||||
end
|
||||
end
|
|
@ -13,8 +13,6 @@
|
|||
# instructeur_id :integer
|
||||
#
|
||||
class AssignTo < ApplicationRecord
|
||||
self.ignored_columns = [:procedure_id]
|
||||
|
||||
belongs_to :instructeur, optional: false
|
||||
belongs_to :groupe_instructeur, optional: false
|
||||
has_one :procedure_presentation, dependent: :destroy
|
||||
|
|
|
@ -54,8 +54,6 @@ class Avis < ApplicationRecord
|
|||
attr_accessor :emails
|
||||
attr_accessor :invite_linked_dossiers
|
||||
|
||||
self.ignored_columns = [:instructeur_id, :tmp_expert_migrated]
|
||||
|
||||
def email_to_display
|
||||
expert&.email
|
||||
end
|
||||
|
|
|
@ -62,6 +62,9 @@ class Dossier < ApplicationRecord
|
|||
has_one :attestation, dependent: :destroy
|
||||
has_one :france_connect_information, through: :user
|
||||
|
||||
# FIXME: some dossiers have more than one attestation
|
||||
has_many :attestations, dependent: :destroy
|
||||
|
||||
has_one_attached :justificatif_motivation
|
||||
has_one_attached :pdf_export_for_instructeur
|
||||
|
||||
|
@ -644,6 +647,7 @@ class Dossier < ApplicationRecord
|
|||
save!
|
||||
remove_titres_identite!
|
||||
NotificationMailer.send_closed_notification(self).deliver_later
|
||||
send_dossier_decision_to_experts(self)
|
||||
log_dossier_operation(instructeur, :accepter, self)
|
||||
end
|
||||
|
||||
|
@ -671,6 +675,7 @@ class Dossier < ApplicationRecord
|
|||
save!
|
||||
remove_titres_identite!
|
||||
NotificationMailer.send_refused_notification(self).deliver_later
|
||||
send_dossier_decision_to_experts(self)
|
||||
log_dossier_operation(instructeur, :refuser, self)
|
||||
end
|
||||
|
||||
|
@ -684,6 +689,7 @@ class Dossier < ApplicationRecord
|
|||
save!
|
||||
remove_titres_identite!
|
||||
NotificationMailer.send_without_continuation_notification(self).deliver_later
|
||||
send_dossier_decision_to_experts(self)
|
||||
log_dossier_operation(instructeur, :classer_sans_suite, self)
|
||||
end
|
||||
|
||||
|
@ -938,4 +944,21 @@ class Dossier < ApplicationRecord
|
|||
DossierMailer.notify_brouillon_not_submitted(dossier).deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
def send_dossier_decision_to_experts(dossier)
|
||||
avis_experts_procedures_ids = Avis
|
||||
.joins(:experts_procedure)
|
||||
.where(dossier: dossier, experts_procedures: { allow_decision_access: true })
|
||||
.with_answer
|
||||
.distinct
|
||||
.pluck('avis.id, experts_procedures.id')
|
||||
|
||||
# rubocop:disable Lint/UnusedBlockArgument
|
||||
avis_ids = avis_experts_procedures_ids
|
||||
.uniq { |(avis_id, experts_procedures_id)| experts_procedures_id }
|
||||
.map { |(avis_id, _)| avis_id }
|
||||
# rubocop:enable Lint/UnusedBlockArgument
|
||||
|
||||
avis_ids.each { |avis_id| ExpertMailer.send_dossier_decision(avis_id).deliver_later }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,8 +47,6 @@
|
|||
# dossier_id :integer
|
||||
#
|
||||
class Etablissement < ApplicationRecord
|
||||
self.ignored_columns = [:entreprise_id]
|
||||
|
||||
belongs_to :dossier, optional: true
|
||||
|
||||
has_one :champ, class_name: 'Champs::SiretChamp'
|
||||
|
|
|
@ -45,8 +45,6 @@
|
|||
#
|
||||
|
||||
class Procedure < ApplicationRecord
|
||||
self.ignored_columns = ['archived_at', 'csv_export_queued', 'xlsx_export_queued', 'ods_export_queued']
|
||||
|
||||
include ProcedureStatsConcern
|
||||
|
||||
include Discard::Model
|
||||
|
|
|
@ -18,6 +18,10 @@ class ProcedureRevision < ApplicationRecord
|
|||
has_many :types_de_champ, through: :revision_types_de_champ, source: :type_de_champ
|
||||
has_many :types_de_champ_private, through: :revision_types_de_champ_private, source: :type_de_champ
|
||||
|
||||
has_many :owned_types_de_champ, class_name: 'TypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
|
||||
has_one :draft_procedure, class_name: 'Procedure', foreign_key: :draft_revision_id, dependent: :nullify, inverse_of: :draft_revision
|
||||
has_one :published_procedure, class_name: 'Procedure', foreign_key: :published_revision_id, dependent: :nullify, inverse_of: :published_revision
|
||||
|
||||
def build_champs
|
||||
types_de_champ.map(&:build_champ)
|
||||
end
|
||||
|
|
20
app/views/expert_mailer/send_dossier_decision.html.haml
Normal file
20
app/views/expert_mailer/send_dossier_decision.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- avis_link = expert_avis_url(@dossier.procedure, @avis)
|
||||
%p
|
||||
Bonjour,
|
||||
|
||||
%p
|
||||
Une décision a été rendue sur le dossier n°
|
||||
%strong= @dossier.id
|
||||
et ce dossier a préalablement reçu votre avis.
|
||||
%p
|
||||
Le dossier a été
|
||||
= succeed '.' do
|
||||
%strong= @decision
|
||||
|
||||
%p
|
||||
Pour plus d'informations, cliquez sur le lien ci-dessous :
|
||||
|
||||
%p
|
||||
= round_button("Voir le dossier", avis_link, :primary)
|
||||
|
||||
= render partial: "layouts/mailers/signature"
|
|
@ -52,6 +52,8 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :services, only: [:index, :show]
|
||||
|
||||
resources :super_admins, only: [:index, :show, :destroy]
|
||||
|
||||
post 'demandes/create_administrateur'
|
||||
post 'demandes/refuse_administrateur'
|
||||
|
||||
|
|
55
lib/cops/add_concurrent_index.rb
Normal file
55
lib/cops/add_concurrent_index.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (c) 2011-present GitLab B.V.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
if defined?(RuboCop)
|
||||
module RuboCop
|
||||
module Cop
|
||||
module DS
|
||||
# Cop that checks if `add_concurrent_index` is used with `up`/`down` methods
|
||||
# and not `change`.
|
||||
class AddConcurrentIndex < Cop
|
||||
MSG = '`add_concurrent_index` is not reversible so you must manually define ' \
|
||||
'the `up` and `down` methods in your migration class, using `remove_index` in `down`'
|
||||
|
||||
def on_send(node)
|
||||
dirname = File.dirname(node.location.expression.source_buffer.name)
|
||||
return unless dirname.end_with?('db/migrate')
|
||||
|
||||
name = node.children[1]
|
||||
|
||||
return unless name == :add_concurrent_index
|
||||
|
||||
node.each_ancestor(:def) do |def_node|
|
||||
next unless method_name(def_node) == :change
|
||||
|
||||
add_offense(def_node, location: :name)
|
||||
end
|
||||
end
|
||||
|
||||
def method_name(node)
|
||||
node.children.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,7 +19,7 @@ feature 'Signing up:' do
|
|||
|
||||
before do
|
||||
visit commencer_path(path: procedure.path)
|
||||
click_on 'Créer un compte demarches-simplifiees.fr'
|
||||
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'
|
||||
|
@ -49,7 +49,7 @@ feature 'Signing up:' do
|
|||
|
||||
scenario 'a new user can’t sign-up with too short password when visiting a procedure' do
|
||||
visit commencer_path(path: procedure.path)
|
||||
click_on 'Créer un compte demarches-simplifiees.fr'
|
||||
click_on "Créer un compte #{APPLICATION_NAME}"
|
||||
|
||||
expect(page).to have_current_path new_user_registration_path
|
||||
sign_up_with user_email, '1234567'
|
||||
|
|
|
@ -8,14 +8,14 @@ RSpec.describe ConservationDeDonneesHelper, type: :helper do
|
|||
let(:dans_ds) { 3 }
|
||||
let(:hors_ds) { 6 }
|
||||
|
||||
it { is_expected.to eq(["Dans demarches-simplifiees.fr : 3 mois", "Par l’administration : 6 mois"]) }
|
||||
it { is_expected.to eq(["Dans #{APPLICATION_NAME} : 3 mois", "Par l’administration : 6 mois"]) }
|
||||
end
|
||||
|
||||
context "when only in-app retention time is set" do
|
||||
let(:dans_ds) { 3 }
|
||||
let(:hors_ds) { nil }
|
||||
|
||||
it { is_expected.to eq(["Dans demarches-simplifiees.fr : 3 mois"]) }
|
||||
it { is_expected.to eq(["Dans #{APPLICATION_NAME} : 3 mois"]) }
|
||||
end
|
||||
|
||||
context "when only out of app retention time is set" do
|
||||
|
|
133
spec/lib/database/migration_helpers_spec.rb
Normal file
133
spec/lib/database/migration_helpers_spec.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
describe Database::MigrationHelpers do
|
||||
class TestLabel < ApplicationRecord
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
ActiveRecord::Migration.suppress_messages do
|
||||
ActiveRecord::Migration.create_table "test_labels", force: true do |t|
|
||||
t.string :label
|
||||
t.integer :user_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# User 1 labels
|
||||
TestLabel.create({ id: 1, label: 'Important', user_id: 1 })
|
||||
TestLabel.create({ id: 2, label: 'Urgent', user_id: 1 })
|
||||
TestLabel.create({ id: 3, label: 'Done', user_id: 1 })
|
||||
TestLabel.create({ id: 4, label: 'Bug', user_id: 1 })
|
||||
|
||||
# User 2 labels
|
||||
TestLabel.create({ id: 5, label: 'Important', user_id: 2 })
|
||||
TestLabel.create({ id: 6, label: 'Critical', user_id: 2 })
|
||||
|
||||
# Duplicates
|
||||
TestLabel.create({ id: 7, label: 'Urgent', user_id: 1 })
|
||||
TestLabel.create({ id: 8, label: 'Important', user_id: 2 })
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
ActiveRecord::Migration.suppress_messages do
|
||||
ActiveRecord::Migration.drop_table :test_labels, force: true
|
||||
end
|
||||
end
|
||||
|
||||
let(:model) { ActiveRecord::Migration.new.extend(Database::MigrationHelpers) }
|
||||
|
||||
describe '.find_duplicates' do
|
||||
context 'using a single column for uniqueness' do
|
||||
subject do
|
||||
model.find_duplicates(:test_labels, [:label])
|
||||
end
|
||||
|
||||
it 'finds duplicates' do
|
||||
expect(subject.length).to eq 2
|
||||
end
|
||||
|
||||
it 'finds three labels with "Important"' do
|
||||
expect(subject).to include [1, 5, 8]
|
||||
end
|
||||
|
||||
it 'finds two labels with "Urgent"' do
|
||||
expect(subject).to include [2, 7]
|
||||
end
|
||||
end
|
||||
|
||||
context 'using multiple columns for uniqueness' do
|
||||
subject do
|
||||
model.find_duplicates(:test_labels, [:label, :user_id])
|
||||
end
|
||||
|
||||
it 'finds duplicates' do
|
||||
expect(subject.length).to eq 2
|
||||
end
|
||||
|
||||
it 'finds two labels with "Important" for user 2' do
|
||||
expect(subject).to include [5, 8]
|
||||
end
|
||||
|
||||
it 'finds two labels with "Urgent" for user 1' do
|
||||
expect(subject).to include [2, 7]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.delete_duplicates' do
|
||||
subject do
|
||||
model.delete_duplicates(:test_labels, [:label])
|
||||
end
|
||||
|
||||
it 'keeps the first item, and delete the others' do
|
||||
expect { subject }.to change(TestLabel, :count).by(-3)
|
||||
expect(TestLabel.where(label: 'Critical').count).to eq(1)
|
||||
expect(TestLabel.where(label: 'Important').count).to eq(1)
|
||||
expect(TestLabel.where(label: 'Urgent').count).to eq(1)
|
||||
expect(TestLabel.where(label: 'Bug').count).to eq(1)
|
||||
expect(TestLabel.where(label: 'Done').count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.add_concurrent_index' do
|
||||
let(:model) { ActiveRecord::Migration.new.extend(Database::MigrationHelpers) }
|
||||
|
||||
context 'outside a transaction' do
|
||||
before do
|
||||
model.verbose = false
|
||||
allow(model).to receive(:transaction_open?).and_return(false)
|
||||
allow(model).to receive(:disable_statement_timeout).and_call_original
|
||||
end
|
||||
|
||||
it 'creates the index concurrently' do
|
||||
expect(model).to receive(:add_index)
|
||||
.with(:users, :foo, algorithm: :concurrently)
|
||||
|
||||
model.add_concurrent_index(:users, :foo)
|
||||
end
|
||||
|
||||
it 'creates unique index concurrently' do
|
||||
expect(model).to receive(:add_index)
|
||||
.with(:users, :foo, { algorithm: :concurrently, unique: true })
|
||||
|
||||
model.add_concurrent_index(:users, :foo, unique: true)
|
||||
end
|
||||
|
||||
it 'does nothing if the index exists already' do
|
||||
expect(model).to receive(:index_exists?)
|
||||
.with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
|
||||
expect(model).not_to receive(:add_index)
|
||||
|
||||
model.add_concurrent_index(:users, :foo, unique: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'inside a transaction' do
|
||||
it 'raises RuntimeError' do
|
||||
expect(model).to receive(:transaction_open?).and_return(true)
|
||||
|
||||
expect { model.add_concurrent_index(:users, :foo) }
|
||||
.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
spec/mailers/previews/expert_mailer_preview.rb
Normal file
11
spec/mailers/previews/expert_mailer_preview.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class AvisMailerPreview < ActionMailer::Preview
|
||||
def send_dossier_decision
|
||||
procedure = Procedure.new(libelle: 'Démarche pour faire des marches')
|
||||
dossier = Dossier.new(id: 1, procedure: procedure)
|
||||
instructeur = Instructeur.new(id: 1, user: User.new(email: 'jeanmichel.de-chauvigny@exemple.fr'))
|
||||
expert = Expert.new(id: 1, user: User.new('moussa.kanga@exemple.fr'))
|
||||
experts_procedure = ExpertsProcedure.new(expert: expert, procedure: procedure, allow_decision_access: true)
|
||||
avis = Avis.new(id: 1, email: 'test@exemple.fr', dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure)
|
||||
ExpertMailer.send_dossier_decision(avis.id)
|
||||
end
|
||||
end
|
|
@ -1422,4 +1422,16 @@ describe Dossier do
|
|||
it { expect(dossier.api_entreprise_job_exceptions).to eq(['#<StandardError: My special exception!>']) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroy" do
|
||||
let(:dossier) { create(:dossier) }
|
||||
before do
|
||||
create(:attestation, dossier: dossier)
|
||||
create(:attestation, dossier: dossier)
|
||||
end
|
||||
|
||||
it "can destroy dossier with two attestations" do
|
||||
expect(dossier.destroy).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1030,4 +1030,12 @@ describe Procedure do
|
|||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroy" do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ) }
|
||||
|
||||
it "can destroy procedure" do
|
||||
expect(procedure.destroy).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue