Skip to content

Commit

Permalink
Merge branch 'staging' into add_crisp_chatbox_for_agents
Browse files Browse the repository at this point in the history
  • Loading branch information
Holist authored Jan 22, 2025
2 parents 33de0e4 + a8f3277 commit 2d5f9aa
Show file tree
Hide file tree
Showing 35 changed files with 277 additions and 103 deletions.
30 changes: 30 additions & 0 deletions app/controllers/brevo/ip_whitelist_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "ipaddr"

module Brevo::IpWhitelistConcern
extend ActiveSupport::Concern

# IP list comes from
# https://help.brevo.com/hc/en-us/articles/15127404548498-Brevo-IP-ranges-List-of-publicly-exposed-services#h_01HENC062K8KJKJE7BJNYMPM77
IP_WHITELIST_RANGE = "1.179.112.0/20".freeze

included do
before_action :ensure_ip_comes_from_brevo_ips
end

private

def ensure_ip_comes_from_brevo_ips
# In case Brevo decides to use some other IP range without notice
# we need a quick way to skip this check
return if ENV["DISABLE_BREVO_IP_WHITELIST"].present?

return if IPAddr.new(IP_WHITELIST_RANGE).include?(request.remote_ip)

Sentry.capture_message("Brevo Webhook received with following non whitelisted IP", {
extra: {
ip: request.remote_ip
}
})
head :forbidden
end
end
1 change: 1 addition & 0 deletions app/controllers/brevo/mail_webhooks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Brevo
class MailWebhooksController < ApplicationController
include Brevo::IpWhitelistConcern
skip_before_action :authenticate_agent!, :verify_authenticity_token

