From 5abba5a1662619fda88965bb6044af50c57153ad Mon Sep 17 00:00:00 2001 From: mfo Date: Thu, 5 Sep 2024 17:12:13 +0200 Subject: [PATCH 1/5] =?UTF-8?q?feat(Champs::CommuneChamp):=20ensure=20pres?= =?UTF-8?q?ence=20of=20external=5Fid=20since=20some=20of=20Champs::Commune?= =?UTF-8?q?Champ.external=5Fid=20are=20missing=20while=20.value=20is=20pre?= =?UTF-8?q?sent=20=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/champs/commune_champ.rb | 10 ++++++++++ spec/models/champs/commune_champ_spec.rb | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/models/champs/commune_champ.rb b/app/models/champs/commune_champ.rb index 67f448e4b..a1e69ba48 100644 --- a/app/models/champs/commune_champ.rb +++ b/app/models/champs/commune_champ.rb @@ -4,6 +4,9 @@ class Champs::CommuneChamp < Champs::TextChamp store_accessor :value_json, :code_departement, :code_postal, :code_region before_save :on_codes_change, if: :should_refresh_after_code_change? + validates :external_id, presence: true, if: -> { validate_champ_value_or_prefill? && value.present? } + after_validation :instrument_external_id_error, if: -> { errors.include?(:external_id) } + def departement_name APIGeoService.departement_name(code_departement) end @@ -101,4 +104,11 @@ class Champs::CommuneChamp < Champs::TextChamp def should_refresh_after_code_change? !departement? || code_postal_changed? || external_id_changed? end + + def instrument_external_id_error + Sentry.capture_message( + "Commune with value and no external id Edge case reached", + extra: { request_id: Current.request_id } + ) + end end diff --git a/spec/models/champs/commune_champ_spec.rb b/spec/models/champs/commune_champ_spec.rb index 5448aa048..97e5e8569 100644 --- a/spec/models/champs/commune_champ_spec.rb +++ b/spec/models/champs/commune_champ_spec.rb @@ -28,6 +28,22 @@ describe Champs::CommuneChamp do expect(champ.for_export(:departement)).to eq '63 – Puy-de-Dôme' end + context 'with tricky bug (should not happen, but it happens)' do + let(:champ) do + described_class.new(stable_id: 99, dossier:).tap do |champ| + champ.external_id = '' + champ.value = 'Gagny' + champ.run_callbacks(:save) + end + end + + it 'fails' do + expect(champ).to receive(:instrument_external_id_error) + expect(champ.validate(:champs_public_value)).to be_falsey + expect(champ.errors).to include('external_id') + end + end + context 'with code' do let(:champ) do described_class.new(stable_id: 99, dossier:).tap do |champ| From ace2f4382fc5c83958f1981f4a13e7bed5c2e01d Mon Sep 17 00:00:00 2001 From: mfo Date: Thu, 5 Sep 2024 17:50:32 +0200 Subject: [PATCH 2/5] tech(refactor): DataSources::CommuneController, move request methods to fetch and format commune searched by postal_code or name --- .../data_sources/commune_controller.rb | 70 +------------------ app/services/api_geo_service.rb | 64 +++++++++++++++++ 2 files changed, 66 insertions(+), 68 deletions(-) diff --git a/app/controllers/data_sources/commune_controller.rb b/app/controllers/data_sources/commune_controller.rb index 0962bd9a0..4ceab5492 100644 --- a/app/controllers/data_sources/commune_controller.rb +++ b/app/controllers/data_sources/commune_controller.rb @@ -3,12 +3,12 @@ class DataSources::CommuneController < ApplicationController def search if params[:q].present? && params[:q].length > 1 - response = fetch_results + response = APIGeoService.commune_by_name_or_postal_code(params[:q]) if response.success? results = JSON.parse(response.body, symbolize_names: true) - render json: format_results(results) + render json: APIGeoService.format_commune_response(results, params[:with_combined_code]) else render json: [] end @@ -16,70 +16,4 @@ class DataSources::CommuneController < ApplicationController render json: [] end end - - private - - def fetch_results - if postal_code?(params[:q]) - fetch_by_postal_code(params[:q]) - else - fetch_by_name(params[:q]) - end - end - - def fetch_by_name(name) - Typhoeus.get("#{API_GEO_URL}/communes", params: { - type: 'commune-actuelle,arrondissement-municipal', - nom: name, - boost: 'population', - limit: 100 - }, timeout: 3) - end - - def fetch_by_postal_code(postal_code) - Typhoeus.get("#{API_GEO_URL}/communes", params: { - type: 'commune-actuelle,arrondissement-municipal', - codePostal: postal_code, - boost: 'population', - limit: 50 - }, timeout: 3) - end - - def postal_code?(string) - string.match?(/\A[-+]?\d+\z/) ? true : false - end - - def format_results(results) - results.reject(&method(:code_metropole?)).flat_map do |result| - item = { - name: result[:nom].tr("'", '’'), - code: result[:code], - epci_code: result[:codeEpci], - departement_code: result[:codeDepartement] - }.compact - - if result[:codesPostaux].present? - result[:codesPostaux].map { item.merge(postal_code: _1) } - else - [item] - end.map do |item| - if params[:with_combined_code].present? - { - label: "#{item[:name]} (#{item[:postal_code]})", - value: "#{item[:code]}-#{item[:postal_code]}" - } - else - { - label: "#{item[:name]} (#{item[:postal_code]})", - value: item[:code], - data: item[:postal_code] - } - end - end - end - end - - def code_metropole?(result) - result[:code].in?(['75056', '13055', '69123']) - end end diff --git a/app/services/api_geo_service.rb b/app/services/api_geo_service.rb index e7bf052a1..5a8e0bcc1 100644 --- a/app/services/api_geo_service.rb +++ b/app/services/api_geo_service.rb @@ -84,6 +84,14 @@ class APIGeoService communes(departement_code).find { _1[:code] == code }&.dig(:name) end + def commune_by_name_or_postal_code(query) + if postal_code?(query) + fetch_by_postal_code(query) + else + fetch_by_name(query) + end + end + def commune_code(departement_code, name) communes(departement_code).find { _1[:name] == name }&.dig(:code) end @@ -215,8 +223,42 @@ class APIGeoService commune_name(department_code, city_code) || fallback end + def format_commune_response(results, with_combined_code) + results.reject(&method(:code_metropole?)).flat_map do |result| + item = { + name: result[:nom].tr("'", '’'), + code: result[:code], + epci_code: result[:codeEpci], + departement_code: result[:codeDepartement] + }.compact + + if result[:codesPostaux].present? + result[:codesPostaux].map { item.merge(postal_code: _1) } + else + [item] + end.map do |item| + if with_combined_code.present? + { + label: "#{item[:name]} (#{item[:postal_code]})", + value: "#{item[:code]}-#{item[:postal_code]}" + } + else + { + label: "#{item[:name]} (#{item[:postal_code]})", + value: item[:code], + data: item[:postal_code] + } + end + end + end + end + private + def code_metropole?(result) + result[:code].in?(['75056', '13055', '69123']) + end + def communes_by_postal_code_map Rails.cache.fetch('api_geo_communes', expires_in: 1.day, version: 3) do departements @@ -251,6 +293,28 @@ class APIGeoService private + def fetch_by_name(name) + Typhoeus.get("#{API_GEO_URL}/communes", params: { + type: 'commune-actuelle,arrondissement-municipal', + nom: name, + boost: 'population', + limit: 100 + }, timeout: 3) + end + + def fetch_by_postal_code(postal_code) + Typhoeus.get("#{API_GEO_URL}/communes", params: { + type: 'commune-actuelle,arrondissement-municipal', + codePostal: postal_code, + boost: 'population', + limit: 50 + }, timeout: 3) + end + + def postal_code?(string) + string.match?(/\A[-+]?\d+\z/) ? true : false + end + def ban_address_schema JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json')) end From 7a97ef656b3ce43ec78c091ae05542e0ac764a6b Mon Sep 17 00:00:00 2001 From: mfo Date: Fri, 6 Sep 2024 06:12:22 +0200 Subject: [PATCH 3/5] env(add): add ENV["MAINTENANCE_INSTRUCTEUR_EMAIL"] which will be used by our maintenance task to fix data, this email will be visible to users, in audit log etc... --- config/env.example.optional | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/env.example.optional b/config/env.example.optional index ffad0f1cd..82e95ce84 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -274,3 +274,8 @@ CRON_JOBS_DISABLED="" # optional license key for lightgallery VITE_LIGHTGALLERY_LICENSE_KEY = "" + +# Email used to find the Instructeur who fixes data on production. +# This email will be visible to users whom dossier had been fixed by our maintenance_tasks +# By default we use CONTACT_EMAIL, but you can customize it +MAINTENANCE_INSTRUCTEUR_EMAIL="" From 99b31b0cc8f178ca26c747163a5a18796caa3ddf Mon Sep 17 00:00:00 2001 From: mfo Date: Fri, 6 Sep 2024 06:42:49 +0200 Subject: [PATCH 4/5] db(seed): create user_fixer when seeding db, owner of instance will have to reset password --- db/seeds.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/db/seeds.rb b/db/seeds.rb index e34c1fd33..3d828de0e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -20,3 +20,9 @@ user = User.create!( ) user.create_instructeur! user.create_administrateur! + +user_fixer User.create(email: ENV.fetch('DEFAULT_INSTRUCTEUR_EMAIL') { CONTACT_EMAIL }, + password: Random.srand, + confirmed_at: Time.zone.now, + email_verified_at: Time.zone.now) +user_fixer.create_instructeur! From 657fb0ebf435891c29f48a6624f1364fc9df3cb9 Mon Sep 17 00:00:00 2001 From: mfo Date: Thu, 5 Sep 2024 18:43:45 +0200 Subject: [PATCH 5/5] feat(Maintenance.communes): backfill missing external_id for communes champs in error --- ...e_having_value_but_not_external_id_task.rb | 69 +++++++++++++++++++ ...mps-commune-with-more-than-one-results.yml | 41 +++++++++++ .../fix-champs-commune-with-one-results.yml | 41 +++++++++++ ...ing_value_but_not_external_id_task_spec.rb | 51 ++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 app/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task.rb create mode 100644 spec/fixtures/cassettes/fix-champs-commune-with-more-than-one-results.yml create mode 100644 spec/fixtures/cassettes/fix-champs-commune-with-one-results.yml create mode 100644 spec/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task_spec.rb diff --git a/app/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task.rb b/app/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task.rb new file mode 100644 index 000000000..da6fe8dd4 --- /dev/null +++ b/app/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Maintenance + # some of our Champs::CommuneChamp had been corrupted, ie: missing external_id + # this tasks fix this issue + class FixChampsCommuneHavingValueButNotExternalIdTask < MaintenanceTasks::Task + DEFAULT_INSTRUCTEUR_EMAIL = ENV.fetch('DEFAULT_INSTRUCTEUR_EMAIL') { CONTACT_EMAIL } + + def collection + Champs::CommuneChamp.where.not(value: nil) + end + + def process(champ) + return if !fixable?(champ) + + response = APIGeoService.commune_by_name_or_postal_code(champ.value) + if !response.success? + notify("Strange case of existing commune not requestable", champ) + else + results = JSON.parse(response.body, symbolize_names: true) + formated_results = APIGeoService.format_commune_response(results, true) + case formated_results.size + when 1 + champ.code = formated_results.first[:value] + champ.save! + else # otherwise, we can't find the expected departement + champ.code_departement = nil + champ.code_postal = nil + champ.external_id = nil + champ.value = nil + champ.save(validate: false) + + ask_user_correction(champ) + end + end + end + + def count + # 2.4M champs, count is not an option + end + + private + + def ask_user_correction(champ) + dossier = champ.dossier + + commentaire = CommentaireService.build(current_instructeur, dossier, { body: "Suite à un problème technique, Veuillez re-remplir le champs : #{champ.libelle}" }) + dossier.flag_as_pending_correction!(commentaire, :incomplete) + end + + def current_instructeur + user = User.find_by(email: DEFAULT_INSTRUCTEUR_EMAIL) + user ||= User.create(email: DEFAULT_INSTRUCTEUR_EMAIL, + password: Random.srand, + confirmed_at: Time.zone.now, + email_verified_at: Time.zone.now) + instructeur = user.instructeur + instructeur ||= user.create_instructeur! + + instructeur + end + + def fixable?(champ) + champ.value.present? && [champ.dossier.en_instruction? || champ.dossier.en_construction?] + end + + def notify(message, champ) = Sentry.capture_message(message, extra: { champ: }) + end +end diff --git a/spec/fixtures/cassettes/fix-champs-commune-with-more-than-one-results.yml b/spec/fixtures/cassettes/fix-champs-commune-with-more-than-one-results.yml new file mode 100644 index 000000000..4afc35529 --- /dev/null +++ b/spec/fixtures/cassettes/fix-champs-commune-with-more-than-one-results.yml @@ -0,0 +1,41 @@ +--- +http_interactions: +- request: + method: get + uri: https://geo.api.gouv.fr/communes?boost=population&limit=100&nom=Marseille&type=commune-actuelle,arrondissement-municipal + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - demarches-simplifiees.fr + Expect: + - '' + response: + status: + code: 200 + message: '' + headers: + Server: + - nginx/1.10.3 (Ubuntu) + Date: + - Fri, 06 Sep 2024 07:25:27 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '3583' + Vary: + - Accept-Encoding + - Origin + X-Powered-By: + - Express + Etag: + - W/"dff-vG1Dx3YDZB27TTb5VLsh9AEwHbw" + Strict-Transport-Security: + - max-age=15552000 + body: + encoding: ASCII-8BIT + string: !binary |- + W3sibm9tIjoiTWFyc2VpbGxlIiwiY29kZSI6IjEzMDU1IiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJzaXJlbiI6IjIxMTMwMDU1MyIsImNvZGVFcGNpIjoiMjAwMDU0ODA3IiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDAxIiwiMTMwMDIiLCIxMzAwMyIsIjEzMDA0IiwiMTMwMDUiLCIxMzAwNiIsIjEzMDA3IiwiMTMwMDgiLCIxMzAwOSIsIjEzMDEwIiwiMTMwMTEiLCIxMzAxMiIsIjEzMDEzIiwiMTMwMTQiLCIxMzAxNSIsIjEzMDE2Il0sInBvcHVsYXRpb24iOjg3MzA3NiwiX3Njb3JlIjo0Ljg4ODM3MDE4MzkyOTEwMn0seyJub20iOiJNYXJzZWlsbGV0dGUiLCJjb2RlIjoiMTEyMjAiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMSIsInNpcmVuIjoiMjExMTAyMjA3IiwiY29kZUVwY2kiOiIyMDAwMzU3MTUiLCJjb2RlUmVnaW9uIjoiNzYiLCJjb2Rlc1Bvc3RhdXgiOlsiMTE4MDAiXSwicG9wdWxhdGlvbiI6NzAwLCJfc2NvcmUiOjAuNjE1Njg0Njg2ODA1MzMxN30seyJub20iOiJNYXJzZWlsbGUgMTNlIEFycm9uZGlzc2VtZW50IiwiY29kZSI6IjEzMjEzIiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJjb2RlUmVnaW9uIjoiOTMiLCJjb2Rlc1Bvc3RhdXgiOlsiMTMwMTMiXSwicG9wdWxhdGlvbiI6OTIyNjEsIl9zY29yZSI6MC41OTc4NjI1OTk0MDkxODg0fSx7Im5vbSI6Ik1hcnNlaWxsZSA4ZSBBcnJvbmRpc3NlbWVudCIsImNvZGUiOiIxMzIwOCIsImNvZGVEZXBhcnRlbWVudCI6IjEzIiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDA4Il0sInBvcHVsYXRpb24iOjgyNjA5LCJfc2NvcmUiOjAuNTY3ODQ4MzQ4OTM5Nzg3NX0seyJub20iOiJNYXJzZWlsbGUgMTVlIEFycm9uZGlzc2VtZW50IiwiY29kZSI6IjEzMjE1IiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJjb2RlUmVnaW9uIjoiOTMiLCJjb2Rlc1Bvc3RhdXgiOlsiMTMwMTUiXSwicG9wdWxhdGlvbiI6Nzk2NTYsIl9zY29yZSI6MC41NTg2NjU1ODA0MzIxMDYxfSx7Im5vbSI6Ik1hcnNlaWxsZSA5ZSBBcnJvbmRpc3NlbWVudCIsImNvZGUiOiIxMzIwOSIsImNvZGVEZXBhcnRlbWVudCI6IjEzIiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDA5Il0sInBvcHVsYXRpb24iOjc3MTA2LCJfc2NvcmUiOjAuNTUwNzM1OTk3MDYxMDk3OH0seyJub20iOiJNYXJzZWlsbGUgMTJlIEFycm9uZGlzc2VtZW50IiwiY29kZSI6IjEzMjEyIiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJjb2RlUmVnaW9uIjoiOTMiLCJjb2Rlc1Bvc3RhdXgiOlsiMTMwMTIiXSwicG9wdWxhdGlvbiI6NjMxMDgsIl9zY29yZSI6MC41MDcyMDcyNDg4MTUwNjg1fSx7Im5vbSI6Ik1hcnNlaWxsZSAxNGUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMTQiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAxNCJdLCJwb3B1bGF0aW9uIjo1OTk0OCwiX3Njb3JlIjowLjQ5NzM4MDc4NDcxNjA5MzU0fSx7Im5vbSI6Ik1hcnNlaWxsZSAxMGUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMTAiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAxMCJdLCJwb3B1bGF0aW9uIjo1OTAwMiwiX3Njb3JlIjowLjQ5NDQzOTA2NDc2NzQ3NjM0fSx7Im5vbSI6Ik1hcnNlaWxsZSAxMWUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMTEiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAxMSJdLCJwb3B1bGF0aW9uIjo1NzkyNCwiX3Njb3JlIjowLjQ5MTA4Njg3MjI2Nzg4OTMzfSx7Im5vbSI6Ik1hcnNlaWxsZSAzZSBBcnJvbmRpc3NlbWVudCIsImNvZGUiOiIxMzIwMyIsImNvZGVEZXBhcnRlbWVudCI6IjEzIiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDAzIl0sInBvcHVsYXRpb24iOjUzMTE1LCJfc2NvcmUiOjAuNDc2MTMyNjEwOTIyMzI4OTV9LHsibm9tIjoiTWFyc2VpbGxlcy1sw6hzLUF1YmlnbnkiLCJjb2RlIjoiMTgxMzkiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxOCIsInNpcmVuIjoiMjExODAxMzk0IiwiY29kZUVwY2kiOiIyMDAwMTE3ODEiLCJjb2RlUmVnaW9uIjoiMjQiLCJjb2Rlc1Bvc3RhdXgiOlsiMTgzMjAiXSwicG9wdWxhdGlvbiI6NjQ5LCJfc2NvcmUiOjAuNDcyMzE5NTc0NzcwNjA2OH0seyJub20iOiJNYXJzZWlsbGUgNGUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMDQiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAwNCJdLCJwb3B1bGF0aW9uIjo1MDA2OCwiX3Njb3JlIjowLjQ2NjY1NzUzNjIwNDEwODR9LHsibm9tIjoiTWFyc2VpbGxlIDVlIEFycm9uZGlzc2VtZW50IiwiY29kZSI6IjEzMjA1IiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJjb2RlUmVnaW9uIjoiOTMiLCJjb2Rlc1Bvc3RhdXgiOlsiMTMwMDUiXSwicG9wdWxhdGlvbiI6NDU0NDksIl9zY29yZSI6MC40NTIyOTQxMDY1NjA3MDE1N30seyJub20iOiJNYXJzZWlsbGUgNmUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMDYiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAwNiJdLCJwb3B1bGF0aW9uIjozOTY0NywiX3Njb3JlIjowLjQzNDI1MTk3MjE2MTI1NDMzfSx7Im5vbSI6Ik1hcnNlaWxsZSA3ZSBBcnJvbmRpc3NlbWVudCIsImNvZGUiOiIxMzIwNyIsImNvZGVEZXBhcnRlbWVudCI6IjEzIiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDA3Il0sInBvcHVsYXRpb24iOjM0NjMzLCJfc2NvcmUiOjAuNDE4NjYwMjM0NTA1NDc1N30seyJub20iOiJNYXJzZWlsbGUgMWVyIEFycm9uZGlzc2VtZW50IiwiY29kZSI6IjEzMjAxIiwiY29kZURlcGFydGVtZW50IjoiMTMiLCJjb2RlUmVnaW9uIjoiOTMiLCJjb2Rlc1Bvc3RhdXgiOlsiMTMwMDEiXSwicG9wdWxhdGlvbiI6Mzk0MzYsIl9zY29yZSI6MC4zODc5OTk5NzIyNDQ0ODU1N30seyJub20iOiJNYXJzZWlsbGUgMmUgQXJyb25kaXNzZW1lbnQiLCJjb2RlIjoiMTMyMDIiLCJjb2RlRGVwYXJ0ZW1lbnQiOiIxMyIsImNvZGVSZWdpb24iOiI5MyIsImNvZGVzUG9zdGF1eCI6WyIxMzAwMiJdLCJwb3B1bGF0aW9uIjoyMzYyNywiX3Njb3JlIjowLjM4NDQzNTUzMDc0ODA5NjJ9LHsibm9tIjoiTWFyc2VpbGxlIDE2ZSBBcnJvbmRpc3NlbWVudCIsImNvZGUiOiIxMzIxNiIsImNvZGVEZXBhcnRlbWVudCI6IjEzIiwiY29kZVJlZ2lvbiI6IjkzIiwiY29kZXNQb3N0YXV4IjpbIjEzMDE2Il0sInBvcHVsYXRpb24iOjE1NDg3LCJfc2NvcmUiOjAuMzU5MTIzMDU2NzcxNjIyNjV9LHsibm9tIjoiTWFyc2VpbGxlLWVuLUJlYXV2YWlzaXMiLCJjb2RlIjoiNjAzODciLCJjb2RlRGVwYXJ0ZW1lbnQiOiI2MCIsInNpcmVuIjoiMjE2MDAzODMwIiwiY29kZUVwY2kiOiIyNDYwMDA4NDgiLCJjb2RlUmVnaW9uIjoiMzIiLCJjb2Rlc1Bvc3RhdXgiOlsiNjA2OTAiXSwicG9wdWxhdGlvbiI6MTQ0MywiX3Njb3JlIjowLjMwMTMyNjM4Mzc4MTY5NjZ9XQ== + recorded_at: Fri, 06 Sep 2024 07:25:27 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/cassettes/fix-champs-commune-with-one-results.yml b/spec/fixtures/cassettes/fix-champs-commune-with-one-results.yml new file mode 100644 index 000000000..de9060b61 --- /dev/null +++ b/spec/fixtures/cassettes/fix-champs-commune-with-one-results.yml @@ -0,0 +1,41 @@ +--- +http_interactions: +- request: + method: get + uri: https://geo.api.gouv.fr/communes?boost=population&limit=100&nom=Coye-la-For%C3%AAt&type=commune-actuelle,arrondissement-municipal + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - demarches-simplifiees.fr + Expect: + - '' + response: + status: + code: 200 + message: '' + headers: + Server: + - nginx/1.10.3 (Ubuntu) + Date: + - Fri, 06 Sep 2024 07:25:28 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '197' + Vary: + - Accept-Encoding + - Origin + X-Powered-By: + - Express + Etag: + - W/"c5-7Mng3tlbwfBH/m/FGIzzGMvfw/8" + Strict-Transport-Security: + - max-age=15552000 + body: + encoding: ASCII-8BIT + string: !binary |- + W3sibm9tIjoiQ295ZS1sYS1Gb3LDqnQiLCJjb2RlIjoiNjAxNzIiLCJjb2RlRGVwYXJ0ZW1lbnQiOiI2MCIsInNpcmVuIjoiMjE2MDAxNzE5IiwiY29kZUVwY2kiOiIyNDYwMDA3NjQiLCJjb2RlUmVnaW9uIjoiMzIiLCJjb2Rlc1Bvc3RhdXgiOlsiNjA1ODAiXSwicG9wdWxhdGlvbiI6Mzk1MCwiX3Njb3JlIjowLjA3MTQ1MzgwNDc4MTQ5OTQ2fV0= + recorded_at: Fri, 06 Sep 2024 07:25:28 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task_spec.rb b/spec/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task_spec.rb new file mode 100644 index 000000000..8317d9ffe --- /dev/null +++ b/spec/tasks/maintenance/fix_champs_commune_having_value_but_not_external_id_task_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "rails_helper" + +module Maintenance + RSpec.describe FixChampsCommuneHavingValueButNotExternalIdTask do + describe "#process" do + let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :communes }]) } + let(:dossier) { create(:dossier, state, :with_populated_champs, procedure:) } + let(:champ) { dossier.champs.first } + subject(:process) { described_class.process(champ) } + + context 'when search find one result', vcr: { cassette_name: 'fix-champs-commune-with-one-results' } do + let(:state) { [:en_instruction, :en_construction].sample } + let!(:expected_external_id) { champ.external_id } + before { champ.update_column(:external_id, nil) } + + it "backfill external_id" do + expect { subject }.to change { champ.reload.external_id }.from(nil).to(expected_external_id) + end + end + + context 'when search find 0 or more than 1 results', vcr: { cassette_name: 'fix-champs-commune-with-more-than-one-results' } do + let(:instructeur) { create(:instructeur, user: create(:user, email: Maintenance::FixChampsCommuneHavingValueButNotExternalIdTask::DEFAULT_INSTRUCTEUR_EMAIL)) } + before do + instructeur + champ.update_columns(external_id: nil, value: 'Marseille') # more than one marseille in france + end + + context 'en_instruction (go back to en_construction!), send comment' do + let(:state) { [:en_instruction, :en_construction].sample } + + it 'flags as pending correction' do + expect { subject }.to change { champ.reload.value }.from('Marseille').to(nil) + expect(Commentaire.first.instructeur).to eq(instructeur) + expect(champ.dossier.state).to eq("en_construction") + end + end + + context 'when champs will passthru validator (ie: state is brouillon)' \ + 'or champ belongs to dossier.termine (ie: state is accepte, refuse or classer_sans_suite)' do + let(:state) { [:brouillon, :accepte, :refuse, :sans_suite].sample } + + it "skips backfill as well as asks for correction" do + expect { subject }.not_to change { champ.reload } + end + end + end + end + end +end