add Isere ENS connector (#50019)
This commit is contained in:
parent
2e30f462c9
commit
a4bc4c2335
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2021-01-19 13:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('base', '0028_rename_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IsereENS',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(unique=True, verbose_name='Identifier')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
('basic_auth_username', models.CharField(blank=True, max_length=128, verbose_name='Basic authentication username')),
|
||||
('basic_auth_password', models.CharField(blank=True, max_length=128, verbose_name='Basic authentication password')),
|
||||
('client_certificate', models.FileField(blank=True, null=True, upload_to='', verbose_name='TLS client certificate')),
|
||||
('trusted_certificate_authorities', models.FileField(blank=True, null=True, upload_to='', verbose_name='TLS trusted CAs')),
|
||||
('verify_cert', models.BooleanField(default=True, verbose_name='TLS verify certificates')),
|
||||
('http_proxy', models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy')),
|
||||
('base_url', models.URLField(help_text='Base API URL (before /api/...)', verbose_name='Webservice Base URL')),
|
||||
('token', models.CharField(max_length=128, verbose_name='Access token')),
|
||||
('users', models.ManyToManyField(blank=True, related_name='_isereens_users_+', related_query_name='+', to='base.ApiUser')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Espaces naturels sensibles du CD38',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,414 @@
|
|||
# passerelle - uniform access to multiple data sources and services
|
||||
# Copyright (C) 2021 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
import datetime
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from passerelle.utils.conversion import simplify
|
||||
from passerelle.utils.jsonresponse import APIError
|
||||
from passerelle.utils.api import endpoint
|
||||
from passerelle.base.models import BaseResource, HTTPResource
|
||||
|
||||
|
||||
SITE_BOOKING_SCHOOL_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "ENS site/booking/school",
|
||||
"description": "",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"code",
|
||||
"status",
|
||||
"beneficiary_id",
|
||||
"beneficiary_first_name",
|
||||
"beneficiary_last_name",
|
||||
"beneficiary_email",
|
||||
"beneficiary_phone",
|
||||
"beneficiary_cellphone",
|
||||
"entity_id",
|
||||
"entity_name",
|
||||
"entity_type",
|
||||
"project",
|
||||
"site",
|
||||
"applicant",
|
||||
"public",
|
||||
"date",
|
||||
"participants",
|
||||
"morning",
|
||||
"lunch",
|
||||
"afternoon",
|
||||
"pmr",
|
||||
"grade_levels",
|
||||
"animator",
|
||||
],
|
||||
"properties": OrderedDict(
|
||||
{
|
||||
"code": {
|
||||
"description": "booking code",
|
||||
"type": "string",
|
||||
},
|
||||
"status": {
|
||||
"description": "booking status",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_id": {
|
||||
"description": "beneficiary id",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_first_name": {
|
||||
"description": "beneficiary first name",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_last_name": {
|
||||
"description": "beneficiary last name",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_email": {
|
||||
"description": "beneficiary email",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_phone": {
|
||||
"description": "beneficiary phone number",
|
||||
"type": "string",
|
||||
},
|
||||
"beneficiary_cellphone": {
|
||||
"description": "beneficiary cell phone number",
|
||||
"type": "string",
|
||||
},
|
||||
"entity_id": {
|
||||
"description": "entity/school id (UAI/RNE)",
|
||||
"type": "string",
|
||||
},
|
||||
"entity_name": {
|
||||
"description": "entity/school name",
|
||||
"type": "string",
|
||||
},
|
||||
"entity_type": {
|
||||
"description": "entity/school type",
|
||||
"type": "string",
|
||||
},
|
||||
"project": {
|
||||
"description": "project code",
|
||||
"type": "string",
|
||||
},
|
||||
"site": {
|
||||
"description": "site id (code)",
|
||||
"type": "string",
|
||||
},
|
||||
"applicant": {
|
||||
"description": "applicant",
|
||||
"type": "string",
|
||||
},
|
||||
"public": {
|
||||
"description": "public",
|
||||
"type": "string",
|
||||
},
|
||||
"date": {
|
||||
"description": "booking date (format: YYYY-MM-DD)",
|
||||
"type": "string",
|
||||
},
|
||||
"participants": {
|
||||
"description": "number of participants",
|
||||
"type": "string",
|
||||
"pattern": "[0-9]+",
|
||||
},
|
||||
"morning": {
|
||||
"description": "morning booking",
|
||||
"type": "boolean",
|
||||
},
|
||||
"lunch": {
|
||||
"description": "lunch booking",
|
||||
"type": "boolean",
|
||||
},
|
||||
"afternoon": {
|
||||
"description": "afternoon booking",
|
||||
"type": "boolean",
|
||||
},
|
||||
"pmr": {
|
||||
"description": "PMR",
|
||||
"type": "boolean",
|
||||
},
|
||||
"grade_levels": {
|
||||
"description": "grade levels",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "level",
|
||||
},
|
||||
},
|
||||
"animator": {
|
||||
"description": "animator id",
|
||||
"type": "string",
|
||||
"pattern": "[0-9]+",
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class IsereENS(BaseResource, HTTPResource):
|
||||
category = _("Business Process Connectors")
|
||||
|
||||
base_url = models.URLField(
|
||||
verbose_name=_("Webservice Base URL"),
|
||||
help_text=_("Base API URL (before /api/...)"),
|
||||
)
|
||||
token = models.CharField(verbose_name=_("Access token"), max_length=128)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Espaces naturels sensibles de l'Isère")
|
||||
|
||||
def request(self, endpoint, params=None, json=None):
|
||||
url = urlparse.urljoin(self.base_url, endpoint)
|
||||
headers = {"token": self.token}
|
||||
if json is not None:
|
||||
response = self.requests.post(
|
||||
url, params=params, json=json, headers=headers
|
||||
)
|
||||
else:
|
||||
response = self.requests.get(url, params=params, headers=headers)
|
||||
|
||||
if response.status_code // 100 != 2:
|
||||
try:
|
||||
json_content = response.json()
|
||||
except ValueError:
|
||||
json_content = None
|
||||
raise APIError(
|
||||
"error status:%s %r, content:%r"
|
||||
% (response.status_code, response.reason, response.content[:1024]),
|
||||
data={
|
||||
"status_code": response.status_code,
|
||||
"json_content": json_content,
|
||||
},
|
||||
)
|
||||
|
||||
if response.status_code == 204: # 204 No Content
|
||||
raise APIError("abnormal empty response")
|
||||
|
||||
try:
|
||||
return response.json()
|
||||
except ValueError:
|
||||
raise APIError("invalid JSON in response: %r" % response.content[:1024])
|
||||
|
||||
@endpoint(
|
||||
name="sites",
|
||||
description=_("Sites"),
|
||||
display_order=1,
|
||||
perm="can_access",
|
||||
parameters={
|
||||
"q": {"description": _("Search text in name field")},
|
||||
"id": {
|
||||
"description": _("Returns site with code=id"),
|
||||
},
|
||||
"kind": {
|
||||
"description": _("Select sites by bind: school_group or social"),
|
||||
},
|
||||
},
|
||||
)
|
||||
def sites(self, request, q=None, id=None, kind=None):
|
||||
if id is not None:
|
||||
site = self.request("api/1.0.0/site/" + id)
|
||||
site["id"] = site["code"]
|
||||
site["text"] = "%(name)s (%(city)s)" % site
|
||||
sites = [site]
|
||||
else:
|
||||
cache_key = "isere-ens-sites-%d" % self.id
|
||||
sites = cache.get(cache_key)
|
||||
if not sites:
|
||||
sites = self.request("api/1.0.0/site")
|
||||
for site in sites:
|
||||
site["id"] = site["code"]
|
||||
site["text"] = "%(name)s (%(city)s)" % site
|
||||
cache.set(cache_key, sites, 300)
|
||||
if kind is not None:
|
||||
sites = [site for site in sites if site.get(kind)]
|
||||
if q is not None:
|
||||
q = simplify(q)
|
||||
sites = [site for site in sites if q in simplify(site["text"])]
|
||||
return {"data": sites}
|
||||
|
||||
@endpoint(
|
||||
name="animators",
|
||||
description=_("Animators"),
|
||||
display_order=2,
|
||||
perm="can_access",
|
||||
parameters={
|
||||
"q": {"description": _("Search text in name field")},
|
||||
"id": {
|
||||
"description": _("Returns animator number id"),
|
||||
},
|
||||
},
|
||||
)
|
||||
def animators(self, request, q=None, id=None):
|
||||
cache_key = "isere-ens-animators-%d" % self.id
|
||||
animators = cache.get(cache_key)
|
||||
if not animators:
|
||||
animators = self.request("api/1.0.0/schoolAnimator")
|
||||
for animator in animators:
|
||||
animator["id"] = str(animator["id"])
|
||||
animator["text"] = (
|
||||
"%(first_name)s %(last_name)s <%(email)s> (%(entity)s)" % animator
|
||||
)
|
||||
cache.set(cache_key, animators, 300)
|
||||
if id is not None:
|
||||
animators = [animator for animator in animators if animator["id"] == id]
|
||||
if q is not None:
|
||||
q = simplify(q)
|
||||
animators = [
|
||||
animator for animator in animators if q in simplify(animator["text"])
|
||||
]
|
||||
return {"data": animators}
|
||||
|
||||
@endpoint(
|
||||
name="site-calendar",
|
||||
description=_("Available bookings for a site"),
|
||||
display_order=3,
|
||||
perm="can_access",
|
||||
parameters={
|
||||
"site": {"description": _("Site code (aka id)")},
|
||||
"participants": {
|
||||
"description": _("Number of participants"),
|
||||
},
|
||||
"start_date": {
|
||||
"description": _(
|
||||
"First date of the calendar (format: YYYY-MM-DD, default: today)"
|
||||
),
|
||||
},
|
||||
"end_date": {
|
||||
"description": _(
|
||||
"Last date of the calendar (format: YYYY-MM-DD, default: start_date + 92 days)"
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
def site_calendar(
|
||||
self, request, site, participants="1", start_date=None, end_date=None
|
||||
):
|
||||
if start_date:
|
||||
try:
|
||||
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
raise APIError(
|
||||
"bad start_date format (%s), should be YYYY-MM-DD" % start_date,
|
||||
http_status=400,
|
||||
)
|
||||
else:
|
||||
start_date = datetime.date.today()
|
||||
if end_date:
|
||||
try:
|
||||
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
raise APIError(
|
||||
"bad end_date format (%s), should be YYYY-MM-DD" % end_date,
|
||||
http_status=400,
|
||||
)
|
||||
else:
|
||||
end_date = start_date + datetime.timedelta(days=92)
|
||||
|
||||
params = {
|
||||
"site": site,
|
||||
"participants": participants,
|
||||
"start_date": start_date.strftime("%Y-%m-%d"),
|
||||
"end_date": end_date.strftime("%Y-%m-%d"),
|
||||
}
|
||||
dates = self.request("api/1.0.0/site/" + site + "/calendar", params=params)
|
||||
|
||||
for date in dates:
|
||||
date["id"] = site + ":" + date["date"]
|
||||
date["site"] = site
|
||||
date_ = datetime.datetime.strptime(date["date"], "%Y-%m-%d").date()
|
||||
date["date_format"] = date_format(date_, format="DATE_FORMAT")
|
||||
date["disabled"] = False
|
||||
date["color"] = "green"
|
||||
for period in ("morning", "afternoon"):
|
||||
if date[period] == "COMPLETE":
|
||||
date["color"] = "orange"
|
||||
date["%s_status" % period] = _("Complete")
|
||||
else:
|
||||
date["%s_status" % period] = _("Available")
|
||||
if date["morning"] == "COMPLETE" and date["afternoon"] == "COMPLETE":
|
||||
date["disabled"] = True
|
||||
date["color"] = "red"
|
||||
if date["lunch"] == "CLOSE":
|
||||
date["lunch_status"] = _("Complete")
|
||||
else:
|
||||
date["lunch_status"] = _("Available")
|
||||
date["details"] = _(
|
||||
"Morning (%(morning_status)s), Lunch (%(lunch_status)s), Afternoon (%(afternoon_status)s)"
|
||||
% date
|
||||
)
|
||||
date["text"] = "%(date_format)s - %(details)s" % date
|
||||
return {"data": dates}
|
||||
|
||||
@endpoint(
|
||||
name="site-booking",
|
||||
description=_("Book a site for an entity (school)"),
|
||||
display_order=4,
|
||||
perm="can_access",
|
||||
methods=["post"],
|
||||
post={
|
||||
"request_body": {
|
||||
"schema": {
|
||||
"application/json": SITE_BOOKING_SCHOOL_SCHEMA,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
def site_booking(self, request, post_data):
|
||||
payload = {
|
||||
"code": post_data["code"],
|
||||
"status": post_data["status"],
|
||||
"beneficiary": {
|
||||
"id": post_data["beneficiary_id"],
|
||||
"firstName": post_data["beneficiary_first_name"],
|
||||
"lastName": post_data["beneficiary_last_name"],
|
||||
"email": post_data["beneficiary_email"],
|
||||
"phone": post_data["beneficiary_phone"],
|
||||
"cellphone": post_data["beneficiary_cellphone"],
|
||||
},
|
||||
"entity": {
|
||||
"id": post_data["entity_id"],
|
||||
"name": post_data["entity_name"],
|
||||
"type": post_data["entity_type"],
|
||||
},
|
||||
"booking": {
|
||||
"projectCode": post_data.get("project"),
|
||||
"siteCode": post_data["site"],
|
||||
"applicant": post_data["applicant"],
|
||||
"public": post_data["public"],
|
||||
"bookingDate": post_data["date"],
|
||||
"participants": int(post_data["participants"]),
|
||||
"morning": post_data["morning"],
|
||||
"lunch": post_data["lunch"],
|
||||
"afternoon": post_data["afternoon"],
|
||||
"pmr": post_data["pmr"],
|
||||
"gradeLevels": post_data["grade_levels"],
|
||||
"schoolAnimator": int(post_data["animator"]),
|
||||
},
|
||||
}
|
||||
booking = self.request("api/1.0.0/booking", json=payload)
|
||||
if not isinstance(booking, dict):
|
||||
raise APIError("response is not a dict", data=booking)
|
||||
if not "status" in booking:
|
||||
raise APIError("no status in response", data=booking)
|
||||
if booking["status"] not in ("BOOKING", "OVERBOOKING"):
|
||||
raise APIError("booking status is %s" % booking["status"], data=booking)
|
||||
return {"data": booking}
|
|
@ -22,6 +22,7 @@ INSTALLED_APPS += (
|
|||
'passerelle.contrib.grandlyon_streetsections',
|
||||
'passerelle.contrib.greco',
|
||||
'passerelle.contrib.grenoble_gru',
|
||||
'passerelle.contrib.isere_ens',
|
||||
'passerelle.contrib.iparapheur',
|
||||
'passerelle.contrib.iws',
|
||||
'passerelle.contrib.lille_urban_card',
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Passerelle - uniform access to data and services
|
||||
# Copyright (C) 2021 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; exclude even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a.deepcopy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
import utils
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from passerelle.contrib.isere_ens.models import IsereENS
|
||||
from passerelle.utils.jsonresponse import APIError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup(db):
|
||||
return utils.setup_access_rights(
|
||||
IsereENS.objects.create(
|
||||
slug="test", base_url="https://ens38.example.net/", token="toktok"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
SITES_RESPONSE = """[
|
||||
{
|
||||
"name": "Save - étangs de la Serre",
|
||||
"code": "SD29a",
|
||||
"city": "Arandon-Passins - Courtenay",
|
||||
"school_group": true,
|
||||
"social": true
|
||||
},
|
||||
{
|
||||
"name": "Save - étangs de Passins",
|
||||
"code": "SD29b",
|
||||
"city": "Arandon-Passins",
|
||||
"school_group": true,
|
||||
"social": true
|
||||
},
|
||||
{
|
||||
"name": "Save - lac de Save",
|
||||
"code": "SD29c",
|
||||
"city": "Arandon-Passins",
|
||||
"school_group": true,
|
||||
"social": false
|
||||
}
|
||||
]"""
|
||||
|
||||
SD29B_RESPONSE = """{
|
||||
"name": "Save - étangs de Passins",
|
||||
"code": "SD29b",
|
||||
"type": "DEPARTMENTAL_ENS",
|
||||
"city": "Arandon-Passins",
|
||||
"natural_environment": "Etangs, tourbières, mares, forêts, rivière, pelouses sèches",
|
||||
"lunch_equipment": null,
|
||||
"pmr_access": "NO",
|
||||
"school_group": true,
|
||||
"social": true,
|
||||
"user_informations": "<p>Un album jeunesse a été réalisé sur l'ENS de la Save. Il permet aux enfants de s'approprier le site en amont d'une sortie.</p>",
|
||||
"dogs": "LEASH",
|
||||
"educational_equipments": null,
|
||||
"eps_regulation": null,
|
||||
"main_manager": {
|
||||
"first_name": "Jo",
|
||||
"last_name": "Smith",
|
||||
"managing_entity": "Département de l'Isère",
|
||||
"main_phone": "01 23 45 67 89",
|
||||
"email": "jo.smith@example.net"
|
||||
}
|
||||
}"""
|
||||
|
||||
SITE_404_RESPONSE = """{
|
||||
"user_message": "Impossible de trouver le site",
|
||||
"message": "Site not found with code SD29x"
|
||||
}"""
|
||||
|
||||
ANIMATORS_RESPONSE = """[
|
||||
{
|
||||
"id": 1,
|
||||
"first_name": "Francis",
|
||||
"last_name": "Kuntz",
|
||||
"email": "fk@mail.grd",
|
||||
"phone": "123",
|
||||
"entity": "Association Nature Morte"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"first_name": "Michael",
|
||||
"last_name": "Kael",
|
||||
"email": "mk@mail.grd",
|
||||
"phone": "456",
|
||||
"entity": "Association Porte de l'Enfer"
|
||||
}
|
||||
]"""
|
||||
|
||||
SITE_CALENDAR_RESPONSE = """[
|
||||
{
|
||||
"date": "2020-01-21",
|
||||
"morning": "AVAILABLE",
|
||||
"lunch": "CLOSE",
|
||||
"afternoon": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"date": "2020-01-22",
|
||||
"morning": "AVAILABLE",
|
||||
"lunch": "OPEN",
|
||||
"afternoon": "COMPLETE"
|
||||
},
|
||||
{
|
||||
"date": "2020-01-23",
|
||||
"morning": "COMPLETE",
|
||||
"lunch": "CLOSE",
|
||||
"afternoon": "COMPLETE"
|
||||
}
|
||||
]"""
|
||||
|
||||
BOOK_RESPONSE = """{
|
||||
"status": "BOOKING"
|
||||
}"""
|
||||
BOOK_RESPONSE_OVERBOOKING = """{
|
||||
"status": "OVERBOOKING"
|
||||
}"""
|
||||
BOOK_RESPONSE_REFUSED = """{
|
||||
"status": "REFUSED"
|
||||
}"""
|
||||
|
||||
|
||||
@mock.patch("passerelle.utils.Request.get")
|
||||
def test_get_sites(mocked_get, app, setup):
|
||||
mocked_get.return_value = utils.FakedResponse(
|
||||
content=SITES_RESPONSE, status_code=200
|
||||
)
|
||||
endpoint = reverse(
|
||||
"generic-endpoint",
|
||||
kwargs={"connector": "isere-ens", "slug": setup.slug, "endpoint": "sites"},
|
||||
)
|
||||
response = app.get(endpoint)
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site")
|
||||
assert mocked_get.call_args[1]["headers"]["token"] == "toktok"
|
||||
assert mocked_get.call_count == 1
|
||||
assert "data" in response.json
|
||||
assert response.json["err"] == 0
|
||||
for item in response.json["data"]:
|
||||
assert "id" in item
|
||||
assert "text" in item
|
||||
assert "city" in item
|
||||
assert "code" in item
|
||||
|
||||
# test cache system
|
||||
response = app.get(endpoint)
|
||||
assert mocked_get.call_count == 1
|
||||
|
||||
response = app.get(endpoint + "?q=etangs")
|
||||
assert len(response.json["data"]) == 2
|
||||
response = app.get(endpoint + "?q=CourTe")
|
||||
assert len(response.json["data"]) == 1
|
||||
response = app.get(endpoint + "?kind=social")
|
||||
assert len(response.json["data"]) == 2
|
||||
|
||||
mocked_get.return_value = utils.FakedResponse(
|
||||
content=SD29B_RESPONSE, status_code=200
|
||||
)
|
||||
response = app.get(endpoint + "?id=SD29b")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/SD29b")
|
||||
assert len(response.json["data"]) == 1
|
||||
assert response.json["data"][0]["id"] == "SD29b"
|
||||
assert response.json["data"][0]["dogs"] == "LEASH"
|
||||
|
||||
# bad response for ENS API
|
||||
mocked_get.return_value = utils.FakedResponse(
|
||||
content=SITE_404_RESPONSE, status_code=404
|
||||
)
|
||||
response = app.get(endpoint + "?id=SD29x")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/SD29x")
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"].startswith("error status:404")
|
||||
assert response.json["data"]["status_code"] == 404
|
||||
assert (
|
||||
response.json["data"]["json_content"]["message"]
|
||||
== "Site not found with code SD29x"
|
||||
)
|
||||
mocked_get.return_value = utils.FakedResponse(content="crash", status_code=500)
|
||||
response = app.get(endpoint + "?id=foo500")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/foo500")
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_desc"].startswith("error status:500")
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["data"]["status_code"] == 500
|
||||
assert response.json["data"]["json_content"] is None
|
||||
mocked_get.return_value = utils.FakedResponse(content=None, status_code=204)
|
||||
response = app.get(endpoint + "?id=foo204")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/foo204")
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"] == "abnormal empty response"
|
||||
mocked_get.return_value = utils.FakedResponse(content="not json", status_code=200)
|
||||
response = app.get(endpoint + "?id=foo")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/foo")
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"].startswith("invalid JSON in response:")
|
||||
|
||||
|
||||
@mock.patch("passerelle.utils.Request.get")
|
||||
def test_get_animators(mocked_get, app, setup):
|
||||
mocked_get.return_value = utils.FakedResponse(
|
||||
content=ANIMATORS_RESPONSE, status_code=200
|
||||
)
|
||||
endpoint = reverse(
|
||||
"generic-endpoint",
|
||||
kwargs={"connector": "isere-ens", "slug": setup.slug, "endpoint": "animators"},
|
||||
)
|
||||
response = app.get(endpoint)
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/schoolAnimator")
|
||||
assert mocked_get.call_count == 1
|
||||
assert "data" in response.json
|
||||
assert response.json["err"] == 0
|
||||
for item in response.json["data"]:
|
||||
assert "id" in item
|
||||
assert "text" in item
|
||||
assert "first_name" in item
|
||||
assert "email" in item
|
||||
|
||||
# test cache system
|
||||
response = app.get(endpoint)
|
||||
assert mocked_get.call_count == 1
|
||||
|
||||
response = app.get(endpoint + "?q=Kael")
|
||||
assert len(response.json["data"]) == 1
|
||||
response = app.get(endpoint + "?q=association")
|
||||
assert len(response.json["data"]) == 2
|
||||
response = app.get(endpoint + "?q=mail.grd")
|
||||
assert len(response.json["data"]) == 2
|
||||
response = app.get(endpoint + "?id=2")
|
||||
assert len(response.json["data"]) == 1
|
||||
assert response.json["data"][0]["first_name"] == "Michael"
|
||||
|
||||
|
||||
@mock.patch("passerelle.utils.Request.get")
|
||||
def test_get_site_calendar(mocked_get, app, setup, freezer):
|
||||
freezer.move_to("2021-01-21 12:00:00")
|
||||
mocked_get.return_value = utils.FakedResponse(
|
||||
content=SITE_CALENDAR_RESPONSE, status_code=200
|
||||
)
|
||||
endpoint = reverse(
|
||||
"generic-endpoint",
|
||||
kwargs={
|
||||
"connector": "isere-ens",
|
||||
"slug": setup.slug,
|
||||
"endpoint": "site-calendar",
|
||||
},
|
||||
)
|
||||
response = app.get(endpoint + "?site=SD29b")
|
||||
assert mocked_get.call_args[0][0].endswith("api/1.0.0/site/SD29b/calendar")
|
||||
assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-21"
|
||||
assert mocked_get.call_args[1]["params"]["end_date"] == "2021-04-23"
|
||||
assert response.json["err"] == 0
|
||||
assert len(response.json["data"]) == 3
|
||||
response = app.get(endpoint + "?site=SD29b&start_date=2021-01-22")
|
||||
assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-22"
|
||||
assert mocked_get.call_args[1]["params"]["end_date"] == "2021-04-24"
|
||||
assert response.json["err"] == 0
|
||||
response = app.get(
|
||||
endpoint + "?site=SD29b&start_date=2021-01-22&end_date=2021-01-30"
|
||||
)
|
||||
assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-22"
|
||||
assert mocked_get.call_args[1]["params"]["end_date"] == "2021-01-30"
|
||||
assert response.json["err"] == 0
|
||||
response = app.get(endpoint + "?site=SD29b&start_date=foo", status=400)
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert (
|
||||
response.json["err_desc"] == "bad start_date format (foo), should be YYYY-MM-DD"
|
||||
)
|
||||
response = app.get(endpoint + "?site=SD29b&end_date=bar", status=400)
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert (
|
||||
response.json["err_desc"] == "bad end_date format (bar), should be YYYY-MM-DD"
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("passerelle.utils.Request.post")
|
||||
def test_post_book(mocked_post, app, setup):
|
||||
mocked_post.return_value = utils.FakedResponse(
|
||||
content=BOOK_RESPONSE, status_code=200
|
||||
)
|
||||
endpoint = reverse(
|
||||
"generic-endpoint",
|
||||
kwargs={
|
||||
"connector": "isere-ens",
|
||||
"slug": setup.slug,
|
||||
"endpoint": "site-booking",
|
||||
},
|
||||
)
|
||||
book = {
|
||||
"code": "resa",
|
||||
"status": "OK",
|
||||
"beneficiary_id": "42",
|
||||
"beneficiary_first_name": "Foo",
|
||||
"beneficiary_last_name": "Bar",
|
||||
"beneficiary_email": "foobar@example.net",
|
||||
"beneficiary_phone": "9876",
|
||||
"beneficiary_cellphone": "06",
|
||||
"entity_id": "38420D",
|
||||
"entity_name": "Ecole FooBar",
|
||||
"entity_type": "school",
|
||||
"project": "Publik",
|
||||
"site": "SD29b",
|
||||
"applicant": "app",
|
||||
"public": "GS",
|
||||
"date": "2020-01-22",
|
||||
"participants": "50",
|
||||
"morning": True,
|
||||
"lunch": False,
|
||||
"afternoon": False,
|
||||
"pmr": True,
|
||||
"grade_levels": ["CP", "CE1"],
|
||||
"animator": "42",
|
||||
}
|
||||
response = app.post_json(endpoint, params=book)
|
||||
assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
|
||||
assert mocked_post.call_count == 1
|
||||
assert response.json["err"] == 0
|
||||
assert response.json["data"]["status"] == "BOOKING"
|
||||
|
||||
mocked_post.return_value = utils.FakedResponse(
|
||||
content=BOOK_RESPONSE_OVERBOOKING, status_code=200
|
||||
)
|
||||
response = app.post_json(endpoint, params=book)
|
||||
assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
|
||||
assert mocked_post.call_count == 2
|
||||
assert response.json["err"] == 0
|
||||
assert response.json["data"]["status"] == "OVERBOOKING"
|
||||
|
||||
mocked_post.return_value = utils.FakedResponse(
|
||||
content=BOOK_RESPONSE_REFUSED, status_code=200
|
||||
)
|
||||
response = app.post_json(endpoint, params=book)
|
||||
assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
|
||||
assert mocked_post.call_count == 3
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"] == "booking status is REFUSED"
|
||||
assert response.json["data"]["status"] == "REFUSED"
|
||||
|
||||
mocked_post.return_value = utils.FakedResponse(
|
||||
content="""["not", "a", "dict"]""", status_code=200
|
||||
)
|
||||
response = app.post_json(endpoint, params=book)
|
||||
assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
|
||||
assert mocked_post.call_count == 4
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"] == "response is not a dict"
|
||||
assert response.json["data"] == ["not", "a", "dict"]
|
||||
|
||||
mocked_post.return_value = utils.FakedResponse(
|
||||
content="""{"foo": "bar"}""", status_code=200
|
||||
)
|
||||
response = app.post_json(endpoint, params=book)
|
||||
assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
|
||||
assert mocked_post.call_count == 5
|
||||
assert response.json["err"] == 1
|
||||
assert response.json["err_class"].endswith("APIError")
|
||||
assert response.json["err_desc"] == "no status in response"
|
||||
assert response.json["data"] == {"foo": "bar"}
|
Loading…
Reference in New Issue