PERMITTED_PARAMS = %i[
Expand Down
1 change: 1 addition & 0 deletions app/controllers/brevo/sms_webhooks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Brevo
class SmsWebhooksController < ApplicationController
include Brevo::IpWhitelistConcern
skip_before_action :authenticate_agent!, :verify_authenticity_token

PERMITTED_PARAMS = %i[
Expand Down
7 changes: 6 additions & 1 deletion app/controllers/category_configurations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class CategoryConfigurationsController < ApplicationController
only: [:index, :new, :create, :show, :edit, :update, :destroy]
before_action :set_category_configuration, :set_file_configuration, :set_template,
only: [:show, :edit, :update, :destroy]
before_action :set_department, :set_file_configurations, only: [:new, :create, :edit, :update]
before_action :set_department, :set_file_configurations, :set_authorized_motif_categories,
only: [:new, :create, :edit, :update]
before_action :set_back_to_users_list_url, :set_messages_configuration, :set_category_configurations, only: [:index]

def index
Expand Down Expand Up @@ -107,6 +108,10 @@ def set_organisation
@organisation = current_organisation
end

def set_authorized_motif_categories
@authorized_motif_categories = MotifCategoryPolicy.authorized_for_organisation(@organisation)
end

def user_count_by_tag_id
User.joins(:tags,
:organisations)
Expand Down
4 changes: 3 additions & 1 deletion app/models/category_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ class CategoryConfiguration < ApplicationRecord

validates :organisation, uniqueness: { scope: :motif_category,
message: "a déjà une category_configuration pour cette catégorie de motif" }
validate :minimum_invitation_duration, :invitation_formats_validity, :periodic_invites_can_be_activated
validate :minimum_invitation_duration,
:invitation_formats_validity,
:periodic_invites_can_be_activated

validates :email_to_notify_no_available_slots, :email_to_notify_rdv_changes,
format: {
Expand Down
2 changes: 2 additions & 0 deletions app/models/motif_category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class MotifCategory < ApplicationRecord
:name, :short_name
].freeze

RSA_RELATED_TYPES = %w[rsa_orientation rsa_accompagnement].freeze

has_many :category_configurations, dependent: :restrict_with_exception
has_many :follow_ups, dependent: :restrict_with_exception
has_many :motifs, dependent: :restrict_with_exception
Expand Down
1 change: 1 addition & 0 deletions app/models/organisation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def france_travail? = safir_code?
def with_parcours_access?
organisation_type.in?(ORGANISATION_TYPES_WITH_PARCOURS_ACCESS)
end
alias_method :rsa_related?, :with_parcours_access?

def requires_dpa_acceptance?
dpa_agreement.nil?
Expand Down
15 changes: 15 additions & 0 deletions app/policies/motif_category_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class MotifCategoryPolicy < ApplicationPolicy
def self.authorized_for_organisation(organisation)
if organisation.rsa_related?
MotifCategory.where(motif_category_type: MotifCategory::RSA_RELATED_TYPES)
else
MotifCategory.where(motif_category_type: organisation.organisation_type)
end
end

def self.authorized_for_organisation?(motif_category, organisation)
return MotifCategory::RSA_RELATED_TYPES.include?(motif_category.motif_category_type) if organisation.rsa_related?

organisation.organisation_type == motif_category.motif_category_type
end
end
12 changes: 11 additions & 1 deletion app/services/category_configurations/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@ def initialize(category_configuration:)

def call
CategoryConfiguration.transaction do
activate_motif_category_on_rdvs_territory
ensure_motif_category_is_authorized
save_record!(@category_configuration)
activate_motif_category_on_rdvs_territory
end
end

private

def ensure_motif_category_is_authorized
return if MotifCategoryPolicy.authorized_for_organisation?(
@category_configuration.motif_category,
@category_configuration.organisation
)

fail!("La catégorie de motif n'est pas autorisée pour cette organisation")
end

def activate_motif_category_on_rdvs_territory
@activate_motif_category_on_rdvs_territory ||= call_service!(
RdvSolidaritesApi::CreateMotifCategoryTerritory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ def call
return if old_update?
return if @record.delivered?

alert_sentry_if_webhook_mismatch
set_last_brevo_webhook_received_at
return unless delivery_status.in?(record_class.delivery_statuses.keys)

Expand All @@ -29,10 +28,6 @@ def record_class
@record_class ||= @record.class
end

def alert_sentry_if_webhook_mismatch
raise NoMethodError
end

def delivery_status
raise NoMethodError
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ class AssignMailDeliveryStatusAndDate < AssignDeliveryStatusAndDateBase
def delivery_status
@delivery_status ||= @webhook_params[:event]
end

def alert_sentry_if_webhook_mismatch
return if @record.email.casecmp?(@webhook_params[:email])

Sentry.capture_message("#{record_class} email and webhook email does not match",
extra: { record: @record, webhook: @webhook_params })
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ class AssignSmsDeliveryStatusAndDate < AssignDeliveryStatusAndDateBase
def delivery_status
@delivery_status ||= @webhook_params[:msg_status]
end

def alert_sentry_if_webhook_mismatch
return if @record.user.phone_number == PhoneNumberHelper.format_phone_number(@webhook_params[:to])

Sentry.capture_message("#{record_class} mobile phone and webhook mobile phone does not match",
extra: { record: @record, webhook: @webhook_params })
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
required="true"
>
<option value=""> - </option>
<% MotifCategory.all.each do |motif_category| %>
<% @authorized_motif_categories.each do |motif_category| %>
<option value="<%= motif_category.id %>"><%= motif_category.name %></option>
<% end %>
</select>
Expand Down
11 changes: 5 additions & 6 deletions app/views/website/teleprocedure_landings/_13.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<h3 class="pt-3 pb-5">Si vous résidez dans l’une des 29 communes listées ci-dessous, vous êtes concerné par une expérimentation du Conseil départemental des Bouches-du-Rhône visant à faciliter votre prise de rendez-vous RSA.</h3>
<h5>À la suite de votre demande de RSA, un email et/ou un SMS va vous être transmis par le Conseil départemental pour fixer un premier rendez-vous d’orientation. Veuillez cliquer sur le lien contenu dans ce mail/SMS pour choisir un créneau à votre convenance.</h5>
<br/>
<p><u>Communes concernées&nbsp;:</u><mark> Marseille - 2e arrondissement, Marseille - 3e arrondissement, Arles, Aureille, Barbentane, Baux-de-Provence, Boulbon, Cabannes, Chateaurenard, Eygalières, Eyragues, Fontvieille, Graveson, Maillane, Mas-Blanc-des-Alpilles, Maussane-les-Alpilles, Saint-Pierre-de-Mézoargues, Molleges, Mouries, Noves, Orgon, Paradou, Plan-d'Orgon, Rognonas, Saint-Andiol, Saint-Etienne-du-Gres, Saintes-Maries-de-la-Mer, Saint-Martin-de-Crau, Saint-Rémy-de-Provence, Tarascon, Verquieres.</mark></p>
<br/>
<p>Si vous ne résidez pas dans l’une des 29 communes listées ci-dessus, vous n’êtes pas concerné par ces modalités de prise de rendez-vous et serez convié à votre rendez-vous d’orientation par courrier postal avec accusé de réception.</p>
<p>Vous pouvez désormais choisir le jour et l’heure, les plus appropriés à votre emploi du temps, pour rencontrer le conseiller d’orientation qui définira avec vous les démarches et actions à mettre en œuvre pour faciliter votre insertion.</p>

<p>À la suite de votre demande de RSA, un email et/ou un SMS vous sera transmis par le Conseil départemental pour fixer votre premier rendez-vous d’orientation. Cliquez sur le lien contenu dans ce mail/SMS pour choisir un créneau à votre convenance.</p>

<p>Simple, rapide, facile le service « RENDEZ-VOUS INSERTION » est disponible sur tous les Pôles d’insertion des Bouches-du- Rhône est accessible en un clic.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ChangeAgentIdNullConstraintOnParcoursDocuments < ActiveRecord::Migration[7.1]
def change
change_column_null :parcours_documents, :agent_id, true
end
end
2 changes: 1 addition & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@
create_table "parcours_documents", force: :cascade do |t|
t.bigint "department_id", null: false
t.bigint "user_id", null: false
t.bigint "agent_id", null: false
t.bigint "agent_id"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand Down
2 changes: 1 addition & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@
rdv_solidarites_organisation_id: 3,
# rdv_solidarites_organisation_id: vérifier l'id de l'organisation correspondante sur RDV-Solidarites
department_id: yonne.id,
organisation_type: "siae"
organisation_type: "france_travail"
)

CategoryConfiguration.create!(
Expand Down
8 changes: 8 additions & 0 deletions spec/controllers/brevo/mail_webhooks_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
describe Brevo::MailWebhooksController do
include_context "with ip whitelist"

describe "#create" do
before do
allow(InboundWebhooks::Brevo::ProcessMailDeliveryStatusJob).to receive(:perform_later)
Expand All @@ -13,6 +15,12 @@
}
end

context "when called with non-matching IP" do
let(:params) { valid_mail_params }

include_examples "returns 403 for non-whitelisted IP", "18.12.12.12"
end

context "when X-Mailin-custom header is present and environment matches" do
it "enqueues the job for processing mail delivery status" do
expect(InboundWebhooks::Brevo::ProcessMailDeliveryStatusJob).to receive(:perform_later)
Expand Down
8 changes: 8 additions & 0 deletions spec/controllers/brevo/sms_webhooks_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
describe Brevo::SmsWebhooksController do
include_context "with ip whitelist"

describe "#create" do
before do
allow(InboundWebhooks::Brevo::ProcessSmsDeliveryStatusJob).to receive(:perform_later)
Expand All @@ -8,6 +10,12 @@
{ to: "1234567890", msg_status: "delivered", date: "2023-06-07T12:34:56Z", record_identifier: "invitation_1" }
end

context "when called with non-matching IP" do
let(:params) { valid_sms_params }

include_examples "returns 403 for non-whitelisted IP", "18.12.12.12"
end

it "enqueues the job for processing SMS delivery status" do
expect(InboundWebhooks::Brevo::ProcessSmsDeliveryStatusJob).to receive(:perform_later)
.with({ to: "1234567890", msg_status: "delivered", date: "2023-06-07T12:34:56Z" }, "invitation_1")
Expand Down
2 changes: 1 addition & 1 deletion spec/factories/motif_category.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :motif_category do
template
motif_category_type { "autre" }
motif_category_type { "rsa_orientation" }
sequence(:short_name) { |n| "rsa_orientation_#{n}" }
name { "RSA orientation" }
end
Expand Down
4 changes: 2 additions & 2 deletions spec/factories/organisation.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :organisation do
sequence(:name) { |n| "Organisation n°#{n}" }
sequence(:email) { |n| "organisation#{n}@rdv-insertion.fr" }
sequence(:name) { |n| "Organisation n°#{n + Process.pid}" }
sequence(:email) { |n| "organisation#{n + Process.pid}@rdv-insertion.fr" }
rdv_solidarites_organisation_id { rand(1..10_000_000_000) }
department
phone_number { "0101010101" }
Expand Down
8 changes: 4 additions & 4 deletions spec/factories/user.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
FactoryBot.define do
factory :user do
sequence(:uid) { |n| "uid#{n}" }
sequence(:uid) { |n| "uid#{n + Process.pid}" }
sequence(:rdv_solidarites_user_id) { |n| n + Process.pid }
sequence(:affiliation_number) { |n| "numero_#{n}" }
sequence(:affiliation_number) { |n| "numero_#{n + Process.pid}" }
department_internal_id { rand(4000..5000).to_s }
role { "demandeur" }
title { "monsieur" }
sequence(:first_name) { |n| "john#{n}" }
sequence(:last_name) { |n| "doe#{n}" }
sequence(:first_name) { |n| "john#{n + Process.pid}" }
sequence(:last_name) { |n| "doe#{n + Process.pid}" }
sequence(:email) { |n| "johndoe#{n + Process.pid}@yahoo.fr" }
address { "27 avenue de Ségur 75007 Paris" }
phone_number { "+33782605941" }
Expand Down
63 changes: 63 additions & 0 deletions spec/features/agent_can_create_category_configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
describe "Agents can edit category configuration", :js do
let!(:agent) { create(:agent) }
let!(:organisation) { create(:organisation, organisation_type: "delegataire_rsa") }
let!(:agent_role) { create(:agent_role, organisation: organisation, agent: agent, access_level: "admin") }
let!(:motif_category) do
create(
:motif_category,
name: "RSA Follow up",
short_name: "rsa_follow_up",
motif_category_type: "rsa_accompagnement"
)
end

let!(:category_configuration) { create(:category_configuration, organisation:, file_configuration:) }
let(:file_configuration) { create(:file_configuration) }

before do
stub_rdv_solidarites_activate_motif_category_territories(
organisation.rdv_solidarites_organisation_id,
motif_category.short_name
)
setup_agent_session(agent)
end

context "category configuration edit" do
it "allows to create category configuration" do
visit new_organisation_category_configuration_path(organisation)

fill_in "category_configuration_phone_number", with: "3234"
fill_in "category_configuration_email_to_notify_rdv_changes", with: "test@test.com"
fill_in "category_configuration_email_to_notify_no_available_slots", with: "test@test.com"

select "RSA Follow up", from: "category_configuration[motif_category_id]"

find("input[name=\"category_configuration[file_configuration_id]\"]").click

click_button "Enregistrer"

expect(page).to have_content("3234")
expect(page).to have_content("test@test.com")

new_category_configuration = CategoryConfiguration.last
expect(new_category_configuration.reload.phone_number).to eq("3234")
expect(new_category_configuration.email_to_notify_rdv_changes).to eq("test@test.com")
expect(new_category_configuration.email_to_notify_no_available_slots).to eq("test@test.com")
end

context "motif category selection" do
let!(:motif_category2) do
create(:motif_category, name: "Autre", short_name: "autre", motif_category_type: "autre")
end

it "allows to select authorized motif categories" do
visit new_organisation_category_configuration_path(organisation)

expect(page).to have_select(
"category_configuration[motif_category_id]", options: ["-", "RSA Follow up",
category_configuration.motif_category.name]
)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
it "allows to edit category configuration" do
visit edit_organisation_category_configuration_path(organisation, organisation.category_configurations.first)

fill_in "category_configuration_phone_number", with: "3234"
fill_in "category_configuration_phone_number", with: "3234"
fill_in "category_configuration_email_to_notify_rdv_changes", with: "test@test.com"
fill_in "category_configuration_email_to_notify_no_available_slots", with: "test@test.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
let(:participation2) { create(:participation, user: user1, follow_up: follow_up2, convocable: true) }
let!(:notification2) { create(:notification, participation: participation2, created_at: 1.day.ago) }

before do
follow_up.set_status
follow_up.save
follow_up2.set_status
follow_up2.save
end

context "with convocation date before" do
before do
setup_agent_session(agent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
stub_geo_api_request(user.address)
stub_brevo
stub_creneau_availability(true)
follow_up.set_status
follow_up.save
end

shared_examples "agent can invite user" do
Expand Down
Loading

0 comments on commit 2d5f9aa

Please sign in to comment.