diff --git a/.rubocop.yml b/.rubocop.yml index 41ffc3e03..fad8c07b2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -93,6 +93,10 @@ Metrics/MethodLength: RSpec/NestedGroups: Enabled: false +RSpec/SpecFilePathFormat: + Exclude: + - "spec/**/*hubee*" + Naming/VariableNumber: Enabled: false diff --git a/Gemfile b/Gemfile index ed4443b5d..6f1563b98 100644 --- a/Gemfile +++ b/Gemfile @@ -77,6 +77,11 @@ gem 'ransack' gem 'wicked' gem 'rest-client' +gem 'faraday' +gem 'faraday-gzip' +gem 'faraday-net_http' +gem 'faraday-retry' +gem 'faraday-encoding' group :development, :test do gem 'awesome_print' @@ -101,10 +106,10 @@ group :development do # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md gem 'rack-mini-profiler', '~> 3.3' gem 'rubocop', require: false - gem 'rubocop-rails' - gem 'rubocop-rspec' gem 'rubocop-capybara' gem 'rubocop-factory_bot' + gem 'rubocop-rails' + gem 'rubocop-rspec' gem 'rubocop-rspec_rails' gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index 35d11c50d..1f7a944d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,11 +153,18 @@ GEM railties (>= 5.0.0) faraday (2.9.0) faraday-net_http (>= 2.0, < 3.2) + faraday-encoding (0.0.6) + faraday + faraday-gzip (2.0.1) + faraday (>= 1.0) + zlib (~> 3.0) faraday-net_http (3.1.0) net-http faraday-net_http_persistent (2.1.0) faraday (~> 2.5) net-http-persistent (~> 4.0) + faraday-retry (2.2.1) + faraday (~> 2.0) ferrum (0.15) addressable (~> 2.5) concurrent-ruby (~> 1.1) @@ -560,6 +567,7 @@ GEM nokogiri (~> 1.8) yajl-ruby (1.4.3) zeitwerk (2.7.0) + zlib (3.1.1) PLATFORMS aarch64-linux @@ -583,6 +591,11 @@ DEPENDENCIES cuprite draper factory_bot_rails + faraday + faraday-encoding + faraday-gzip + faraday-net_http + faraday-retry gaffe good_job (~> 3.99) guard-rspec diff --git a/app/clients/abstract_hubee_api_client.rb b/app/clients/abstract_hubee_api_client.rb new file mode 100644 index 000000000..9081c04aa --- /dev/null +++ b/app/clients/abstract_hubee_api_client.rb @@ -0,0 +1,23 @@ +require 'faraday' + +class AbstractHubEEAPIClient + protected + + def http_connection(&block) + Faraday.new do |conn| + conn.request :retry, max: 5 + conn.response :raise_error + conn.response :json + conn.options.timeout = 2 + yield(conn) if block + end + end + + def consumer_key + Rails.application.credentials.hubee_consumer_key + end + + def consumer_secret + Rails.application.credentials.hubee_consumer_secret + end +end diff --git a/app/clients/abstract_insee_api_client.rb b/app/clients/abstract_insee_api_client.rb new file mode 100644 index 000000000..5b836e6fa --- /dev/null +++ b/app/clients/abstract_insee_api_client.rb @@ -0,0 +1,23 @@ +require 'faraday' + +class AbstractINSEEAPIClient + protected + + def http_connection(&block) + @http_connection ||= Faraday.new do |conn| + conn.request :retry, max: 5 + conn.response :raise_error + conn.response :json + conn.options.timeout = 2 + yield(conn) if block + end + end + + def consumer_key + Rails.application.credentials.insee_consumer_key + end + + def consumer_secret + Rails.application.credentials.insee_consumer_secret + end +end diff --git a/app/clients/formulaire_qf_api_client.rb b/app/clients/formulaire_qf_api_client.rb new file mode 100644 index 000000000..9d3582175 --- /dev/null +++ b/app/clients/formulaire_qf_api_client.rb @@ -0,0 +1,35 @@ +class FormulaireQFAPIClient + def create_collectivity(authorization_request) + organization = authorization_request.organization + editor_name = authorization_request.extra_infos.dig('service_provider', 'id') + + params = { + siret: organization.siret, + code_cog: organization.code_commune_etablissement, + departement: organization.code_postal_etablissement[0..1], + name: organization.denomination, + status: 'active', + editor: editor_name + } + + http_connection.post("#{host}/api/collectivites", params.to_json) + end + + private + + def host + Rails.application.credentials.formulaire_qf.host + end + + def http_connection(&block) + Faraday.new do |conn| + conn.headers['Content-Type'] = 'application/json' + conn.request :authorization, 'Bearer', -> { secret } + yield(conn) if block + end + end + + def secret + Rails.application.credentials.formulaire_qf.secret + end +end diff --git a/app/clients/hubee_api_authentication.rb b/app/clients/hubee_api_authentication.rb new file mode 100644 index 000000000..76db47095 --- /dev/null +++ b/app/clients/hubee_api_authentication.rb @@ -0,0 +1,28 @@ +class HubEEAPIAuthentication < AbstractHubEEAPIClient + def access_token + http_connection.post( + auth_url, + 'grant_type=client_credentials&scope=ADMIN', + { + 'Authorization' => "Basic #{encoded_client_id_and_secret}" + } + ).body['access_token'] + end + + private + + def auth_url + Rails.application.credentials.hubee_auth_url + end + + def encoded_client_id_and_secret + Base64.strict_encode64("#{consumer_key}:#{consumer_secret}") + end + + def http_connection(&block) + @http_connection ||= super do |conn| + conn.response :json + yield(conn) if block + end + end +end diff --git a/app/clients/hubee_api_client.rb b/app/clients/hubee_api_client.rb new file mode 100644 index 000000000..c47610d84 --- /dev/null +++ b/app/clients/hubee_api_client.rb @@ -0,0 +1,110 @@ +class HubEEAPIClient < AbstractHubEEAPIClient + class NotFound < StandardError; end + class AlreadyExists < StandardError; end + + def find_organization(organization) + http_connection.get("#{host}/referential/v1/organizations/SI-#{organization.siret}-#{organization.code_commune_etablissement}").body + rescue Faraday::ResourceNotFound + raise NotFound + end + + def create_organization(organization, email) + http_connection.post( + "#{host}/referential/v1/organizations", + { + type: 'SI', + companyRegister: organization.siret, + branchCode: organization.code_commune_etablissement, + email:, + name: organization.denomination, + country: 'France', + postalCode: organization.code_postal_etablissement, + territory: organization.code_commune_etablissement, + status: 'Actif' + }.to_json, + 'Content-Type' => 'application/json' + ) + rescue Faraday::BadRequestError => e + raise AlreadyExists if already_exists_error?(e) + + raise + end + + def create_subscription(authorization_request, organization_payload, process_code) # rubocop:disable Metrics/AbcSize + http_connection.post( + "#{host}/referential/v1/subscriptions", + { + datapassId: authorization_request.external_id.to_i, + notificationFrequency: 'Aucune', + processCode: process_code, + subscriber: { + type: 'SI', + companyRegister: organization_payload['companyRegister'], + branchCode: organization_payload['branchCode'] + }, + email: authorization_request.demandeur.email, + status: 'Inactif', + localAdministrator: { + email: authorization_request.demandeur.email + }, + validateDateTime: DateTime.now.iso8601, + updateDateTime: DateTime.now.iso8601 + }.to_json, + 'Content-Type' => 'application/json' + ) + rescue Faraday::BadRequestError => e + raise AlreadyExists if already_exists_error?(e) + + raise + end + + def find_subscription(_authorization_request, organization_payload, process_code) + request = http_connection { |conn| conn.request :gzip }.get( + "#{host}/referential/v1/subscriptions", + companyRegister: organization_payload['companyRegister'], + processCode: process_code + ) + request.body.first + end + + def update_subscription(authorization_request, subscription_payload, editor_payload = {}) # rubocop:disable Metrics/AbcSize + subscription_id = authorization_request.extra_infos['hubee_subscription_id'] + return if subscription_id.blank? + + payload = subscription_payload.with_indifferent_access.merge({ + status: 'Actif', + activateDateTime: DateTime.now.iso8601, + accessMode: 'API', + notificationFrequency: 'Aucune' + }.with_indifferent_access) + + payload.delete('id') + payload.delete('creationDateTime') + payload.merge!(editor_payload.with_indifferent_access) if editor_payload.present? + + http_connection.put( + "#{host}/referential/v1/subscriptions/#{subscription_id}", + payload.to_json, + 'Content-Type' => 'application/json' + ) + end + + protected + + def host + Rails.application.credentials.hubee_api_url + end + + def already_exists_error?(faraday_error) + faraday_error.response[:body]['errors'].any? do |error| + error['message'].include?('already exists') + end + end + + def http_connection(&block) + super do |conn| + conn.request :authorization, 'Bearer', -> { HubEEAPIAuthentication.new.access_token } + yield(conn) if block + end + end +end diff --git a/app/clients/insee_api_authentication.rb b/app/clients/insee_api_authentication.rb new file mode 100644 index 000000000..85154071c --- /dev/null +++ b/app/clients/insee_api_authentication.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class INSEEAPIAuthentication < AbstractINSEEAPIClient + def access_token + http_connection.post( + 'https://api.insee.fr/token', + 'grant_type=client_credentials', + { + 'Authorization' => "Basic #{encoded_client_id_and_secret}" + } + ).body['access_token'] + end + + private + + def encoded_client_id_and_secret + Base64.strict_encode64("#{consumer_key}:#{consumer_secret}") + end +end diff --git a/app/clients/insee_sirene_api_client.rb b/app/clients/insee_sirene_api_client.rb new file mode 100644 index 000000000..488ca977f --- /dev/null +++ b/app/clients/insee_sirene_api_client.rb @@ -0,0 +1,15 @@ +class INSEESireneAPIClient < AbstractINSEEAPIClient + def etablissement(siret:) + http_connection.get( + "https://api.insee.fr/entreprises/sirene/V3.11/siret/#{siret}" + ).body + end + + protected + + def http_connection + super do |conn| + conn.request :authorization, 'Bearer', -> { INSEEAPIAuthentication.new.access_token } + end + end +end diff --git a/app/interactors/datapass_webhook/adapt_v2_to_v1.rb b/app/interactors/datapass_webhook/adapt_v2_to_v1.rb index 7a4dc7f91..3130b36e8 100644 --- a/app/interactors/datapass_webhook/adapt_v2_to_v1.rb +++ b/app/interactors/datapass_webhook/adapt_v2_to_v1.rb @@ -1,6 +1,7 @@ class DatapassWebhook::AdaptV2ToV1 < ApplicationInteractor def call context.event = context.event.sub('authorization_request', 'enrollment') + context.authorization_request_data = generic_data.dup context.data = build_data end @@ -21,7 +22,8 @@ def build_data 'previous_enrollment_id' => nil, 'scopes' => generic_data['scopes'].index_with { |_scope| true }, 'team_members' => build_team_members, - 'events' => [] + 'events' => [], + 'service_provider' => context.data['service_provider'] } } end diff --git a/app/interactors/datapass_webhook/create_or_prolong_token.rb b/app/interactors/datapass_webhook/create_or_prolong_token.rb index cf67e2202..d1c7530cc 100644 --- a/app/interactors/datapass_webhook/create_or_prolong_token.rb +++ b/app/interactors/datapass_webhook/create_or_prolong_token.rb @@ -1,6 +1,11 @@ class DatapassWebhook::CreateOrProlongToken < ApplicationInteractor + before do + context.modalities ||= %w[params] + end + def call return if %w[approve validate].exclude?(context.event) + return if context.modalities.exclude?('params') token = create_or_prolong_token diff --git a/app/interactors/datapass_webhook/find_or_create_authorization_request.rb b/app/interactors/datapass_webhook/find_or_create_authorization_request.rb index a16dcfb1d..2852598fa 100644 --- a/app/interactors/datapass_webhook/find_or_create_authorization_request.rb +++ b/app/interactors/datapass_webhook/find_or_create_authorization_request.rb @@ -72,10 +72,17 @@ def authorization_request_attributes ).merge(authorization_request_attributes_for_current_event).merge( 'last_update' => fired_at_as_datetime, 'previous_external_id' => context.data['pass']['copied_from_enrollment_id'], - 'api' => context.api + 'api' => context.api, + 'extra_infos' => extra_infos_with_service_provider ) end + def extra_infos_with_service_provider + context.authorization_request.extra_infos.merge({ + 'service_provider' => Hash(context.data.dig('pass', 'service_provider')) + }) + end + def authorization_request_attributes_for_current_event authorization_request_attributes_for_current_event = {} diff --git a/app/interactors/datapass_webhook/schedule_create_formulaire_qf_hubee_subscription_job.rb b/app/interactors/datapass_webhook/schedule_create_formulaire_qf_hubee_subscription_job.rb new file mode 100644 index 000000000..3a1e44b88 --- /dev/null +++ b/app/interactors/datapass_webhook/schedule_create_formulaire_qf_hubee_subscription_job.rb @@ -0,0 +1,9 @@ +class DatapassWebhook::ScheduleCreateFormulaireQFHubEESubscriptionJob < ApplicationInteractor + def call + return unless context.event == 'approve' + return unless context.modalities.include?('formulaire_qf') + + CreateFormulaireQFHubEESubscriptionJob.perform_later(context.authorization_request.id) + CreateFormulaireQFCollectivityJob.perform_later(context.authorization_request.id) + end +end diff --git a/app/jobs/create_formulaire_qf_collectivity_job.rb b/app/jobs/create_formulaire_qf_collectivity_job.rb new file mode 100644 index 000000000..f527ccaf4 --- /dev/null +++ b/app/jobs/create_formulaire_qf_collectivity_job.rb @@ -0,0 +1,6 @@ +class CreateFormulaireQFCollectivityJob < ApplicationJob + def perform(authorization_request_id) + authorization_request = AuthorizationRequest.find(authorization_request_id) + FormulaireQFAPIClient.new.create_collectivity(authorization_request) + end +end diff --git a/app/jobs/create_formulaire_qf_hubee_subscription_job.rb b/app/jobs/create_formulaire_qf_hubee_subscription_job.rb new file mode 100644 index 000000000..9a3a4b62c --- /dev/null +++ b/app/jobs/create_formulaire_qf_hubee_subscription_job.rb @@ -0,0 +1,64 @@ +class CreateFormulaireQFHubEESubscriptionJob < ApplicationJob + def perform(authorization_request_id) + authorization_request = AuthorizationRequest.find(authorization_request_id) + + hubee_organization_payload = find_or_create_organization_on_hubee(authorization_request) + + authorization_request.extra_infos['hubee_organization_id'] = build_hubee_organization_id(hubee_organization_payload) + authorization_request.save! + + subscription_payload = find_or_create_subscription_on_hubee(authorization_request, hubee_organization_payload) + + service_provider = authorization_request.extra_infos['service_provider'] + subscription_update_payload = {} + + if service_provider.present? && service_provider['type'] == 'editor' + subscription_update_payload.merge!({ + delegationActor: { + branchCode: service_provider['code_cog'], + companyRegister: service_provider['siret'], + type: 'EDT' + }, + accessMode: 'API' + }) + end + + update_subscription_on_hubee(authorization_request, subscription_payload, subscription_update_payload) + rescue ActiveRecord::RecordNotFound + # do nothing + end + + private + + def find_or_create_organization_on_hubee(authorization_request) + hubee_api_client.find_organization(authorization_request.organization) + rescue HubEEAPIClient::NotFound + hubee_api_client.create_organization(authorization_request.organization, authorization_request.demandeur.email) + end + + def find_or_create_subscription_on_hubee(authorization_request, hubee_organization) + payload = hubee_api_client.create_subscription(authorization_request, hubee_organization, process_code) + authorization_request.extra_infos['hubee_subscription_id'] = payload['id'] + authorization_request.save! + rescue HubEEAPIClient::AlreadyExists + payload = hubee_api_client.find_subscription(authorization_request, hubee_organization, process_code) + authorization_request.extra_infos['hubee_subscription_id'] = payload['id'] + authorization_request.save! + end + + def update_subscription_on_hubee(authorization_request, subscription_payload, editor_payload) + hubee_api_client.update_subscription(authorization_request, subscription_payload, editor_payload) + end + + def build_hubee_organization_id(hubee_organization_payload) + "SI-#{hubee_organization_payload['companyRegister']}-#{hubee_organization_payload['branchCode']}" + end + + def process_code + 'FormulaireQF' + end + + def hubee_api_client + @hubee_api_client ||= HubEEAPIClient.new + end +end diff --git a/app/models/authorization_request.rb b/app/models/authorization_request.rb index 99b125303..7e206c220 100644 --- a/app/models/authorization_request.rb +++ b/app/models/authorization_request.rb @@ -76,6 +76,10 @@ def most_recent_token has_one :contact_technique, through: :contact_technique_authorization_request_role has_one :contact_metier, through: :contact_metier_authorization_request_role + def organization + @organization ||= Organization.new(siret) + end + def contacts_no_demandeur contacts.reject { |user| user == demandeur } end diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 000000000..d3bb2917f --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,37 @@ +class Organization + attr_reader :siret + + def initialize(siret) + @siret = siret + end + + def denomination + unite_legale_insee_payload['denominationUniteLegale'] + end + + def code_commune_etablissement + adresse_etablissement_insee_payload['codeCommuneEtablissement'] + end + + def code_postal_etablissement + adresse_etablissement_insee_payload['codePostalEtablissement'] + end + + private + + def adresse_etablissement_insee_payload + etablissement_insee_payload['adresseEtablissement'] + end + + def etablissement_insee_payload + insee_payload['etablissement'] + end + + def unite_legale_insee_payload + etablissement_insee_payload['uniteLegale'] + end + + def insee_payload + @insee_payload ||= INSEESireneAPIClient.new.etablissement(siret:) + end +end diff --git a/app/organizers/datapass_webhook/api_particulier.rb b/app/organizers/datapass_webhook/api_particulier.rb index 0ff90188f..372da114c 100644 --- a/app/organizers/datapass_webhook/api_particulier.rb +++ b/app/organizers/datapass_webhook/api_particulier.rb @@ -8,6 +8,8 @@ class APIParticulier < ApplicationOrganizer } context.api = 'particulier' + context.authorization_request_data ||= {} + context.modalities = context.authorization_request_data['modalities'].presence || %w[params] end organize ::DatapassWebhook::FindOrCreateUser, @@ -18,6 +20,7 @@ class APIParticulier < ApplicationOrganizer ::DatapassWebhook::ReopenAuthorizationRequest, ::DatapassWebhook::RevokeCurrentToken, ::DatapassWebhook::UpdateMailjetContacts, + ::DatapassWebhook::ScheduleCreateFormulaireQFHubEESubscriptionJob, ::DatapassWebhook::ExtractMailjetVariables, ::DatapassWebhook::ScheduleAuthorizationRequestEmails, ::DatapassWebhook::APIParticulier::NotifyReporters diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc index 63bb3d6a4..cabc0142e 100644 --- a/config/credentials/production.yml.enc +++ b/config/credentials/production.yml.enc @@ -1 +1 @@ -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--HDNBQQ3PpNAKkT4p--BVWiQoNDqeWggu8sJnPyxA== \ No newline at end of file +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--8AwvpS+u7H/EDghs--eM7LS2qsBz5qPC/z+utNKw== \ No newline at end of file diff --git a/config/credentials/sandbox.yml.enc b/config/credentials/sandbox.yml.enc index a9fa2cd98..92e621b3b 100644 --- a/config/credentials/sandbox.yml.enc +++ b/config/credentials/sandbox.yml.enc @@ -1 +1 @@ -WXXMzkxUMGPk9DyWjuIoBM7ei3Qxi1M3mgKXW0jpEmW3DD7SJ4CVm7VnfuerOq59nDBeugTdY34h6DWSXQQ+d/ZpgxDDBX/TASWj5tBYjjYu+g7bYGsvXwYnGNJd7xnAD8zCndBAhwfQn7TXnCzhCCytrNvLen61+scndaPRNv3ipIC/ct+NlsogiEyB/3XXntZhm7Seu/2UdO5VNSFzQlW03m+6r226Tv/17oMPR8+ZF6z/jY+igJBhOQIAD8pKfSau+ju/mSJMVdKCkM+AYWQKiDHYwS5LJIKsdK3sRIIuXbDeijo2EgiwV9HniUbloD6ElDpTf0VAj4s30ZYxIJGfy0S4ZIOsmummBk+jUEoi3GqwFMNpgMRwpVVTnDdI+opVOk6trSf0F9dv2sD8j20jXBR1lrSLN/7RXgpd+iunivJ1191AxdLsDuz/nFkUye1fGDwc/lM6BaO3/ADbFVJTpg3ro7NvUt22T9uVq5X87jRLbJ2kNkKhtiz+cAaeEp5M++Lxl9kWTDgHOQY5p/YhZnuyri7yschIqyKU3KOGMxOPns+/BOpSRS+XrJwmXxRlYGqSQKgy+ryI8XC3k97/p8tSuaYbPQr9CN1x18TPxQ5quLn9rvjYVjlyOuF2bwxV3hHiqECn+y/IYTx6Mzagk1wLeixYb8Xwgu270DzyEooac15kAfGJxeeCer1Ao/UJpahJFOsc31xKeleufyUfducCO4jFEcsipVCBAvMWY9XeFGSv0byE5CO6GkFNwZIvArnKWahGkDBg7cMehp6QdvwuzxJCDUeZLXg2ESdXoFHgVFpk/o4wRBcrC1N1bNbcwhLasKisSxN02y81qeyCxuVc3cfvdPz4MPLsIRRqUzIupayramNsUI83+35+JhIA3UGCCIxt77btVFg8c77untKWHEVc5hquN1/KofSBHYAcL7C+Ja//nGCSSyPQd8dU15V1hMzA4ts8re07eEPFxkozpGMviDLO5w1iDuKb0nf5A3V+5sFhakMzL4Pdy269m43vuUXU+teop0q+nSt2/TOTqd524FU28ZTpTgR37fdp8brdKA5PrfguPJfTHWLBoo6o7mQxEcyEyLIypQe713klLK1fp6IJsKw1GwT9CJI7PQppIxCGqPKIP9uDtVNvKG0UcX05L+L4E9k88j5i7ZuCvb0P92bbMLmld1Thsx2OdGCbCDjdnDdgAJFFRDSdZmL8GbaZHRNhojQ5Q0UEJrs7P+ow+2pfl+mfxn1H8cALc6uceC+MpZqyyNp+CEzDL/gVdugmuXm5BeFKOTwomn5aZ5uZ/2NcobHyym9DyLqFhUjalN3kaR4eAGRBy0ey+SYuaSHVbLLwenKw2KPWQeitadbjOyFowwFOU5ZTnRqC5JedBLq8wYCGqdihmT0+GfvL8KMU8bE2AUSXBz4RdUdIPyeqXdXZT+L79Xf0T63FrZjSTP8AqyZh+0xrNhVycWFXGRPN3GgfisEFI9+fFSWsMqqn+1g98btMfQMBixd2H47ZXWjgpbULHVjbE7nGiaa4Dauwr72SuGvU608B2EZcgdF9SrpXCjwarJz5ntdAc3erS/I=--cnVnlQsoPPSaGR9D--QaXFsqNdQvQ/Hw7vpmCLuw== \ No newline at end of file +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--sVa2IBBRCHcz0YkY--KqKofsqRXSMfN0XY3PEkxw== \ No newline at end of file diff --git a/config/credentials/staging.yml.enc b/config/credentials/staging.yml.enc index 0a007e35d..3524d000d 100644 --- a/config/credentials/staging.yml.enc +++ b/config/credentials/staging.yml.enc @@ -1 +1 @@ -9JD9jNdU7DCiqe0LAHSWFuB7ANmPinU32C0Fv1YkQhItRWtWKcGo+fsbv/Z/T2UbwKB8CMBRM2zwL4sKedcWBw+uTACeA/86k1kQJXuLZO/X3XuTAf2jOLLLo11YLXBYthN2tkXl+qXH2dyOvZxjSsAc+pKKe1VZkl7+XIgikEgXZw2lSjf7RV9XS3i/QwO63XkedHuTbLTSPh4U9pCsa8oFALQzVyenBiLtgi2PIbFXdYpzdd5ZlDinWGFIz7rg2qIe2s1q0bVFjMSCi+7n7yLtS0yZy7jYVfvXBh2bOrfKGd8L+9akOG1nhWu7FaY/4wxxnlaTeN9uBqZkbVOPpecE35MYmjhpuw+PCaSiussADICqutiSPkqXpUtZHPYJ2EGcT1p6aRBU9ZQzs2eywVOp5rcbuKVVnUdEb6JLNt81OLiG0Mh3efq2q9bDTT9ygBReRqFVNr/sNq4wWPTb1dBbua8q44zJmDzL659mJZWlyPZjjIga+vrq/KfYQ7XIluZk17Fd4ESJ09a8AAYvDF1alzWsEs4ne2/SZGZHlDczrQEb4kq12h2PwKx5PNxdJQudrCR3acp8D6gjHr0XOGHcbH6jwaWe2VLXp1ovx4pf6su9JsvhJ+2wdPyeBof4vLGPhakScor8BVefVo88yYGUFvXV43NGIwbHlJYlUqv60jV17stiwKhVddUDyEdL34DJqCMYn1PuEsvAIKhVVKKPV/MPZT2PXto0QU5eD/167caa5JFMSYxNvUfIrgRAEtvhBJlnSB5psKi8DVIWt6rFPHDQoykVl6Ow5tkJCheXnggsrZX/a+SzyC6nqWXf9Dw1p1d7lIaaH+bSuiugBZTAZj5MmE9t/yOJMWmpN8+OXf4CErlcFIlphg3EBP8TTK8A44dMEzAvqABcpyCAsBi+spOg55lpmJAdbWwm+OtQ1fgOFS0CrShCIMs0gAOAaZQQKLgVaWHx93y1Fx0e5qP8B+CzxKBjZY1YgCTfPORGD71VZhOhA0X7zme9Ot0Y1DmQ04aQvqRmACrEN95pyqYZ7FWFFG1Mzy2VGb7INpeiXBaWPpaoWCdOkh7SBB+Foj6klXY1hbB+jPaxP4jqsOMheVXBLMRcJCps1Ax+mTqiMN9OHi0ibT2kGS+Ir/CuLtHTPTMD1mkHQN4PNH7HTemCdk0Fi87PAYtdG/f4IqlFAvPI5VSp04wDv1vN9iSxSVOjO6n2DULcAjhT22DHU87Lb2vsvzRNG88GoY9gGmOKiEUxkwKWV85/D3MzV376kY6R699jfsh7Ms2GiyWH5fKD3ZqS+LdN7+0bkarPdKYqtBaB/e2Gsx1YHB1l8ZW3IpXONA0s8gvH+OxJg3xNaH13m8DZKPK20ZOZWdW+3VIF/54e2NhS49D4KXutuo5xB2YGSRLpDSMoIYscJID7cc+VK2Gqmf07AlvpmUYW2S2mO5dgzu+KOPVzBkTQ+rhUsthEdUIpQXn1y94SLlxgJcMlA3deqXLfQZwKG/mNAuqmvx/t5xkMyvhz3OdGHek2Os6wesQ/t4TqkUs4iY6WHolZ0TARP+hRDRtLMdNtwP7V0vSfWSvrZut9UypfEqKlrWtiPIgpZlRaro8uQkoaweTpPR6CQJ6klnsoOQj6B7pbscg9E6X4tM15Ld8IogItn9Ted5OyxwroWUc=--ikgyIcb4z3MncQnj--HZOkrCFarVAis0DMXy0oaw== \ No newline at end of file +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--XsN3Zq365PlsbdKl--Mxes8FOFobS+b+Yx+Ky+Lg== \ No newline at end of file diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc index de8b4b441..26ec8c3a3 100644 --- a/config/credentials/test.yml.enc +++ b/config/credentials/test.yml.enc @@ -1 +1 @@ -ADXpePqAwH/J5T8SGYvBbmZcw5VWezhEngFQVDYROdye9KoQ6OJ46aNYpBQW2W6wofG2IBQq19hpNcI8/X31I1RMOyvcRCnfeJ8nSFiCEH/6vnxySB3gpxEMfosQ2BZs3QOWe/ulB3JcoZE1nFMbD8GTBXK8U7YUHUNeAqJowOQ/5AE0/jVkDkoV2s/cZAdiSxAdvSU+flib16yrjVKdPlyyLDs6lnyNx9deps8Hd/FYy09bBvJ8srROXBLPpCaagYoKvxHSJTidj/n9yQ29Pdj4vyylaDaN2kCXZAVQ+XTwM/hz8bBhYlZZ6McWQU9QHyM/WeU7pTPMy+yNVTUvF2A5z4vd4vW4fON1rUOlHSULUNSyhvSWfZcmXF4LTpKX5By0qOFs8fbUrUZI2upto1wcCDL2e4bbw2TXxL1sZSNBzBN8da7w4pKeCppJshA4glDGN+z7jWHnn8mD1lqG1YuoVsmGwLDnoWbvQ+Ke5+0VVLiH4A6pzWC+oD0ZQF8ENDT8LpFwojwwB6EsfECG0GjWcEFEWyWddYTQ4QEv8360U/gpWAAZUp5Qcc9V5QZ+XNzFJS6Q3fLGxK+juOnRMcc8DJTEZUlFIemB6fDTAUCUODLjJdBYmj2rL+la4Ib8zYozqoLHNQYPPKentwl0bgQQSHiDMkXohDLb8nYcvg66KGdcRdvCtE7y1Q5vgNX4C+CkrWNGuynuQ4wpHnQ/b7vmJQYNpaDjNsHpkjSxnBACBz7RgLFA+xtF0JAYTvW8jbrWQNDrTzp1SZA/53RBZYRsbNUJ0g8FHkFQTZpvsLDVw/0A6uv2avnOrtM3N//v+arwMU6Pmw5uy4iK9OhzExmjf+wfsmBwDHWyMF54nzN0uW7kwpau5b5lmiRhYYxVfJZyPUm0Pyvba/ZhkznYOtItA+xiTLpeJrghqXD5TCDl8r3bvVACQzQsBcYCC7kbWPJ3AKfsH7ml7v5zVj2a1gx8JZ+TKIF9i97lbOZ9XrktmdFmXsVE4CmFkpYpwWfzmOoO4hJQDqNu5164WyUEEURPNVa1e+nmtQNB6rK0ckL+nJ0bQDoVEx1JgJcgvkGlUF41vv2i4v0gqI7lECsCaLTnL9czKuKMf/BwYOSZVttBaSJDGi2HVbXmWfBdbJrcR4oVs/Y+UJ17Pi8PBA5k4L/Tzc9LZ4b4WgX3A7TYMdTMASTaILOMA8QC1uqz4+YIvp7TI4spOtQz6djrzMkNpQoyogcXWvxFIeYhetEz6W+RHKOQe6GMLYY9sQQ5E0DNcICkVpJ2IuixDdpE2tU5U9IaCofJ3dMWx32vH8xTqXyvzQRS0b2NXBw/7zn15qs5I+T1JiS+L/EWDf/HyPlsqiZsFSkiAdmmZPV6eOGd2L/eZ0RmmKna2bMeuCdk44oREJxW9nrTobODsX+CYLCqN+ckNIQW9BxDoBkIdh7hM+omM3SW5ZYL5LQby8z/YsIMECkdf7Pj4bD5+KSvJ2UMP8mnCMghXMYJJd3oKZCkij1oZhrelYYp9APNKF3wsG7skJoO7iBc+v+aAi95FKPo64wy+xySICXVd1/SCI2Gqiju0FvGTVcLt0hcmmG01mA4nGX5b3f5AzX/x7g1HjxEdL80AyEAPW5/o94yE2eOaWHwCEDRbp6xwT8vMtobyyB0Lm/ns4vA3UBljmafsvzUBWBEhid83ns0IjSWzVYtuY2PrU/E3/i9Btw0J2oa1va89iA/yWFH1O4dZhdfhSUFt6HE1ydf16GRoLINHbTzVolJCtlivfdP2oRzFQDKrmQaXYGSMGbxLHNam45CopHPWThkx1AY9xs=--qBCUG6JSF8JVLmIJ--BlPRDPPc+M7l/sFLkzoRDQ== \ No newline at end of file +YjmNdlDDxPQa0FD+f39/FXCtYlrsfgE3s7ahezgeZpVmQBMAw6g9TZEYclfqeawN6mvSwmmUVOtg50/fGrLelWtZipZM34dC/s+2sEJk+jsJNon8oh2JiKmNBp+0/d/2EnpgXsSgxcq8s8x5uRG1dVyF0PsNvZzW8AHLj1BR3LjCN1BfFzGjUXFzMOLN1ULqQvNvYmdKcVLqlQAOh5G9OvFFPhY2IOfEX6zuYlpgM3F3n+tiySykeqhXSL0KLRl+xe1XxS7LIB716vI1fegQxhVaHqsv48uS+87ACSyexsMioDSJ/sW8NUQY7ggRN8aJERjIjtKrEA6ElAaEvHwGXQJqmYlqZKCzer+X8zt7xMrXToO+sEvBlAKJ1IRFixg/aWai1grMBvlnEKGKRo7N18d45JGkc2wSaEUwoRDLA0AIqXz8O3ShUpVBhGmiulT5SpWbvCOsovvTC7kqi4645W4XZLpXhBAPWqjMNhKdnOSI4pK2y4iaOYgMIRVnRpM/q9Uq0Kn/+q3VO9E6CsVofni5b7n3LNRenMNZN1R4D6LBbTZrtd+zO3W6KrD46qj32aAmU5gNJoxnGaWebcH6Csibrh12MjQfAaLnmCzqy8q04YYSN0OnFhI4LDCiZdnMTbf+/xpqMfMgEbXE2RX0TvqzxreN4vqorIlEpw9Q9bHqsfkxC7Rpx8Kr/Hs0aJ/ZN2vrqVjHD3v4GZn3JaP3UtZIXQf54nBp6fSaG533V1sjy/Q7R15k8xf7h6c6C4dkbn1osIqVI1kmGk6wkDqAz4N4BWYZC85AQ8oOqEyRRe63dWPaAhaulPcszka1nuC5LLdfLtTLrgLk0hypjTN8VA6rJ3TLMcOqToDSGCGZew+dyfKdUXFiZOm6na5OctmzJYCyJa+DplHGAN709aNT4phkFP+hcX/VnG4c0OrFVyZsPlsIbcnNfW4pG9OJyHVUGQoLXZsPcwTSX4VlVPbSekDYjO+khEODW5MVqXc/V/uxf1/Qhou9wsImsEKtLEiBW6sR6JvjyyiVlnFu5CMXphEluXzmw/k+ClwRpQ6iIhx0Ydj2GoyhtJ24ebCQmMvQkXjSd7n2i2wWE7D9eOAxjPnzCvMIrYVQ/rI+sVA9MkYyl9bT4aVQq6A5KMnG+yLSHkDwaDft9BPc8ihBJQY33Sm6ZM0BnxG7qDgIY+jY/9uVVn3qMxDR75M6gt6EHQFdI+uO+lNDXe27R9nXLR9+3crHD0Hd6NULIrLlPrLGegD9RwY0kNRPl9ovo698sKwS8vueI0XUg7qVnvNkTJiCUObTHlsU/cocBRtD5w65I6ClQ6r0I83u/98odS4uJOseqZxiDI+QpBQK/J4aI513QdFQiUcAp/SUGUGjodMOdi186ShnrwnD7hgT9M2Asjm0lSi9qtsYaRsm9xLJeRAzxjgbtpYlAJizW2r0LMrkEOXeqB5f2xpoz6h3IiBf4u8RHjzbPF/LYKwejK7AA2T6SLvTo1f1CXuoglE4b4yG1UbSQEmFEzzyfHsuK6ULS2i7ZIrpqTx1+uWXKhY6XfvIxY+aHzr27FXxVLrbhLj6y2EgeLao2THw+PRqGdWMizdjY0NGP37V96dd33QjL98UJaQbb9J0bAxzhvOThd4VeTxtFl/dCa0wLHApYHfGYdrVgIjpEC0xMd/mennJz8chTm90xPciKS772UqewGZiUCFvFQ37d/bIOr+fyikFbYfM3+yMbpy183iBg1xuWklocFpZvRe4UMo1M8xCSHvr1T5gtOxC3pu4mb5NuzmH9KT1Z2Y41TRjy9jcS4JaOG4mDvZP75q5EmTkVXDopbdIbyCf77gCCmiVmRjCOI1igzwQyh96iDDDbw4RluUCZPjgEXEVj87d5VtXhjdDkqWLekEh3oCchboNShsKAUdkk0OvkAzXq6LDjj3VljNemVAb9krhIawXm6oJiBM9rQSnN22JQjfvzAIhQYOhp7/8jJKRbnZd5M9rcJNf1MxGxnHvxUHslWRN0SzzCMuihrAc3ZmcfzHBPPwGIgCJ7s8bgF2zBD8rRQvbNBxP35rF/zKvTR4npt7FwX37DjCjXTTtmYSQvdcRzUUBFpsA2WLBCQM/FsdTx2l8K8uRCxgYdaZbCQZhPMH8FIYW3aNdqxUdWETdMagBA2exnNJO2IvTg2fe6Nrur/bzorjhgoi7KqR13RypvOQYSJn+JIHrf3EIRy1pH3l4e888/928Cv9SvF5PsfIzCxW5VW3lISQW/LDp+W4ietq2RWE8EU43byA/vtHiVRjZ1bF23Hiiyq9XWchS--1iVrN2uZYhSZST7Q--wTX5GsuCHbFXF8Ki2bYStA== \ No newline at end of file diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 6cc7380eb..cac5b73cd 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -15,4 +15,9 @@ inflect.acronym 'HTTP' inflect.acronym 'FAQ' + + inflect.acronym 'HubEE' + inflect.acronym 'INSEE' + + inflect.acronym 'QF' end diff --git a/db/migrate/20240624152759_add_extra_infos_to_authorization_requests.rb b/db/migrate/20240624152759_add_extra_infos_to_authorization_requests.rb new file mode 100644 index 000000000..3db463e21 --- /dev/null +++ b/db/migrate/20240624152759_add_extra_infos_to_authorization_requests.rb @@ -0,0 +1,5 @@ +class AddExtraInfosToAuthorizationRequests < ActiveRecord::Migration[7.1] + def change + add_column :authorization_requests, :extra_infos, :jsonb, default: {} + end +end diff --git a/db/schema.rb b/db/schema.rb index 9e3385e3b..a5141ef0d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -34,6 +34,7 @@ t.string "siret" t.string "api", null: false t.string "demarche" + t.jsonb "extra_infos", default: {} t.uuid "public_id" t.index ["external_id"], name: "index_authorization_requests_on_external_id", unique: true, where: "(external_id IS NOT NULL)" end diff --git a/spec/clients/hubee_api_client_spec.rb b/spec/clients/hubee_api_client_spec.rb new file mode 100644 index 000000000..9e6b335d1 --- /dev/null +++ b/spec/clients/hubee_api_client_spec.rb @@ -0,0 +1,67 @@ +RSpec.describe HubEEAPIClient do + let(:hubee_api_authentication) { instance_double(HubEEAPIAuthentication, access_token: 'access_token') } + + before do + allow(HubEEAPIAuthentication).to receive(:new).and_return(hubee_api_authentication) + end + + describe '#find_organization' do + subject(:find_organization_payload) { described_class.new.find_organization(organization) } + + let(:organization) { Organization.new(siret) } + let(:siret) { '13002526500013' } + let(:code_commune) { '13055' } + let(:host) { Rails.application.credentials.hubee_api_url } + + before do + allow(organization).to receive(:code_commune_etablissement).and_return(code_commune) + end + + context 'when the API returns a 200' do + let(:valid_payload) { hubee_organization_payload(siret:, code_commune:) } + + before do + stub_request(:get, "#{host}/referential/v1/organizations/SI-#{siret}-#{code_commune}").to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: valid_payload.to_json + ) + end + + it 'renders a valid json from payload' do + expect(find_organization_payload).to eq(valid_payload) + end + end + + context 'when API returns 404' do + before do + stub_request(:get, "#{host}/referential/v1/organizations/SI-#{siret}-#{code_commune}").to_return( + status: 404, + headers: { 'Content-Type' => 'application/json' }, + body: { + 'header' => { + 'statut' => 404, + 'message' => "Aucun élément trouvé pour le siret #{siret}" + } + }.to_json + ) + end + + it 'raises a NotFound error' do + expect { find_organization_payload }.to raise_error(HubEEAPIClient::NotFound) + end + end + + context 'when API returns unknown status' do + before do + stub_request(:get, "#{host}/referential/v1/organizations/SI-#{siret}-#{code_commune}").to_return( + status: 500 + ) + end + + it 'raises an error' do + expect { find_organization_payload }.to raise_error(Faraday::Error) + end + end + end +end diff --git a/spec/clients/insee_sirene_api_client_spec.rb b/spec/clients/insee_sirene_api_client_spec.rb new file mode 100644 index 000000000..1695076fe --- /dev/null +++ b/spec/clients/insee_sirene_api_client_spec.rb @@ -0,0 +1,43 @@ +RSpec.describe INSEESireneAPIClient do + let(:insee_api_authentication) { instance_double(INSEEAPIAuthentication, access_token: 'access_token') } + + before do + allow(INSEEAPIAuthentication).to receive(:new).and_return(insee_api_authentication) + end + + describe '#etablissement' do + subject(:etablissement_payload) { described_class.new.etablissement(siret:) } + + let(:siret) { '13002526500013' } + + context 'when the API returns a 200' do + let(:valid_payload) { insee_sirene_api_etablissement_valid_payload(siret:) } + + before do + stub_request(:get, "https://api.insee.fr/entreprises/sirene/V3.11/siret/#{siret}").to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: valid_payload.to_json + ) + end + + it 'renders a valid json from payload' do + expect(etablissement_payload).to eq(valid_payload) + end + end + + context 'when API returns something else than 200' do + before do + stub_request(:get, "https://api.insee.fr/entreprises/sirene/V3.11/siret/#{siret}").to_return( + status: 404, + headers: { 'Content-Type' => 'application/json' }, + body: '' + ) + end + + it 'raises an error' do + expect { etablissement_payload }.to raise_error(Faraday::Error) + end + end + end +end diff --git a/spec/factories/datapass_webhooks.rb b/spec/factories/datapass_webhooks.rb index b574ba846..cd813e636 100644 --- a/spec/factories/datapass_webhooks.rb +++ b/spec/factories/datapass_webhooks.rb @@ -63,6 +63,15 @@ 'entreprises' => true } end + + service_provider do + { + 'id' => '3d_ouest', + 'type' => 'editor', + 'siret' => '44973625500018', + 'code' => '22113' + } + end end factory :datapass_webhook_team_member_model, class: Hash do diff --git a/spec/factories/datapass_webhooks_v2.rb b/spec/factories/datapass_webhooks_v2.rb index 323b5dddd..6360112b0 100644 --- a/spec/factories/datapass_webhooks_v2.rb +++ b/spec/factories/datapass_webhooks_v2.rb @@ -60,5 +60,9 @@ contact_metier_given_name { 'Jacques' } contact_metier_family_name { 'Metier' } contact_metier_job_title { 'CMO' } + + modalities do + %w[params] + end end end diff --git a/spec/fixtures/insee/13002526500013.json b/spec/fixtures/insee/13002526500013.json new file mode 100644 index 000000000..af5c31feb --- /dev/null +++ b/spec/fixtures/insee/13002526500013.json @@ -0,0 +1,102 @@ +{ + "header": { + "statut": 200, + "message": "ok" + }, + "etablissement": { + "siren": "130025265", + "nic": "00013", + "siret": "13002526500013", + "statutDiffusionEtablissement": "O", + "dateCreationEtablissement": "2017-05-24", + "trancheEffectifsEtablissement": "22", + "anneeEffectifsEtablissement": "2021", + "activitePrincipaleRegistreMetiersEtablissement": null, + "dateDernierTraitementEtablissement": "2023-11-30T10:17:12", + "etablissementSiege": true, + "nombrePeriodesEtablissement": 1, + "uniteLegale": { + "etatAdministratifUniteLegale": "A", + "statutDiffusionUniteLegale": "O", + "dateCreationUniteLegale": "2017-05-24", + "categorieJuridiqueUniteLegale": "7120", + "denominationUniteLegale": "DIRECTION INTERMINISTERIELLE DU NUMERIQUE", + "sigleUniteLegale": "DINUM", + "denominationUsuelle1UniteLegale": null, + "denominationUsuelle2UniteLegale": null, + "denominationUsuelle3UniteLegale": null, + "sexeUniteLegale": null, + "nomUniteLegale": null, + "nomUsageUniteLegale": null, + "prenom1UniteLegale": null, + "prenom2UniteLegale": null, + "prenom3UniteLegale": null, + "prenom4UniteLegale": null, + "prenomUsuelUniteLegale": null, + "pseudonymeUniteLegale": null, + "activitePrincipaleUniteLegale": "84.11Z", + "nomenclatureActivitePrincipaleUniteLegale": "NAFRev2", + "identifiantAssociationUniteLegale": null, + "economieSocialeSolidaireUniteLegale": "N", + "societeMissionUniteLegale": null, + "caractereEmployeurUniteLegale": "N", + "trancheEffectifsUniteLegale": "22", + "anneeEffectifsUniteLegale": "2021", + "nicSiegeUniteLegale": "00013", + "dateDernierTraitementUniteLegale": "2023-11-30T10:17:13", + "categorieEntreprise": "PME", + "anneeCategorieEntreprise": "2021" + }, + "adresseEtablissement": { + "complementAdresseEtablissement": null, + "numeroVoieEtablissement": "20", + "indiceRepetitionEtablissement": null, + "typeVoieEtablissement": "AV", + "libelleVoieEtablissement": "DE SEGUR", + "codePostalEtablissement": "75007", + "libelleCommuneEtablissement": "PARIS 7", + "libelleCommuneEtrangerEtablissement": null, + "distributionSpecialeEtablissement": null, + "codeCommuneEtablissement": "75107", + "codeCedexEtablissement": null, + "libelleCedexEtablissement": null, + "codePaysEtrangerEtablissement": null, + "libellePaysEtrangerEtablissement": null + }, + "adresse2Etablissement": { + "complementAdresse2Etablissement": null, + "numeroVoie2Etablissement": null, + "indiceRepetition2Etablissement": null, + "typeVoie2Etablissement": null, + "libelleVoie2Etablissement": null, + "codePostal2Etablissement": null, + "libelleCommune2Etablissement": null, + "libelleCommuneEtranger2Etablissement": null, + "distributionSpeciale2Etablissement": null, + "codeCommune2Etablissement": null, + "codeCedex2Etablissement": null, + "libelleCedex2Etablissement": null, + "codePaysEtranger2Etablissement": null, + "libellePaysEtranger2Etablissement": null + }, + "periodesEtablissement": [ + { + "dateFin": null, + "dateDebut": "2017-05-24", + "etatAdministratifEtablissement": "A", + "changementEtatAdministratifEtablissement": false, + "enseigne1Etablissement": null, + "enseigne2Etablissement": null, + "enseigne3Etablissement": null, + "changementEnseigneEtablissement": false, + "denominationUsuelleEtablissement": null, + "changementDenominationUsuelleEtablissement": false, + "activitePrincipaleEtablissement": "84.11Z", + "nomenclatureActivitePrincipaleEtablissement": "NAFRev2", + "changementActivitePrincipaleEtablissement": false, + "caractereEmployeurEtablissement": "N", + "changementCaractereEmployeurEtablissement": false + } + ] + } +} \ No newline at end of file diff --git a/spec/interactors/datapass_webhook/find_or_create_authorization_request_spec.rb b/spec/interactors/datapass_webhook/find_or_create_authorization_request_spec.rb index 35ef0d0bd..dc99f5929 100644 --- a/spec/interactors/datapass_webhook/find_or_create_authorization_request_spec.rb +++ b/spec/interactors/datapass_webhook/find_or_create_authorization_request_spec.rb @@ -68,6 +68,10 @@ expect(subject.reopening).to be_falsey end + it 'stores the service provider in extra_infos' do + expect { subject }.to change { authorization_request.reload.extra_infos['service_provider'] } + end + context 'when it is the same demandeur' do let(:demandeur) { build(:datapass_webhook_team_member_model, type: 'demandeur', email: authorization_request.demandeur.email) } diff --git a/spec/jobs/create_formulaire_qf_collectivity_job_spec.rb b/spec/jobs/create_formulaire_qf_collectivity_job_spec.rb new file mode 100644 index 000000000..90c2744a2 --- /dev/null +++ b/spec/jobs/create_formulaire_qf_collectivity_job_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe CreateFormulaireQFCollectivityJob do + describe '#perform' do + subject(:job) { described_class.perform_now(authorization_request.id) } + + let(:api_client) { instance_double(FormulaireQFAPIClient) } + let(:authorization_request) { create(:authorization_request, :with_demandeur, api: 'particulier') } + + before do + allow(FormulaireQFAPIClient).to receive(:new).and_return(api_client) + end + + it 'creates collectivity on Formulaire QF' do + expect(api_client).to receive(:create_collectivity).with(authorization_request) + + job + end + end +end diff --git a/spec/jobs/create_formulaire_qf_hubee_subscription_job_spec.rb b/spec/jobs/create_formulaire_qf_hubee_subscription_job_spec.rb new file mode 100644 index 000000000..3496d39fb --- /dev/null +++ b/spec/jobs/create_formulaire_qf_hubee_subscription_job_spec.rb @@ -0,0 +1,106 @@ +RSpec.describe CreateFormulaireQFHubEESubscriptionJob do + describe '#perform' do + subject(:create_formulaire_qf_hubee_subscription_job) do + described_class.perform_now(authorization_request.id) + end + + let(:hubee_api_client) { instance_double(HubEEAPIClient) } + let(:authorization_request) { create(:authorization_request, :with_demandeur, api: 'particulier') } + let(:subscription_payload) { hubee_subscription_payload(authorization_request:) } + + before do + allow(HubEEAPIClient).to receive(:new).and_return(hubee_api_client) + end + + context 'when organization does not exist on HubEE' do + let(:organization_payload) { hubee_organization_payload(siret:, code_commune:) } + let(:siret) { '12345678901234' } + let(:code_commune) { '12345' } + + before do + allow(hubee_api_client).to receive(:find_organization).and_raise(HubEEAPIClient::NotFound) + allow(hubee_api_client).to receive_messages(create_organization: organization_payload, create_subscription: subscription_payload) + allow(hubee_api_client).to receive(:update_subscription) + end + + it 'creates an organization on HubEE' do + expect(hubee_api_client).to receive(:create_organization) + + create_formulaire_qf_hubee_subscription_job + end + + it 'stores the organization id on the authorization request' do + create_formulaire_qf_hubee_subscription_job + + expect(authorization_request.reload.extra_infos['hubee_organization_id']).to eq("SI-#{siret}-#{code_commune}") + end + + it 'creates a subscription on HubEE' do + expect(hubee_api_client).to receive(:create_subscription) + + create_formulaire_qf_hubee_subscription_job + end + + it 'stores the subscription id on the authorization request' do + create_formulaire_qf_hubee_subscription_job + + expect(authorization_request.reload.extra_infos['hubee_subscription_id']).to eq(subscription_payload['id']) + end + end + + context 'when organization exists on HubEE' do + before do + allow(hubee_api_client).to receive_messages(find_organization: hubee_organization_payload, create_subscription: subscription_payload) + allow(hubee_api_client).to receive(:update_subscription) + end + + it 'does not create an organization on HubEE' do + expect(hubee_api_client).not_to receive(:create_organization) + + create_formulaire_qf_hubee_subscription_job + end + + it 'creates a subscription on HubEE' do + expect(hubee_api_client).to receive(:create_subscription) + + create_formulaire_qf_hubee_subscription_job + end + + it 'stores the subscription id on the authorization request' do + create_formulaire_qf_hubee_subscription_job + + expect(authorization_request.reload.extra_infos['hubee_subscription_id']).to eq(subscription_payload['id']) + end + end + + describe 'subscription creation' do + before do + allow(hubee_api_client).to receive(:find_organization).and_return(hubee_organization_payload) + end + + context 'when subscription already exists' do + let(:hubee_subscription_payload) { { 'id' => 123 } } + + before do + allow(hubee_api_client).to receive(:create_subscription).and_raise(HubEEAPIClient::AlreadyExists) + allow(hubee_api_client).to receive(:find_subscription).and_return(hubee_subscription_payload) + allow(hubee_api_client).to receive(:update_subscription) + end + + it 'does not raise an error' do + expect { create_formulaire_qf_hubee_subscription_job }.not_to raise_error + end + end + + context 'when subscription failed' do + before do + allow(hubee_api_client).to receive(:create_subscription).and_raise(Faraday::Error) + end + + it 'raises an error' do + expect { create_formulaire_qf_hubee_subscription_job }.to raise_error(Faraday::Error) + end + end + end + end +end diff --git a/spec/organizers/datapass_webhook/v2/api_particulier_spec.rb b/spec/organizers/datapass_webhook/v2/api_particulier_spec.rb index 6c857b030..19e5829bc 100644 --- a/spec/organizers/datapass_webhook/v2/api_particulier_spec.rb +++ b/spec/organizers/datapass_webhook/v2/api_particulier_spec.rb @@ -41,6 +41,80 @@ expect(last_token.api).to eq('particulier') end + context 'when modalities does no include params' do + before do + datapass_webhook_params['data']['data']['modalities'] = ['formulaire_qf'] + end + + it 'does not create a token' do + expect { + subject + }.not_to change(Token, :count) + end + end + + describe 'modalities' do + context 'when modalities does not include formulaire_qf' do + before do + datapass_webhook_params['data']['data']['modalities'] = ['params'] + end + + it 'does not schedule a job to create formulaire qf access on HubEE' do + expect { + subject + }.not_to have_enqueued_job(CreateFormulaireQFHubEESubscriptionJob) + end + + it 'does not schedule a job to create formulaire qf access on HubEE' do + expect { + subject + }.not_to have_enqueued_job(CreateFormulaireQFCollectivityJob) + end + end + + context 'when modalities include formulaire_qf' do + before do + datapass_webhook_params['data']['data']['modalities'] = ['formulaire_qf'] + end + + context 'when event is approve' do + before do + datapass_webhook_params['event'] = 'approve' + end + + it 'schedules a job to create formulaire qf access on HubEE' do + expect { + subject + }.to have_enqueued_job(CreateFormulaireQFHubEESubscriptionJob) + end + + it 'schedules a job to create the collectivity on formulaire qf' do + expect { + subject + }.to have_enqueued_job(CreateFormulaireQFCollectivityJob) + end + end + + context 'when event not approve' do + before do + datapass_webhook_params['event'] = 'reject' + end + + it 'does not schedule a job to create formulaire qf access on HubEE' do + expect { + subject + }.not_to have_enqueued_job(CreateFormulaireQFHubEESubscriptionJob) + end + + it 'does not schedule a job to create formulaire qf access on HubEE' do + expect { + subject + }.not_to have_enqueued_job(CreateFormulaireQFCollectivityJob) + end + end + end + end + describe 'Mailjet adding contacts' do it 'adds contacts to Entreprise mailjet list' do expect(Mailjet::Contactslist_managemanycontacts).to receive(:create).with( @@ -71,4 +145,8 @@ subject end end + + it 'affects authorization request data to authorization_request_data on context' do + expect(subject.authorization_request_data).to eq(datapass_webhook_params['data']['data']) + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index f63c9ac57..2cee3272f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -84,6 +84,8 @@ config.include SpecsHelper config.include FeatureHelper, type: :feature config.include ExternalUrlHelper, type: :feature + config.include INSEESireneAPIMocks + config.include HubEEAPIMocks config.around(:each, :js) do |example| example.run_with_retry retry: example.metadata[:retry] || 3 diff --git a/spec/support/hubee_api_mocks.rb b/spec/support/hubee_api_mocks.rb new file mode 100644 index 000000000..cc21d99b1 --- /dev/null +++ b/spec/support/hubee_api_mocks.rb @@ -0,0 +1,40 @@ +module HubEEAPIMocks + def hubee_organization_payload(siret: '13002526500013', code_commune: '75017') + { + 'country' => 'France', + 'code' => 'DINUM', + 'postalCode' => '75007', + 'type' => 'SI', + 'companyRegister' => siret, + 'createDateTime' => '2021-05-20T15:59:02.569+00:00', + 'branchCode' => code_commune, + 'phoneNumber' => '0000000000', + 'name' => 'DIRECTION INTERMINISTERIELLE DU NUMERIQUE', + 'updateDateTime' => '2022-01-27T19:42:07.386+00:00', + 'email' => 'datapass@yopmail.com', + 'territory' => 'PARIS 7', + 'status' => 'Actif' + } + end + + def hubee_subscription_payload(authorization_request:, organization_payload: hubee_organization_payload, process_code: 'TEST') + { + 'id' => SecureRandom.uuid, + 'datapassId' => authorization_request.external_id.to_i, + 'notificationFrequency' => 'unitaire', + 'processCode' => process_code, + 'email' => authorization_request.demandeur.email, + 'localAdministrator' => { + 'email' => authorization_request.demandeur.email + }, + 'status' => 'Actif', + 'subscriber' => { + 'branchCode' => organization_payload['branchCode'], + 'companyRegister' => organization_payload['companyRegister'], + 'type' => 'SI' + }, + 'creationDateTime' => '2024-06-24T16:01:27.142+00:00', + 'updateDateTime' => '2024-06-24T16:01:27+0000' + } + end +end diff --git a/spec/support/insee_sirene_api_mocks.rb b/spec/support/insee_sirene_api_mocks.rb new file mode 100644 index 000000000..2c56643c4 --- /dev/null +++ b/spec/support/insee_sirene_api_mocks.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module INSEESireneAPIMocks + def insee_sirene_api_etablissement_valid_payload(siret:, full: false) + if full + read_json_fixture("insee/#{siret}.json") + else + { + 'header' => { + 'statut' => 200, + 'message' => 'OK' + }, + 'etablissement' => { + 'siren' => siret.first(9) + } + } + end + end + + def insee_sirene_api_not_found_payload + { + 'header' => { + 'statut' => 404, + 'message' => 'Not Found' + } + } + end +end