api/ants: check existing appointments before creating/deleting (#83558)
gitea/ants-hub/pipeline/head This commit looks good Details

This commit is contained in:
Benjamin Dauvergne 2023-11-15 22:44:46 +01:00
parent dcb622a48e
commit 0d4155ed3d
2 changed files with 231 additions and 14 deletions

View File

@ -4,8 +4,9 @@ import logging
import re
import requests
from django.db import OperationalError
from django.db.models import F, Q
from django.db.transaction import atomic
from django.db.transaction import atomic, set_rollback
from ants_hub.data.models import Config, RendezVous
from ants_hub.timezone import localtime, now
@ -93,6 +94,27 @@ class APIDoublon:
raise ANTSError('response is not a dict', data)
return data
def status(self, application_id):
return self.statuses([application_id]).get(application_id, {})
def existing_rdv(self, rdv):
identifiant_predemande = rdv.identifiant_predemande.upper()
appointments = self.status(identifiant_predemande).get('appointments', [])
ref = [
('meeting_point', rdv.lieu.nom),
('appointment_date', localtime(rdv.date).strftime('%Y-%m-%dT%H:%M:%S')),
]
matching_appointments = []
for appointment in appointments:
for key, value in ref:
if appointment[key] != value:
break
else:
matching_appointments.append(appointment)
return matching_appointments
def push_rdv(rdv):
identifiant_predemande = rdv.identifiant_predemande.upper()
@ -103,10 +125,39 @@ def push_rdv(rdv):
return True
api_doublon = APIDoublon()
if rdv.canceled is None:
return api_doublon.create_rdv(rdv)
delete = False
create = False
existing = api_doublon.existing_rdv(rdv)
if rdv.canceled:
action = 'deleted'
if existing:
delete = True
else:
return api_doublon.delete_rdv(rdv)
action = 'created'
if len(existing) > 1:
delete = True
create = True
elif existing:
if existing[0]['management_url'] != rdv.gestion_url:
# L'URL de gestion a changé, on supprime le RdV existant
delete = True
create = True
else:
return True
else:
create = True
success = True
if delete:
success = success and api_doublon.delete_rdv(rdv)
if create:
success = success and api_doublon.create_rdv(rdv)
return success and (delete or create) and action
def get_rdvs_to_upload():
@ -120,17 +171,23 @@ def upload_rdvs():
rdvs = get_rdvs_to_upload()
start = now()
for rdv in rdvs.distinct():
with atomic():
try:
rdv = rdvs.select_for_update(of=('self',)).get(pk=rdv.pk)
except RendezVous.DoesNotExist:
continue
try:
if push_rdv(rdv):
logger.info('pushed rdv(%s) of lieu %s', rdv, rdv.lieu)
try:
with atomic():
try:
rdv = rdvs.select_for_update(of=('self',)).get(pk=rdv.pk)
except RendezVous.DoesNotExist:
continue
try:
RendezVous.objects.filter(pk=rdv.pk).update(last_upload=start)
except ANTSError as e:
logger.warning('unable to push rdv(%s) of lieu %s: %r', rdv, rdv.lieu, e)
result = push_rdv(rdv)
if result:
logger.info(f'{result} rdv(%s) of lieu %s', rdv, rdv.lieu)
continue
except ANTSError as e:
logger.warning('unable to push rdv(%s) of lieu %s: %r', rdv, rdv.lieu, e)
set_rollback(True)
except OperationalError:
pass
def get_status_of_predemandes(identifiant_predemandes: list):

View File

@ -368,6 +368,25 @@ class TestAPIV2Push:
assert RendezVous.objects.filter(canceled__isnull=True).count() == 1
assert RendezVous.objects.filter(last_upload__isnull=True).count() == 1
responses.add(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={
'ABCD123456': {
'appointments': [
{
'meeting_point': 'Mairie de pouetpouet',
'appointment_date': '2023-04-03T12:15:00',
}
]
}
},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
post_response = responses.add(
responses.POST,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
@ -418,6 +437,25 @@ class TestAPIV2Push:
RendezVous.objects.update(canceled=now(), last_update=now())
responses.replace(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={
'ABCD123456': {
'appointments': [
{
'meeting_point': 'Mairie',
'appointment_date': '2023-04-03T12:15:00',
}
]
}
},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
call_command('upload-rdvs')
assert post_response.call_count == 1
@ -473,6 +511,15 @@ class TestAPIV2Push:
],
)
def test_post_error(self, response_kwargs, db, freezer):
responses.add(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
post_response = responses.add(
responses.POST,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
@ -521,6 +568,24 @@ class TestAPIV2Push:
],
)
def test_delete_error(self, response_kwargs, db, freezer):
responses.add(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={
'ABCD123456': {
'appointments': [
{
'meeting_point': 'Mairie',
'appointment_date': '2023-04-03T12:15:00',
}
]
}
},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
post_response = responses.add(
responses.DELETE,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
@ -537,6 +602,101 @@ class TestAPIV2Push:
assert RendezVous.objects.count() == 1
assert RendezVous.objects.filter(last_upload__isnull=True).count() == 1
@responses.activate
def test_delete_duplicates(self, db, freezer):
assert RendezVous.objects.count() == 1
assert RendezVous.objects.filter(canceled__isnull=True).count() == 1
assert RendezVous.objects.filter(last_upload__isnull=True).count() == 1
responses.add(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={
'ABCD123456': {
'appointments': [
{
'meeting_point': 'Mairie',
'appointment_date': '2023-04-03T12:15:00',
},
{
'meeting_point': 'Mairie',
'appointment_date': '2023-04-03T12:15:00',
},
]
}
},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
post_response = responses.add(
responses.POST,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
json={'success': True},
status=200,
match=[responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'})],
)
delete_response = responses.add(
responses.DELETE,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
json={'rowcount': 1},
status=200,
match=[responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'})],
)
call_command('upload-rdvs')
assert post_response.call_count == 1
assert delete_response.call_count == 1
@responses.activate
def test_delete_management_url_changed(self, db, freezer):
assert RendezVous.objects.count() == 1
assert RendezVous.objects.filter(canceled__isnull=True).count() == 1
assert RendezVous.objects.filter(last_upload__isnull=True).count() == 1
responses.add(
responses.GET,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/status',
json={
'ABCD123456': {
'appointments': [
{
'meeting_point': 'Mairie',
'appointment_date': '2023-04-03T12:15:00',
'management_url': 'xyz',
},
]
}
},
status=200,
match=[
responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'}),
],
)
post_response = responses.add(
responses.POST,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
json={'success': True},
status=200,
match=[responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'})],
)
delete_response = responses.add(
responses.DELETE,
'https://api-coordination.rendezvouspasseport.ants.gouv.fr/api/appointments',
json={'rowcount': 1},
status=200,
match=[responses.matchers.header_matcher({'x-rdv-opt-auth-token': 'abcd'})],
)
call_command('upload-rdvs')
assert post_response.call_count == 1
assert delete_response.call_count == 1
class TestGetStatusOfPredemandes:
@pytest.fixture(autouse=True)