✨(plugin) add CommuneCreation plugin
Introduces some machinery for testing and executing API orchestrations. Rolls back some changes in NameFromSiret plugin.
This commit is contained in:
committed by
Laurent Bossavit
parent
1be30496be
commit
34c9dc6cd7
@@ -465,6 +465,21 @@ class Base(Configuration):
|
|||||||
environ_name="MAIL_PROVISIONING_API_CREDENTIALS",
|
environ_name="MAIL_PROVISIONING_API_CREDENTIALS",
|
||||||
environ_prefix=None,
|
environ_prefix=None,
|
||||||
)
|
)
|
||||||
|
DNS_PROVISIONING_API_URL = values.Value(
|
||||||
|
default="https://api.scaleway.com",
|
||||||
|
environ_name="DNS_PROVISIONING_API_URL",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
DNS_PROVISIONING_RESOURCE_ID = values.Value(
|
||||||
|
default=None,
|
||||||
|
environ_name="DNS_PROVISIONING_RESOURCE_ID",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
DNS_PROVISIONING_API_CREDENTIALS = values.Value(
|
||||||
|
default=None,
|
||||||
|
environ_name="DNS_PROVISIONING_API_CREDENTIALS",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
|
||||||
# Organizations
|
# Organizations
|
||||||
ORGANIZATION_REGISTRATION_ID_VALIDATORS = json.loads(
|
ORGANIZATION_REGISTRATION_ID_VALIDATORS = json.loads(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""Organization related plugins."""
|
"""Organization related plugins."""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -27,14 +29,8 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
|||||||
def get_organization_name_from_results(data, siret):
|
def get_organization_name_from_results(data, siret):
|
||||||
"""Return the organization name from the results of a SIRET search."""
|
"""Return the organization name from the results of a SIRET search."""
|
||||||
for result in data["results"]:
|
for result in data["results"]:
|
||||||
is_commune = (
|
|
||||||
result.get("nature_juridique") == "7210"
|
|
||||||
) # INSEE code for commune
|
|
||||||
for organization in result["matching_etablissements"]:
|
for organization in result["matching_etablissements"]:
|
||||||
if organization.get("siret") == siret:
|
if organization.get("siret") == siret:
|
||||||
if is_commune:
|
|
||||||
return organization["libelle_commune"].title()
|
|
||||||
|
|
||||||
store_signs = organization.get("liste_enseignes") or []
|
store_signs = organization.get("liste_enseignes") or []
|
||||||
if store_signs:
|
if store_signs:
|
||||||
return store_signs[0].title()
|
return store_signs[0].title()
|
||||||
@@ -76,3 +72,107 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
|||||||
organization.name = name
|
organization.name = name
|
||||||
organization.save(update_fields=["name", "updated_at"])
|
organization.save(update_fields=["name", "updated_at"])
|
||||||
logger.info("Organization %s name updated to %s", organization, name)
|
logger.info("Organization %s name updated to %s", organization, name)
|
||||||
|
|
||||||
|
class ApiCall:
|
||||||
|
"""Encapsulates a call to an external API"""
|
||||||
|
|
||||||
|
method: str = "GET"
|
||||||
|
base: str = ""
|
||||||
|
url: str = ""
|
||||||
|
params: dict = {}
|
||||||
|
headers: dict = {}
|
||||||
|
response = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
"""Call the specified API endpoint with supplied parameters and record response"""
|
||||||
|
if self.method == "POST":
|
||||||
|
self.response = requests.request(
|
||||||
|
method=self.method,
|
||||||
|
url=f"{self.base}/{self.url}",
|
||||||
|
json=self.params,
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CommuneCreation(BaseOrganizationPlugin):
|
||||||
|
"""
|
||||||
|
This plugin handles setup tasks for French communes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_api_url = "https://recherche-entreprises.api.gouv.fr/search?q={siret}"
|
||||||
|
|
||||||
|
def get_organization_name_from_results(self, data, siret):
|
||||||
|
"""Return the organization name from the results of a SIRET search."""
|
||||||
|
for result in data["results"]:
|
||||||
|
nature = "nature_juridique"
|
||||||
|
commune = nature in result and result[nature] == "7210"
|
||||||
|
for organization in result["matching_etablissements"]:
|
||||||
|
if organization.get("siret") == siret:
|
||||||
|
if commune:
|
||||||
|
return organization["libelle_commune"].title()
|
||||||
|
|
||||||
|
logger.warning("No organization name found for SIRET %s", siret)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def complete_commune_creation(self, name: str) -> ApiCall:
|
||||||
|
"""Specify the tasks to be completed after a commune is created."""
|
||||||
|
inputs = {"name": name.lower()}
|
||||||
|
|
||||||
|
create_zone = ApiCall()
|
||||||
|
create_zone.method = "POST"
|
||||||
|
create_zone.base = "https://api.scaleway.com"
|
||||||
|
create_zone.url = "/domain/v2beta1/dns-zones"
|
||||||
|
create_zone.params = {
|
||||||
|
"project_id": settings.DNS_PROVISIONING_RESOURCE_ID,
|
||||||
|
"domain": "collectivite.fr",
|
||||||
|
"subdomain": inputs["name"],
|
||||||
|
}
|
||||||
|
create_zone.headers = {
|
||||||
|
"X-Auth-Token": settings.DNS_PROVISIONING_API_CREDENTIALS
|
||||||
|
}
|
||||||
|
|
||||||
|
create_domain = ApiCall()
|
||||||
|
create_domain.method = "POST"
|
||||||
|
create_domain.base = settings.MAIL_PROVISIONING_API_URL
|
||||||
|
create_domain.url = "/domains"
|
||||||
|
create_domain.params = {
|
||||||
|
"name": inputs["name"],
|
||||||
|
"delivery": "virtual",
|
||||||
|
"features": ["webmail"],
|
||||||
|
"context_name": inputs["name"],
|
||||||
|
}
|
||||||
|
create_domain.headers = {
|
||||||
|
"Authorization": f"Basic: {settings.MAIL_PROVISIONING_API_CREDENTIALS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return [create_zone, create_domain]
|
||||||
|
|
||||||
|
def run_after_create(self, organization):
|
||||||
|
"""After creating an organization, update the organization name."""
|
||||||
|
if not organization.registration_id_list:
|
||||||
|
# No registration ID to convert...
|
||||||
|
return
|
||||||
|
|
||||||
|
# In the nominal case, there is only one registration ID because
|
||||||
|
# the organization as been created from it.
|
||||||
|
try:
|
||||||
|
# Retry logic as the API may be rate limited
|
||||||
|
s = requests.Session()
|
||||||
|
retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[429])
|
||||||
|
s.mount("https://", HTTPAdapter(max_retries=retries))
|
||||||
|
|
||||||
|
siret = organization.registration_id_list[0]
|
||||||
|
response = s.get(self._api_url.format(siret=siret), timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
name = self.get_organization_name_from_results(data, siret)
|
||||||
|
if not name:
|
||||||
|
return
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
logger.exception("%s: Unable to fetch organization name from SIRET", exc)
|
||||||
|
return
|
||||||
|
|
||||||
|
organization.name = name
|
||||||
|
organization.save(update_fields=["name", "updated_at"])
|
||||||
|
logger.info("Organization %s name updated to %s", organization, name)
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""Tests for the CommuneCreation plugin."""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import responses
|
||||||
|
|
||||||
|
from plugins.organizations import ApiCall, CommuneCreation
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
def test_extract_name_from_org_data_when_commune():
|
||||||
|
"""Test the name is extracted correctly for a French commune."""
|
||||||
|
data = {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"nom_complet": "COMMUNE DE VARZY",
|
||||||
|
"nom_raison_sociale": "COMMUNE DE VARZY",
|
||||||
|
"siege": {
|
||||||
|
"libelle_commune": "VARZY",
|
||||||
|
"liste_enseignes": ["MAIRIE"],
|
||||||
|
"siret": "21580304000017",
|
||||||
|
},
|
||||||
|
"nature_juridique": "7210",
|
||||||
|
"matching_etablissements": [
|
||||||
|
{
|
||||||
|
"siret": "21580304000017",
|
||||||
|
"libelle_commune": "VARZY",
|
||||||
|
"liste_enseignes": ["MAIRIE"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin = CommuneCreation()
|
||||||
|
name = plugin.get_organization_name_from_results(data, "21580304000017")
|
||||||
|
assert name == "Varzy"
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_call_execution():
|
||||||
|
"""Test that calling execute() faithfully executes the API call"""
|
||||||
|
task = ApiCall()
|
||||||
|
task.method = "POST"
|
||||||
|
task.base = "https://some_host"
|
||||||
|
task.url = "some_url"
|
||||||
|
task.params = {"some_key": "some_value"}
|
||||||
|
task.headers = {"Some-Header": "Some-Header-Value"}
|
||||||
|
|
||||||
|
with responses.RequestsMock() as rsps:
|
||||||
|
rsps.add(
|
||||||
|
rsps.POST,
|
||||||
|
url="https://some_host/some_url",
|
||||||
|
body='{"some_key": "some_value"}',
|
||||||
|
content_type="application/json",
|
||||||
|
headers={"Some-Header": "Some-Header-Value"},
|
||||||
|
)
|
||||||
|
|
||||||
|
task.execute()
|
||||||
|
|
||||||
|
|
||||||
|
def test_tasks_on_commune_creation_include_zone_creation():
|
||||||
|
"""Test the first task in commune creation: creating the DNS sub-zone"""
|
||||||
|
plugin = CommuneCreation()
|
||||||
|
name = "Varzy"
|
||||||
|
|
||||||
|
tasks = plugin.complete_commune_creation(name)
|
||||||
|
|
||||||
|
assert tasks[0].base == "https://api.scaleway.com"
|
||||||
|
assert tasks[0].url == "/domain/v2beta1/dns-zones"
|
||||||
|
assert tasks[0].method == "POST"
|
||||||
|
assert tasks[0].params == {
|
||||||
|
"project_id": settings.DNS_PROVISIONING_RESOURCE_ID,
|
||||||
|
"domain": "collectivite.fr",
|
||||||
|
"subdomain": "varzy",
|
||||||
|
}
|
||||||
|
assert tasks[0].headers["X-Auth-Token"] == settings.DNS_PROVISIONING_API_CREDENTIALS
|
||||||
|
|
||||||
|
|
||||||
|
def test_tasks_on_commune_creation_include_dimail_domain_creation():
|
||||||
|
"""Test the second task in commune creation: creating the domain in Dimail"""
|
||||||
|
plugin = CommuneCreation()
|
||||||
|
name = "Merlaut"
|
||||||
|
|
||||||
|
tasks = plugin.complete_commune_creation(name)
|
||||||
|
|
||||||
|
assert tasks[1].base == settings.MAIL_PROVISIONING_API_URL
|
||||||
|
assert tasks[1].url == "/domains"
|
||||||
|
assert tasks[1].method == "POST"
|
||||||
|
assert tasks[1].params == {
|
||||||
|
"name": "merlaut",
|
||||||
|
"delivery": "virtual",
|
||||||
|
"features": ["webmail"],
|
||||||
|
"context_name": "merlaut",
|
||||||
|
}
|
||||||
|
assert (
|
||||||
|
tasks[1].headers["Authorization"]
|
||||||
|
== f"Basic: {settings.MAIL_PROVISIONING_API_CREDENTIALS}"
|
||||||
|
)
|
||||||
@@ -139,37 +139,6 @@ def test_organization_plugins_run_after_create_name_already_set(
|
|||||||
assert organization.name == "Magic WOW"
|
assert organization.name == "Magic WOW"
|
||||||
|
|
||||||
|
|
||||||
def test_extract_name_from_org_data_when_commune(
|
|
||||||
organization_plugins_settings,
|
|
||||||
):
|
|
||||||
"""Test the name is extracted correctly for a French commune."""
|
|
||||||
data = {
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"nom_complet": "COMMUNE DE VARZY",
|
|
||||||
"nom_raison_sociale": "COMMUNE DE VARZY",
|
|
||||||
"siege": {
|
|
||||||
"libelle_commune": "VARZY",
|
|
||||||
"liste_enseignes": ["MAIRIE"],
|
|
||||||
"siret": "21580304000017",
|
|
||||||
},
|
|
||||||
"nature_juridique": "7210",
|
|
||||||
"matching_etablissements": [
|
|
||||||
{
|
|
||||||
"siret": "21580304000017",
|
|
||||||
"libelle_commune": "VARZY",
|
|
||||||
"liste_enseignes": ["MAIRIE"],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin = NameFromSiretOrganizationPlugin()
|
|
||||||
name = plugin.get_organization_name_from_results(data, "21580304000017")
|
|
||||||
assert name == "Varzy"
|
|
||||||
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_organization_plugins_run_after_create_no_list_enseignes(
|
def test_organization_plugins_run_after_create_no_list_enseignes(
|
||||||
organization_plugins_settings,
|
organization_plugins_settings,
|
||||||
|
|||||||
Reference in New Issue
Block a user