Compare commits

..

23 Commits

Author SHA1 Message Date
Benjamin Dauvergne ee1baf8b50 base_adresse: add indexes on on geographic models names (#66694)
gitea/passerelle/pipeline/head This commit looks good Details
2024-01-15 16:22:17 +01:00
Benjamin Dauvergne 37536df9d1 toulouse_maelis: report update errors after a delay (#84953)
gitea/passerelle/pipeline/head This commit looks good Details
Also try to update as soon as possible after an error during daily
updates.
2024-01-15 15:48:01 +01:00
Thomas NOËL 514c3f1995 scrib: always send numBac if present (#85548)
gitea/passerelle/pipeline/head This commit looks good Details
2024-01-13 00:49:28 +01:00
Thomas NOËL dd87030c50 scrib: return with err 1 on error codeErreur/codeRetour responses (#85447)
gitea/passerelle/pipeline/head This commit looks good Details
2024-01-09 16:37:43 +01:00
Nicolas Roche 104f96b61c toulouse-maelis: parse datetime on activity birth dates (#85263)
gitea/passerelle/pipeline/head This commit looks good Details
2024-01-04 11:49:14 +01:00
Thomas NOËL 6f3516297e settings: disable nantes_scrib app by default (#85087)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-22 15:31:48 +01:00
Thomas NOËL 43e2d9222a jenkinsfile: use parallel for pytest/vitest/pylint (#85071)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-22 14:30:02 +01:00
Thomas NOËL d0ecf8af77 update translations
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-22 12:21:00 +01:00
Thomas NOËL 31a0828d6d scrib: use abstract SOAP model (#84895)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-22 11:07:17 +01:00
Thomas NOËL 020a402a96 soap: create an AbstractSOAPConnector (#84895) 2023-12-22 11:07:17 +01:00
Nicolas Roche 0010095146 toulouse-maelis: detail get-recurrent-week errors (#84975)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-21 09:53:08 +01:00
Corentin Sechet b377b87d5d js: fix vitest version used (#84940)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-19 17:54:41 +01:00
Thomas NOËL 52886c216c update translations
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-19 09:54:06 +01:00
Thomas NOËL b8fc9716a4 add contrib.nantes_scrib connector (#83360)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-19 09:46:11 +01:00
Thomas NOËL 40c3c6affb matrix42: add a generic object/action POST endpoint (#84636)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-18 16:43:47 +01:00
Nicolas Roche ec8dd0a43c toulouse-maelis: hide standard unit from main catalog items (#84760)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-18 10:08:19 +01:00
Nicolas Roche 5ad847df95 misc: remove copyright line from footer (#84811)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-15 17:48:02 +01:00
Nicolas Roche 8e6f61ceb7 toulouse-maelis: allow to link providing accents (#84370)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-15 10:47:45 +01:00
Nicolas Roche 13fe6411eb toulouse-maelis: do not apply distinct on activity referential (#84576)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-15 10:47:13 +01:00
Thomas NOËL 6101988404 soap: fix initial migration (#84761)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-15 10:40:32 +01:00
Serghei Mihai 8b3b8edfda api_particulier: increase api_key size (#84671)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-13 13:17:24 +01:00
Pierre Ducroquet 52981183ff jenkins: double test processes (#84680)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-13 11:14:47 +01:00
Benjamin Dauvergne afab9d49a1 astregs: fix typo in find_tiers_by_rib (#84512)
gitea/passerelle/pipeline/head This commit looks good Details
2023-12-09 09:31:31 +01:00
26 changed files with 1495 additions and 97 deletions

39
Jenkinsfile vendored
View File

@ -11,19 +11,34 @@ pipeline {
RAND_TEST = "${Math.abs(new Random().nextInt(max+1))}"
}
stages {
stage('Unit Tests') {
steps {
sh "NUMPROCESSES=6 RAND_TEST=${env.RAND_TEST} tox -rv"
}
post {
always {
script {
utils = new Utils()
utils.publish_coverage('coverage.xml')
utils.publish_coverage_native('index.html')
utils.publish_pylint('pylint.out')
stage('Tests (in parallel)') {
failFast true
parallel {
stage('Unit Tests (pytest)') {
steps {
sh "NUMPROCESSES=12 RAND_TEST=${env.RAND_TEST} tox -rv"
}
post {
always {
script {
utils = new Utils()
utils.publish_coverage('coverage.xml')
utils.publish_coverage_native('index.html')
utils.publish_pylint('pylint.out')
}
mergeJunitResults()
}
}
}
stage('Unit Tests (vitest)') {
steps {
sh "NUMPROCESSES=12 RAND_TEST=${env.RAND_TEST} tox -rv -e vitest"
}
}
stage('Linter (pylint)') {
steps {
sh "NUMPROCESSES=12 RAND_TEST=${env.RAND_TEST} tox -rv -e pylint"
}
mergeJunitResults()
}
}
}

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.18 on 2023-12-13 10:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api_particulier', '0006_api_key_length_1024'),
]
operations = [
migrations.AlterField(
model_name='apiparticulier',
name='api_key',
field=models.CharField(blank=True, default='', max_length=2048, verbose_name='API key'),
),
]

View File

@ -63,7 +63,7 @@ class APIParticulier(BaseResource):
choices=[(key, platform['label']) for key, platform in PLATFORMS.items()],
)
api_key = models.CharField(max_length=1024, default='', blank=True, verbose_name=_('API key'))
api_key = models.CharField(max_length=2048, default='', blank=True, verbose_name=_('API key'))
log_requests_errors = False

View File

@ -891,7 +891,7 @@ class AstreGS(BaseResource):
for item in r.liste.EnregRechercheTiersReturn:
tiers_data = serialize_object(item)
tiers_data['id'] = tiers_data['N']
tiers_data['text'] = '%{Nom_Enregistrement}s (%{N}s)'.format(**tiers_data)
tiers_data['text'] = '{Nom_Enregistrement} ({N})'.format(**tiers_data)
data.append(tiers_data)
return {'data': data}

View File

@ -38,6 +38,8 @@ class Matrix42(BaseResource, HTTPResource):
log_requests_errors = False
_category_ordering = [_('Fragments'), _('Objects')]
class Meta:
verbose_name = _('Matrix42 Public API')
@ -59,7 +61,9 @@ class Matrix42(BaseResource, HTTPResource):
'Authorization': 'Bearer ' + token['RawToken'],
}
def request(self, uri, params=None, json=None, headers=None, method=None, dict_response=True):
def request(
self, uri, params=None, json=None, headers=None, method=None, dict_response=True, allow_empty=False
):
if headers is None:
headers = self.get_authorization_headers()
if method is None:
@ -67,6 +71,10 @@ class Matrix42(BaseResource, HTTPResource):
url = urljoin(self.base_url, uri)
response = self.requests.request(method, url, params=params, json=json, headers=headers)
status_code = response.status_code
if status_code == 204:
if allow_empty:
return None
raise APIError('Matrix42 returned an empty response (status 204)')
try:
response = response.json()
except ValueError:
@ -210,3 +218,24 @@ class Matrix42(BaseResource, HTTPResource):
):
uri = urljoin(self.base_url, 'data/objects/%s' % ciname)
return {'data': self.request(uri, json=post_data, dict_response=False)}
@endpoint(
name='generic',
display_category=_('Generic: ticket, task, change, problem, …'),
methods=['post'],
pattern=r'^(?P<object_type>.+)/(?P<action>.+)$',
example_pattern='ticket/Transform',
post={
'description': _('Perform action on object(s)'),
'request_body': {'schema': {'application/json': DICT_SCHEMA}},
},
)
def generic(
self,
request,
object_type,
action,
post_data,
):
uri = urljoin(self.base_url, '%s/%s' % (object_type, action))
return {'data': self.request(uri, json=post_data, dict_response=False, allow_empty=True)}

View File

@ -59,11 +59,11 @@ class Migration(migrations.Migration):
),
(
'zeep_strict',
models.BooleanField(default=True, verbose_name='Be strict with returned XML'),
models.BooleanField(default=False, verbose_name='Be strict with returned XML'),
),
(
'zeep_xsd_ignore_sequence_order',
models.BooleanField(default=False, verbose_name='Ignore sequence order'),
models.BooleanField(default=True, verbose_name='Ignore sequence order'),
),
(
'zeep_wsse_username',

View File

@ -33,7 +33,7 @@ from passerelle.utils.json import unflatten
from passerelle.utils.jsonresponse import APIError
class SOAPConnector(BaseResource, HTTPResource):
class AbstractSOAPConnector(BaseResource, HTTPResource):
wsdl_url = models.URLField(
max_length=400, verbose_name=_('WSDL URL'), help_text=_('URL of the WSDL file')
)
@ -50,7 +50,8 @@ class SOAPConnector(BaseResource, HTTPResource):
category = _('Business Process Connectors')
class Meta:
verbose_name = _('SOAP connector')
abstract = True
verbose_name = _('Abstract SOAP connector')
def clean(self):
if (not self.client_certificate or self.client_certificate._committed) and (
@ -247,3 +248,8 @@ class SOAPConnector(BaseResource, HTTPResource):
return schema
return t2s(xsd_type)
class SOAPConnector(AbstractSOAPConnector):
class Meta:
verbose_name = _('SOAP connector')

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.18 on 2023-12-14 16:45
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('soap', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Scrib',
fields=[
(
'soapconnector_ptr',
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='soap.soapconnector',
),
),
],
options={
'verbose_name': 'Scrib SOAP API',
},
bases=('soap.soapconnector',),
),
]

View File

@ -0,0 +1,92 @@
# Generated by Django 3.2.18 on 2023-12-19 11:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0030_resourcelog_base_resour_appname_298cbc_idx'),
('nantes_scrib', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SOAPScrib',
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(blank=True, default=True, verbose_name='TLS verify certificates'),
),
(
'http_proxy',
models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy'),
),
(
'wsdl_url',
models.URLField(
help_text='URL of the WSDL file', max_length=400, verbose_name='WSDL URL'
),
),
(
'zeep_strict',
models.BooleanField(default=False, verbose_name='Be strict with returned XML'),
),
(
'zeep_xsd_ignore_sequence_order',
models.BooleanField(default=True, verbose_name='Ignore sequence order'),
),
(
'zeep_wsse_username',
models.CharField(blank=True, default='', max_length=256, verbose_name='WSSE Username'),
),
(
'zeep_wsse_password',
models.CharField(blank=True, default='', max_length=256, verbose_name='WSSE Password'),
),
(
'users',
models.ManyToManyField(
blank=True,
related_name='_nantes_scrib_soapscrib_users_+',
related_query_name='+',
to='base.ApiUser',
),
),
],
options={
'verbose_name': 'Scrib SOAP API',
},
),
migrations.DeleteModel(
name='Scrib',
),
]

View File

@ -0,0 +1,95 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 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 django.utils.translation import gettext_lazy as _
from passerelle.apps.soap.models import AbstractSOAPConnector
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
DICT_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'additionalProperties': True,
'unflatten': True,
}
def lax_int(value):
try:
return int(value)
except (ValueError, TypeError):
return None
class SOAPScrib(AbstractSOAPConnector):
category = _('Business Process Connectors')
class Meta:
verbose_name = _('Scrib SOAP API')
@endpoint(
name='depot',
description=_('Clean payload and sent it to method/depot'),
display_order=-1,
display_category=_('Publik compatible API'),
methods=['post'],
post={'request_body': {'schema': {'application/json': DICT_SCHEMA}}},
)
def depot(self, request, post_data):
# clean "bac" list: keep only bacCollecteXXX if bacCollecteXXX is True
if not isinstance(post_data.get('demandeBacs', {}).get('bac'), list):
raise APIError('missing demandeBacs/bac list', http_status=400)
demandeBacs = post_data['demandeBacs']
cleaned_bacs = []
for bac in demandeBacs['bac']:
if not isinstance(bac, dict):
continue
cleaned_bac = {}
for key in [k for k in bac if k.startswith('bac')]:
if bac[key] not in (True, 'true', 'True', 1, '1'):
continue
cleaned_bac[key] = 'true'
numkey = 'numBac' + key[3:]
num = (bac.get(numkey, '') or '').strip()
if num:
cleaned_bac[numkey] = num
if cleaned_bac:
cleaned_bacs.append(cleaned_bac)
demandeBacs['bac'] = cleaned_bacs
# remove numeroVoie if empty or non-integer (xsd:int/xsd:short in wsdl)
for key in ('numeroVoie', 'nombrePersonnesFoyer'):
if key in demandeBacs:
demandeBacs[key] = lax_int(demandeBacs[key])
if demandeBacs[key] is None: # was not an integer, remove it
del demandeBacs[key]
# remove raisonDemande and typeDegradation if empty (or not string)
for key in ('raisonDemande', 'typeDegradation'):
if key in demandeBacs:
if isinstance(demandeBacs[key], str) and demandeBacs[key].strip():
continue
del demandeBacs[key]
response = self.method(request, method_name='depot', post_data=post_data)
codeRetour = response['data'].get('codeRetour') # should be True
codeErreur = response['data'].get('codeErreur') # should be empty
if not codeRetour or codeErreur:
response['err'] = 1
return response

View File

@ -34,7 +34,7 @@ from django.template.loader import render_to_string as render_template_to_string
from django.utils import dateformat
from django.utils.dateparse import parse_date, parse_datetime
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.timezone import localtime, now
from requests.exceptions import RequestException
from zeep.helpers import serialize_object
from zeep.wsse.username import UsernameToken
@ -43,7 +43,7 @@ from passerelle.apps.base_adresse.models import CityModel
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.base.signature import sign_url
from passerelle.utils.api import endpoint
from passerelle.utils.conversion import simplify
from passerelle.utils.conversion import normalize, simplify
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.soap import SOAPFault, SOAPServiceUnreachable
from passerelle.utils.templates import render_to_string
@ -291,14 +291,62 @@ class ToulouseMaelis(BaseResource, HTTPResource):
town.item_data['zip_and_text'] = '%s %s' % (zipcode, town.item_text)
town.save()
def daily(self):
super().daily()
self.update_referentials()
@classmethod
def earliest_updated(cls, qs):
timestamp = qs.aggregate(updated=models.Min('updated'))['updated']
if timestamp:
timestamp = localtime(timestamp)
return timestamp
def get_activity_referentials_earliest_timestamp(self):
referentials = self.referential.filter(
referential_name__in=['Activity', 'ActivityNatureType', 'Direct', 'Service']
)
return self.earliest_updated(referentials)
def is_activity_referentials_too_old(self):
timestamp = self.get_activity_referentials_earliest_timestamp()
return not timestamp or (now() - timestamp >= datetime.timedelta(hours=6))
def every5min(self):
self.update_activity_referentials()
try:
self.update_activity_referentials()
except UpdateError as e:
if self.is_activity_referentials_too_old():
self.logger.error('Erreur sur la mise à jour (depuis plus de 6 heures): %s' % e)
else:
self.logger.warning('Erreur sur la mise à jour: %s' % e)
else:
self.logger.info('Réferentiels mis à jour.')
def get_referentials_earliest_timestamp(self):
referentials = self.referential.exclude(
referential_name__in=['Activity', 'ActivityNatureType', 'Direct', 'Service']
)
return self.earliest_updated(referentials)
def should_update_referentials(self):
timestamp = self.get_referentials_earliest_timestamp()
# try to update every 24 hours, then do it as long as it fails
if not timestamp:
return True
if now() - timestamp >= datetime.timedelta(hours=24):
return True
# also after a failure do it prefentially after midnight
if (now() - timestamp > datetime.timedelta(hours=7)) and (
datetime.time(0, 0) < localtime(now()).time() < datetime.time(6, 0)
):
return True
return False
def is_referentials_too_old(self):
# referentials are tool old if any entry is more than 30 hours old
timestamp = self.get_referentials_earliest_timestamp()
return not timestamp or (now() - timestamp >= datetime.timedelta(hours=30))
def update_referentials(self):
if not self.should_update_referentials():
return
try:
self.update_family_referentials()
self.update_site_referentials()
@ -307,7 +355,10 @@ class ToulouseMaelis(BaseResource, HTTPResource):
# merge zip codes from base adresse into town referential
self.merge_zipcodes()
except UpdateError as e:
self.logger.warning('Erreur sur la mise à jour: %s' % e)
if self.is_referentials_too_old():
self.logger.error('Erreur sur la mise à jour (depuis plus de 30 heures): %s' % e)
else:
self.logger.warning('Erreur sur la mise à jour: %s' % e)
else:
self.logger.info('Réferentiels mis à jour.')
@ -349,9 +400,11 @@ class ToulouseMaelis(BaseResource, HTTPResource):
subscription.trigger()
def hourly(self):
super().hourly()
self.notify_invoices_paid()
self.cancel_basket_invoices()
self.trigger_subscriptions_cron()
self.update_referentials()
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
if id is not None:
@ -1165,8 +1218,8 @@ class ToulouseMaelis(BaseResource, HTTPResource):
)
continue
if (
response[rlg]['firstname'] == post_data['firstname'].upper()
and response[rlg]['lastname'] == post_data['lastname'].upper()
response[rlg]['firstname'] == normalize(post_data['firstname']).upper()
and response[rlg]['lastname'] == normalize(post_data['lastname']).upper()
and response[rlg]['birth']['dateBirth'].strftime('%Y-%m-%d') == post_data['dateBirth']
):
break
@ -2505,12 +2558,20 @@ class ToulouseMaelis(BaseResource, HTTPResource):
response = self.call('Activity', 'getPersonScheduleList', **payload)
date_min_prev = None
warning_msg = "No week calendar for activity '%s' on %s" % (
activity_id,
ref_date.strftime(utils.json_date_format),
)
recurrent_week = []
day_names = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
for result_data in response or []:
for schedule in result_data['activityScheduleList']:
if schedule['activity']['idAct'] != activity_id or not schedule.get('weeklyCalendar'):
continue
warning_msg = "No open day for activity '%s' on %s" % (
activity_id,
ref_date.strftime(utils.json_date_format),
)
units = []
for item in schedule['unitScheduleList']:
key = item['unit']['calendarLetter']
@ -2524,6 +2585,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
day_num = item['dayNum']
day_str = day_names[day_num - 1]
for key, value in units:
warning_msg = None
recurrent_week.append(
{
'id': '%s-%s' % (day_num, key),
@ -2533,15 +2595,12 @@ class ToulouseMaelis(BaseResource, HTTPResource):
'text': '%s %s' % (day_str, value),
}
)
if not recurrent_week:
raise APIError(
'No week calendar for activity %s on %s'
% (activity_id, ref_date.strftime(utils.json_date_format))
)
return {
'data': recurrent_week,
'meta': {'date_min_prev': date_min_prev.strftime(utils.json_date_format)},
}
if date_min_prev:
date_min_prev = date_min_prev.strftime(utils.json_date_format)
if warning_msg and "'None'" not in warning_msg:
# do not raise on 'None' activity
raise APIError(warning_msg, log_error=True)
return {'data': recurrent_week, 'meta': {'date_min_prev': date_min_prev, 'warning_msg': warning_msg}}
@endpoint(
display_category='Réservation',
@ -2860,7 +2919,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
]
data = []
for activity in self.get_referential('Activity'):
for activity in self.get_referential('Activity', distinct=False):
activity_type = activity['activityPortail'].get('activityType')
activity_nature = activity_type.get('natureSpec') if activity_type else None
if not activity_nature or activity_nature['code'] not in self.get_loisir_nature_codes():
@ -2883,17 +2942,21 @@ class ToulouseMaelis(BaseResource, HTTPResource):
update_criterias_order_field(criterias, ['service', 'nature', 'type', 'day'])
many_units = len(activity['unitPortailList']) > 1
for unit in activity.pop('unitPortailList'):
unit['id'] = unit['idUnit']
unit['text'] = unit['libelle']
unit_text_item = ''
if many_units:
unit_text_item = ' (%s)' % unit['text']
criterias['public']['data'] = {}
start_dob = unit['birthDateStart']
end_dob = unit['birthDateEnd']
if start_dob:
start_dob = parse_date(start_dob)
start_dob = parse_datetime(start_dob)
if end_dob:
end_dob = parse_date(end_dob)
end_dob = parse_datetime(end_dob)
for key, value in utils.get_public_criterias(datetime.date.today(), start_dob, end_dob):
add_criteria('public', key, value)
@ -2909,7 +2972,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
data.append(
{
'id': '%s-%s-%s' % (activity['id'], unit['id'], place['id']),
'text': '%s, %s, %s' % (activity['text'], unit['text'], place['text']),
'text': '%s%s, %s' % (activity['text'], unit_text_item, place['text']),
'activity': activity,
'unit': unit,
'place': place,

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Passerelle 0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-22 21:24+0100\n"
"POT-Creation-Date: 2023-12-22 12:20+0100\n"
"PO-Revision-Date: 2023-11-22 21:24+0100\n"
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
"Language: fr\n"
@ -110,11 +110,11 @@ msgstr "Jeton daccès aux API (API token)"
#: contrib/grenoble_gru/models.py contrib/isere_ens/models.py
#: contrib/isere_esrh/models.py contrib/iws/models.py
#: contrib/lille_kimoce/models.py contrib/mdph13/models.py
#: contrib/planitech/models.py contrib/rsa13/models.py
#: contrib/sigerly/models.py contrib/solis_afi_mss/models.py
#: contrib/solis_apa/models.py contrib/teamnet_axel/models.py
#: contrib/toulouse_axel/models.py contrib/toulouse_foederis/models.py
#: contrib/toulouse_smart/models.py
#: contrib/nantes_scrib/models.py contrib/planitech/models.py
#: contrib/rsa13/models.py contrib/sigerly/models.py
#: contrib/solis_afi_mss/models.py contrib/solis_apa/models.py
#: contrib/teamnet_axel/models.py contrib/toulouse_axel/models.py
#: contrib/toulouse_foederis/models.py contrib/toulouse_smart/models.py
msgid "Business Process Connectors"
msgstr "Connecteurs métiers"
@ -125,7 +125,7 @@ msgstr "Adullact Pastell"
#: apps/adullact_pastell/models.py
msgid "API token or authentication username and password should be defined."
msgstr ""
"Le jeton daccès aux API ou l'identifiant et mot de passe doivent être "
"Le jeton daccès aux API ou lidentifiant et mot de passe doivent être "
"définis."
#: apps/adullact_pastell/models.py
@ -629,7 +629,7 @@ msgstr "Statut des demandes"
#: apps/astech/models.py
msgid "Base API URL (example: https://astech-symphonie.com/app.php/)"
msgstr ""
"URL de base de l'API (par exemple : https://astech-symphonie.com/app.php/)"
"URL de base de lAPI (par exemple : https://astech-symphonie.com/app.php/)"
#: apps/astech/models.py
msgid "Connection code"
@ -3055,6 +3055,14 @@ msgstr "Récupérer le statut dune demande"
msgid "Get submission decree"
msgstr "Récupérer lacte dune demande"
#: apps/matrix42/models.py
msgid "Fragments"
msgstr "Fragments (fragments)"
#: apps/matrix42/models.py
msgid "Objects"
msgstr "Objets (objects)"
#: apps/matrix42/models.py
msgid "Matrix42 Public API"
msgstr "Matrix42 API Publique"
@ -3071,10 +3079,6 @@ msgstr "Jeton dauthentification"
msgid "Fragment Query"
msgstr "Requête sur fragments"
#: apps/matrix42/models.py
msgid "Fragments"
msgstr "Fragments (fragments)"
#: apps/matrix42/models.py
msgid "Technical name of the Data Definition"
msgstr "Nom technique de la définition de donnée"
@ -3116,14 +3120,18 @@ msgstr "Récupérer le fragment complet pour cet ID"
msgid "Get an object"
msgstr "Récupérer un objet"
#: apps/matrix42/models.py
msgid "Objects"
msgstr "Objets (objects)"
#: apps/matrix42/models.py
msgid "Create an new object"
msgstr "Créer un objet"
#: apps/matrix42/models.py
msgid "Generic: ticket, task, change, problem, …"
msgstr "Générique : ticket, task, change, problem, …"
#: apps/matrix42/models.py
msgid "Perform action on object(s)"
msgstr "Exécuter laction sur le(s) objet(s)"
#: apps/mdel/models.py
msgid "SFTP server for outgoing files"
msgstr "Serveur SFTP pour les fichiers sortants"
@ -3896,7 +3904,7 @@ msgstr ""
#: apps/pdf/models.py
msgid "Return possible values for PDF's combo or list form fields"
msgstr ""
"Renvoie les valeurs possibles pour un champ liste à choix d'un formulaire PDF"
"Renvoie les valeurs possibles pour un champ liste à choix dun formulaire PDF"
#: apps/pdf/models.py
msgid "Identifier of the field"
@ -4192,7 +4200,7 @@ msgstr "Obtenir les informations sur laccès au lecteur de code QR"
#: apps/qrcode/models.py
msgid "Open a QRCode reader page."
msgstr "Obtenir l'URL du lecteur de code QR"
msgstr "Obtenir lURL du lecteur de code QR"
#: apps/qrcode/models.py
msgid "Last modification"
@ -4220,7 +4228,7 @@ msgstr "De"
#: apps/qrcode/templates/qrcode/qrcode-reader.html
msgid "This QR code isn't supported by this application."
msgstr "Ce code QR n'est pas supporté par cette application."
msgstr "Ce code QR nest pas supporté par cette application."
#: apps/qrcode/templates/qrcode/qrcode-reader.html
msgid "Signature verification failed."
@ -4231,12 +4239,12 @@ msgid ""
"QR code reader isn\\'t supported on your platform. Please update your "
"browser."
msgstr ""
"Le lecteur de code QR n'est pas supporté sur votre plateforme. Essayez de "
"mettre à jour votre navigateur ou d'en changer."
"Le lecteur de code QR nest pas supporté sur votre plateforme. Essayez de "
"mettre à jour votre navigateur ou den changer."
#: apps/qrcode/templates/qrcode/qrcode-reader.html
msgid "QR code not yet valid"
msgstr "Le certificat n'est pas encore valide."
msgstr "Le certificat nest pas encore valide."
#: apps/qrcode/templates/qrcode/qrcode-reader.html sms/forms.py
msgid "To"
@ -4249,11 +4257,11 @@ msgstr "Code QR et certificat valide"
#: apps/qrcode/templates/qrcode/qrcode-reader.html
msgid "Reader isn't usable yet."
msgstr ""
"Vous n'êtes pas encore autorisé à lire les codes QR, veuillez attendre."
"Vous nêtes pas encore autorisé à lire les codes QR, veuillez attendre."
#: apps/qrcode/templates/qrcode/qrcode-reader.html
msgid "Reader has expired."
msgstr "La période d'autorisation de votre lecteur de code QR est terminée."
msgstr "La période dautorisation de votre lecteur de code QR est terminée."
#: apps/sector/models.py
msgid "all"
@ -4586,13 +4594,13 @@ msgstr "Obtenir la finition dun véhicule par numéro dimmatriculation"
#: apps/sivin/models.py
msgid "Get vehicle \"finition\" by registration plate, ordered by rangs"
msgstr ""
"Obtenir la finition théorique d'un véhicule par numéro d'immatriculation, "
"Obtenir la finition théorique dun véhicule par numéro dimmatriculation, "
"ordonnée par rang"
#: apps/sivin/models.py
msgid "Get vehicle theorical \"finition\" by registration plate"
msgstr ""
"Obtenir la finition théorique d'un véhicule par numéro d'immatriculation"
"Obtenir la finition théorique dun véhicule par numéro dimmatriculation"
#: apps/smsfactor/models.py apps/twilio/models.py
msgid "Auth Token"
@ -4636,13 +4644,17 @@ msgid "WSSE Password"
msgstr "Mot de passe WSSE"
#: apps/soap/models.py
msgid "SOAP connector"
msgstr "Connecteur SOAP"
msgid "Abstract SOAP connector"
msgstr "Connecteur SOAP (abstrait)"
#: apps/soap/models.py
msgid "Call a SOAP method"
msgstr "Appeler une méthode SOAP"
#: apps/soap/models.py
msgid "SOAP connector"
msgstr "Connecteur SOAP"
#: apps/solis/models.py
msgid "Solis API base URL"
msgstr "URL de base de lAPI Solis"
@ -5790,7 +5802,7 @@ msgstr "Récupérer les informations officielles"
#: contrib/isere_esrh/models.py
msgid "Official registration number"
msgstr "Numéro d'enregistrement officiel"
msgstr "Numéro denregistrement officiel"
#: contrib/isere_esrh/models.py
msgid "Public authority"
@ -5814,7 +5826,7 @@ msgstr ""
#: contrib/isere_esrh/models.py
msgid "Get job types"
msgstr "Lister les types d'emploi"
msgstr "Lister les types demploi"
#: contrib/iws/models.py
msgid "URL of SOAP operation endpoint"
@ -5988,6 +6000,18 @@ msgstr "Requête : "
msgid "Response : "
msgstr "Réponse : "
#: contrib/nantes_scrib/models.py
msgid "Scrib SOAP API"
msgstr "Scrib API SOAP"
#: contrib/nantes_scrib/models.py
msgid "Clean payload and sent it to method/depot"
msgstr "Nettoyer le contenu et lenvoyer à method/depot"
#: contrib/nantes_scrib/models.py
msgid "Publik compatible API"
msgstr "API compatible Publik"
#: contrib/planitech/models.py
msgid "Planitec API endpoint"
msgstr "Point daccès à lAPI Planitec"
@ -6767,8 +6791,8 @@ msgstr "Identifiant dun élément de la source de données « nationalite 
#: contrib/toulouse_foederis/models.py
msgid "Applicant end of working authorization, if nationality is 'other'."
msgstr ""
"Date de fin d'autorisation de travail du candidat, si sa nationalité est "
"'autre'."
"Date de fin dautorisation de travail du candidat, si sa nationalité est "
"« autre »."
#: contrib/toulouse_foederis/models.py
msgid "RQTH."
@ -6797,7 +6821,7 @@ msgstr "Date de fin de validité de la FIMO."
#: contrib/toulouse_foederis/models.py
msgid "ID of an element of the data source 'situation-actuelle'."
msgstr ""
"Identifiant d'une élément de la source de données 'situation actuelle'."
"Identifiant d'une élément de la source de données « situation actuelle »."
#: contrib/toulouse_foederis/models.py
msgid "Agent's collectivity"
@ -6854,7 +6878,7 @@ msgstr "Information complémentaires sur la candidature."
#: contrib/toulouse_foederis/models.py
msgid "ID of an element of the data source 'origine-candidature'."
msgstr ""
"Identifiant d'un élément de la source de données 'origine-candidature'."
"Identifiant d'un élément de la source de données « origine-candidature »."
#: contrib/toulouse_foederis/models.py
msgid "Precisions if 'origine' is 'other'."
@ -6866,7 +6890,7 @@ msgstr "Accord RGPD."
#: contrib/toulouse_foederis/models.py
msgid "Wanted job types"
msgstr "Types d'emploi souhaités"
msgstr "Types demploi souhaités"
#: contrib/toulouse_foederis/models.py
msgid "IDs of elements of the data source 'domaine-emploi'."
@ -6979,7 +7003,7 @@ msgstr "Créer une candidature"
#: contrib/toulouse_foederis/models.py
msgid "Couldn't recognize provided phone number."
msgstr "La valeur fournie n'est pas un numéro de téléphone."
msgstr "La valeur fournie nest pas un numéro de téléphone."
#: contrib/toulouse_foederis/models.py
msgid "Attach a file to an application."

View File

@ -194,11 +194,12 @@ INSTALLED_APPS = (
'gadjo',
)
# disable some applications for now
# disable (hide) some applications for now
PASSERELLE_APP_BDP_ENABLED = False
PASSERELLE_APP_GDC_ENABLED = False
PASSERELLE_APP_STRASBOURG_EU_ENABLED = False
PASSERELLE_APP_TOULOUSE_MAELIS_ENABLED = False
PASSERELLE_APP_NANTES_SCRIB_ENABLED = False
# mark some apps as legacy
PASSERELLE_APP_CLICRDV_LEGACY = True

View File

@ -2,7 +2,6 @@
{% load gadjo i18n static %}
{% block page-title %}Passerelle{% endblock %}
{% block site-title %}Passerelle{% endblock %}
{% block footer %}Passerelle — Copyright © Entr'ouvert{% endblock %}
{% block extrascripts %}
{{ block.super }}

View File

@ -3,4 +3,4 @@
pip install $*
nodeenv --prebuilt --python-virtualenv
source $VIRTUAL_ENV/bin/activate # source again to activate npm from env
npm install -g vitest happy-dom
npm install -g vitest@"<1.1.0" happy-dom

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://ws.scrib/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://ws.scrib/" name="DemandeBacWS">
<types>
<xsd:schema targetNamespace="http://ws.scrib/">
<xsd:element name="depot" type="tns:depot"/>
<xsd:element name="depotResponse" type="tns:depotResponse"/>
<xsd:complexType name="depot">
<xsd:sequence>
<xsd:element name="demandeBacs" type="tns:demandeBacWsDto" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="demandeBacWsDto">
<xsd:sequence>
<xsd:element name="appartement" type="xsd:string" minOccurs="0"/>
<xsd:element name="bac" type="tns:typeNumBacWsDto" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="batiment" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeCivel" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeCommune" type="xsd:string" minOccurs="0"/>
<xsd:element name="codePostal" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeSecteurTrisac" type="xsd:string" minOccurs="0"/>
<xsd:element name="commentaires" type="xsd:string" minOccurs="0"/>
<xsd:element name="commune" type="xsd:string" minOccurs="0"/>
<xsd:element name="complementVoie" type="xsd:string" minOccurs="0"/>
<xsd:element name="email" type="xsd:string" minOccurs="0"/>
<xsd:element name="etage" type="xsd:string" minOccurs="0"/>
<xsd:element name="jourCollecte" type="xsd:string" minOccurs="0"/>
<xsd:element name="libelleVoie" type="xsd:string" minOccurs="0"/>
<xsd:element name="mention" type="xsd:string" minOccurs="0"/>
<xsd:element name="natureDemande" type="tns:natureDemande" minOccurs="0"/>
<xsd:element name="natureDemandeur" type="tns:natureDemandeur" minOccurs="0"/>
<xsd:element name="nom" type="xsd:string" minOccurs="0"/>
<xsd:element name="nomAssociation" type="xsd:string" minOccurs="0"/>
<xsd:element name="nomCommercial" type="xsd:string" minOccurs="0"/>
<xsd:element name="nombrePersonnesFoyer" type="xsd:short" minOccurs="0"/>
<xsd:element name="numeroVoie" type="xsd:int" minOccurs="0"/>
<xsd:element name="prenom" type="xsd:string" minOccurs="0"/>
<xsd:element name="raisonDemande" type="tns:raisonDemande" minOccurs="0"/>
<xsd:element name="raisonSociale" type="xsd:string" minOccurs="0"/>
<xsd:element name="sigle" type="xsd:string" minOccurs="0"/>
<xsd:element name="telephone" type="xsd:string" minOccurs="0"/>
<xsd:element name="typeDegradation" type="tns:typeDegradation" minOccurs="0"/>
<xsd:element name="typeHabitat" type="tns:typeHabitat" minOccurs="0"/>
<xsd:element name="urlPublik" type="xsd:string" minOccurs="0"/>
<xsd:element name="usageBac" type="tns:usageBac" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="typeNumBacWsDto">
<xsd:sequence>
<xsd:element name="bacCollecteDechetsMenagers" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollectePapierCarton" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollecteSelective" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollecteVerre" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="numBacCollecteDechetsMenagers" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollectePapierCarton" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollecteSelective" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollecteVerre" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="depotResponse">
<xsd:sequence>
<xsd:element name="return" type="tns:demandeBacWsRetour" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="demandeBacWsRetour">
<xsd:sequence>
<xsd:element name="codeErreur" type="tns:codeErreur" minOccurs="0"/>
<xsd:element name="codeRetour" type="xsd:boolean"/>
<xsd:element name="messageErreur" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="natureDemande">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="DDOT"/>
<xsd:enumeration value="DREN"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="natureDemandeur">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="NPAR"/>
<xsd:enumeration value="NPRO"/>
<xsd:enumeration value="NASS"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="raisonDemande">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="BVOL"/>
<xsd:enumeration value="BDEG"/>
<xsd:enumeration value="BBRU"/>
<xsd:enumeration value="BAJU"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="typeDegradation">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="TDRO"/>
<xsd:enumeration value="TDCU"/>
<xsd:enumeration value="TDCO"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="typeHabitat">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="HCOL"/>
<xsd:enumeration value="HIND"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="usageBac">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="UPRO"/>
<xsd:enumeration value="UPRI"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="codeErreur">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ERR_TECH"/>
<xsd:enumeration value="ERR_FORMAT"/>
<xsd:enumeration value="ERR_FONC"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
</types>
<message name="depot">
<part name="parameters" element="tns:depot"/>
</message>
<message name="depotResponse">
<part name="parameters" element="tns:depotResponse"/>
</message>
<portType name="DemandeBacWS">
<operation name="depot">
<input wsam:Action="http://ws.scrib/DemandeBacWS/depotRequest" message="tns:depot"/>
<output wsam:Action="http://ws.scrib/DemandeBacWS/depotResponse" message="tns:depotResponse"/>
</operation>
</portType>
<binding name="DemandeBacWSPortBinding" type="tns:DemandeBacWS">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="depot">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="DemandeBacWS">
<port name="DemandeBacWSPort" binding="tns:DemandeBacWSPortBinding">
<soap:address location="http://scrib.example.net/scrib2/ws/demandeBac"/>
</port>
</service>
</definitions>

View File

@ -0,0 +1,466 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getPersonScheduleListResponse xmlns:ns2="activity.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com" xmlns:ns4="bean.persistence.school.ws.maelis.sigec.com">
<resultBean>
<personScheduleList>
<person>
<numPerson>261768</numPerson>
<lastname>NICO</lastname>
<firstname>BART</firstname>
</person>
<activityScheduleList>
<activity>
<idAct>A10049327682</idAct>
<libelle>RESTAURATION SCOLAIRE 22/23</libelle>
<activityType>
<code>RESTSCOL</code>
<libelle>Restauration scolaire</libelle>
<natureSpec>
<code>R</code>
<libelle>Restauration Scolaire</libelle>
</natureSpec>
</activityType>
</activity>
<weeklyCalendar>
<yearCalendar>2023</yearCalendar>
<dayWeekInfoList>
<dayNum>1</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>2</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>3</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>4</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>5</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>6</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>7</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
</weeklyCalendar>
<unitScheduleList>
<unit>
<idUnit>A10049355140</idUnit>
<libelle>PAI PANIER 22/23</libelle>
<calendarLetter>B</calendarLetter>
</unit>
<datePrevMin>2023-03-27T00:00:00+02:00</datePrevMin>
<dayInfoList>
<day>2023-04-01T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-02T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-03T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-04T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-05T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-06T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-07T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-08T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-09T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-10T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-11T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-12T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-13T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-14T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-15T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-16T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-17T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-18T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-19T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-20T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-21T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-22T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-23T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-24T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-25T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-26T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-27T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-28T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-29T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-30T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
</unitScheduleList>
<unitScheduleList>
<unit>
<idUnit>A10049327683</idUnit>
<libelle>RESTAURATION SCOLAIRE 22/23</libelle>
<calendarLetter>X</calendarLetter>
</unit>
<datePrevMin>2023-03-27T00:00:00+02:00</datePrevMin>
<dayInfoList>
<day>2023-04-01T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-02T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-03T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-04T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-05T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-06T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-07T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-08T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-09T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-10T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-11T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-12T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-13T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-14T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-15T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-16T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-17T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-18T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-19T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-20T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-21T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-22T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-23T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-24T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-25T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-26T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-27T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-28T00:00:00+02:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-04-29T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-04-30T00:00:00+02:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
</unitScheduleList>
</activityScheduleList>
</personScheduleList>
</resultBean>
</ns2:getPersonScheduleListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -172,6 +172,8 @@
<libelle>Vitrail Fusing 1/2 Je Adultes 2022/2023 - Mardi 14h-17h</libelle>
<dateStart>2022-09-01T00:00:00+02:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
<birthDateStart>2010-01-01T00:00:00+01:00</birthDateStart>
<birthDateEnd>2017-12-31T00:00:00+01:00</birthDateEnd>
<calendarLetter>X</calendarLetter>
<subscribePublication>L</subscribePublication>
<numOrder>0</numOrder>
@ -866,6 +868,92 @@
</placeList>
</unitPortailList>
</activityUnitPlacePortailList>
<activityUnitPlacePortailList>
<activityPortail>
<idAct>A10056517599</idAct>
<libelle>TEST promenade forêt enchantée</libelle>
<libelle2>Promenade forêt enchantée</libelle2>
<blocNoteList>
<note>Activité senior du 15 au 16 juin 2023</note>
<numIndex>1</numIndex>
</blocNoteList>
<idService>plop</idService>
<dateStart>2023-06-15T00:00:00+02:00</dateStart>
<dateEnd>2023-06-16T00:00:00+02:00</dateEnd>
<birthControl>B</birthControl>
<schoolYear>2022</schoolYear>
<calendarGeneration>
<code>FORBIDDEN</code>
<value>I</value>
</calendarGeneration>
<calendarMode>N</calendarMode>
<activityType>
<code>1-APE</code>
<libelle>Activité Pédestre Activité régulière</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</activityType>
<weeklyCalendarActivityList>
<yearCalendar>2023</yearCalendar>
<dayWeekInfoList>
<dayNum>1</dayNum>
<isOpen>true</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>2</dayNum>
<isOpen>true</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>3</dayNum>
<isOpen>true</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>4</dayNum>
<isOpen>true</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>5</dayNum>
<isOpen>true</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>6</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
<dayWeekInfoList>
<dayNum>7</dayNum>
<isOpen>false</isOpen>
</dayWeekInfoList>
</weeklyCalendarActivityList>
</activityPortail>
<openDayList>2023-06-15T00:00:00+02:00</openDayList>
<openDayList>2023-06-16T00:00:00+02:00</openDayList>
<unitPortailList>
<idUnit>A10056517596</idUnit>
<libelle>TEST promenade forêt enchantée</libelle>
<codeExt>A</codeExt>
<dateStart>2023-06-15T00:00:00+02:00</dateStart>
<dateEnd>2023-06-16T00:00:00+02:00</dateEnd>
<birthDateStart>1900-01-01T00:00:00+01:00</birthDateStart>
<birthDateEnd>1963-12-31T00:00:00+01:00</birthDateEnd>
<calendarLetter>X</calendarLetter>
<subscribePublication>E</subscribePublication>
<numOrder>0</numOrder>
<calendarPublication>N</calendarPublication>
<recordAbsence>O</recordAbsence>
<placeList>
<id>A10056517597</id>
<lib>TERRITOIRE OUEST</lib>
<adresse>
<num>0</num>
</adresse>
<capacityInfo>
<controlOK>true</controlOK>
</capacityInfo>
</placeList>
</unitPortailList>
</activityUnitPlacePortailList>
</ReadActivityPortailListResultBean>
</ns2:readActivityListResponse>
</soap:Body>

View File

@ -30,6 +30,7 @@ INSTALLED_APPS += ( # noqa pylint: disable=undefined-variable
'passerelle.contrib.lille_urban_card',
'passerelle.contrib.mdph13',
'passerelle.contrib.nancypoll',
'passerelle.contrib.nantes_scrib',
'passerelle.contrib.planitech',
'passerelle.contrib.rsa13',
'passerelle.contrib.sigerly',

View File

@ -787,7 +787,7 @@ def test_search_tiers_by_rib(mocked_post, mocked_get, connector, app):
for item in resp.json['data']:
assert 'id' in item
assert 'text' in item
assert '487464' in item['text'] or '144984' in item['text']
assert item['text'] in ['FOO (144984)', 'BAR (487464)']
@mock.patch('passerelle.utils.Request.get')

View File

@ -1,6 +1,8 @@
import json
from unittest import mock
import pytest
import responses
from django.contrib.contenttypes.models import ContentType
from passerelle.apps.matrix42.models import Matrix42
@ -213,6 +215,15 @@ def test_matrix42_bad_rawtoken(mocked_request, app, matrix42):
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
assert 'not returned a dict' in resp.json['err_desc']
# empty response: error when not allowed
mocked_request.side_effect = [
FakedResponse(content='', status_code=204),
]
resp = app.get(endpoint, params=params, status=200)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
assert 'returned an empty response' in resp.json['err_desc']
# Matrix42 error
mocked_request.side_effect = [
FakedResponse(content='{"ExceptionName":"NotFound","Message":"4o4"}', status_code=404),
@ -302,3 +313,23 @@ def test_matrix42_object(mocked_request, app, matrix42):
'ID': '424242',
'SPSActivityClassBase': {'TicketNumber': 'TCK0000153', 'TimeStamp': 'AAAAAAHlWr4='},
}
def test_matrix42_generic(app, matrix42):
api = ApiUser.objects.create(username='all', keytype='', key='')
obj_type = ContentType.objects.get_for_model(matrix42)
AccessRight.objects.create(
codename='can_access', apiuser=api, resource_type=obj_type, resource_pk=matrix42.pk
)
endpoint = generic_endpoint_url('matrix42', 'generic', slug=matrix42.slug)
with responses.RequestsMock() as rsps:
rsps.post(
'https://matrix42.example.net/api/ApiToken/GenerateAccessTokenFromApiToken',
status=200,
body=TOKEN,
)
rsps.post('https://matrix42.example.net/api/ticket/Transform', status=204)
resp = app.post_json(endpoint + '/ticket/Transform', params={'foo/bar': 'coin'}, status=200)
assert json.loads(rsps.calls[1].request.body) == {'foo': {'bar': 'coin'}}
assert resp.json == {'err': 0, 'data': None} # empty response 204 is ok here

157
tests/test_nantes_scrib.py Normal file
View File

@ -0,0 +1,157 @@
# Copyright (C) 2023 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/>.
import os
import pytest
import responses
import tests.utils
from passerelle.contrib.nantes_scrib.models import SOAPScrib
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nantes_scrib')
RSPS_OK = """<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body><ns2:depotResponse xmlns:ns2="http://ws.scrib/">
<return><codeRetour>true</codeRetour></return>
</ns2:depotResponse></S:Body></S:Envelope>"""
RSPS_ERR_TECH = """<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body><ns2:depotResponse xmlns:ns2="http://ws.scrib/">
<return><codeErreur>ERR_TECH</codeErreur><codeRetour>false</codeRetour></return>
</ns2:depotResponse></S:Body></S:Envelope>"""
WCS_WSCALL = {
# new request (no numBac)
'demandeBacs/bac/0/bacCollecteDechetsMenagers': 'true',
'demandeBacs/bac/0/bacCollecteSelective': 'false',
'demandeBacs/bac/0/bacCollecteVerre': 'false',
'demandeBacs/bac/0/numBacCollecteDechetsMenagers': '',
'demandeBacs/bac/0/numBacCollecteSelective': None,
# error
'demandeBacs/bac/1': 'not-a-dict',
# renewal (numBac)
'demandeBacs/bac/2/bacCollecteDechetsMenagers': 'false',
'demandeBacs/bac/2/bacCollecteSelective': 1,
'demandeBacs/bac/2/bacCollecteVerre': 'false',
'demandeBacs/bac/2/numBacCollecteDechetsMenagers': '2',
'demandeBacs/bac/2/numBacCollecteSelective': '576X9812',
# no request (nothing is True)
'demandeBacs/bac/3/bacCollecteDechetsMenagers': 'false',
'demandeBacs/bac/3/bacCollecteSelective': '',
'demandeBacs/bac/3/bacCollecteVerre': 'blah',
'demandeBacs/bac/3/numBacCollecteDechetsMenagers': '',
'demandeBacs/bac/3/numBacCollecteSelective': None,
'demandeBacs/bac/3/numBacCollecteVerre': 0,
# others parameters
'demandeBacs/codeCivel': '1140206',
'demandeBacs/codePostal': '44000',
'demandeBacs/commune': 'TestVille',
'demandeBacs/email': 'test@example.org',
'demandeBacs/libelleVoie': 'Route de Test',
'demandeBacs/natureDemande': 'DREN',
'demandeBacs/natureDemandeur': 'NPAR',
'demandeBacs/nom': 'TESTNOM',
'demandeBacs/nombrePersonnesFoyer': ' 2 ',
'demandeBacs/numeroVoie': ' 42 ',
'demandeBacs/prenom': 'TestPrenom',
'demandeBacs/raisonDemande': 'BBRU',
'demandeBacs/typeDegradation': 'TDRO',
'demandeBacs/telephone': '0707070707',
'demandeBacs/typeHabitat': 'HCOL',
}
@pytest.fixture
def wsdl():
with open(os.path.join(TEST_BASE_DIR, 'scrib.wsdl'), 'rb') as wsdl_file:
return wsdl_file.read()
@pytest.fixture
def connector(db):
return tests.utils.setup_access_rights(
SOAPScrib.objects.create(slug='test', wsdl_url='https://scrib.example.net/scrib.wsdl')
)
def test_scrib(wsdl, connector, app):
with responses.RequestsMock() as rsps:
rsps.get('https://scrib.example.net/scrib.wsdl', status=200, content_type='text/xml', body=wsdl)
rsps.post(
'https://scrib.example.net/scrib2/ws/demandeBac',
status=200,
content_type='text/xml',
body=RSPS_OK,
)
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
xml = rsps.calls[1].request.body.decode('utf-8') # post payload
assert resp.json == {
'data': {'codeErreur': None, 'codeRetour': True, 'messageErreur': None},
'err': 0,
}
# cleaned "bac" list, only two remains
assert xml.count('<bac>') == 2
assert '<bac><bacCollecteDechetsMenagers>true</bacCollecteDechetsMenagers></bac>' in xml
assert (
'<bac><bacCollecteSelective>true</bacCollecteSelective>'
'<numBacCollecteSelective>576X9812</numBacCollecteSelective></bac>' in xml
)
assert '<nombrePersonnesFoyer>2</nombrePersonnesFoyer>' in xml
assert '<numeroVoie>42</numeroVoie>' in xml
assert '<raisonDemande>BBRU</raisonDemande>' in xml
assert '<typeDegradation>TDRO</typeDegradation>' in xml
# remove empty or non-integers fields
WCS_WSCALL['demandeBacs/nombrePersonnesFoyer'] = '0'
WCS_WSCALL['demandeBacs/numeroVoie'] = ' '
WCS_WSCALL['demandeBacs/raisonDemande'] = ' '
WCS_WSCALL['demandeBacs/typeDegradation'] = None
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
xml = rsps.calls[2].request.body.decode('utf-8') # post payload
assert xml.count('<bac>') == 2
assert '<nombrePersonnesFoyer>0</nombrePersonnesFoyer>' in xml
assert '<numeroVoie>' not in xml
assert '<raisonDemande>' not in xml
assert '<typeDegradation>' not in xml
with responses.RequestsMock() as rsps:
rsps.post(
'https://scrib.example.net/scrib2/ws/demandeBac', status=200, content_type='text/xml', body='booo'
)
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'passerelle.utils.soap.SOAPInvalidContent'
with responses.RequestsMock() as rsps:
resp = app.post_json('/nantes-scrib/test/depot', params={}, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'missing demandeBacs/bac list'
with responses.RequestsMock() as rsps:
rsps.post(
'https://scrib.example.net/scrib2/ws/demandeBac',
status=200,
content_type='text/xml',
body=RSPS_ERR_TECH,
)
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
assert resp.json == {
'data': {'codeErreur': 'ERR_TECH', 'codeRetour': False, 'messageErreur': None},
'err': 1,
}

View File

@ -25,6 +25,7 @@ import pytest
import responses
from django.db import transaction
from django.utils.dateparse import parse_date
from django.utils.timezone import now
from requests.exceptions import ConnectionError, ReadTimeout
from zeep import Settings
from zeep.helpers import serialize_object
@ -645,6 +646,13 @@ def test_link(family_service, con, app):
assert resp.json['err'] == 0
assert resp.json['data'] == 'ok'
# ignore accents provided by user
params['firstname'] = 'Jhôñ'
resp = app.post_json(url + '?NameID=local', params=params)
assert Link.objects.count() == 1
assert resp.json['err'] == 0
assert resp.json['data'] == 'ok'
params['lastname'] = 'John'
resp = app.post_json(url + '?NameID=local', params=params)
assert Link.objects.count() == 1
@ -6090,12 +6098,61 @@ def test_get_recurrent_week(family_service, activity_service, con, app):
'text': 'Vendredi RESTAURATION SCOLAIRE 22/23',
},
]
assert resp.json['meta'] == {'date_min_prev': '2023-03-27'}
assert resp.json['meta'] == {'date_min_prev': '2023-03-27', 'warning_msg': None}
params['activity_id'] = 'plop'
resp = app.get(url + '?NameID=local', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'No week calendar for activity plop on 2023-04-01'
assert resp.json['err_desc'] == "No week calendar for activity 'plop' on 2023-04-01"
del params['activity_id']
resp = app.get(url + '?NameID=local', params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "missing parameters: 'activity_id'."
# do not raise on calls generated by the form providing 'None' activity
params['activity_id'] = 'None'
resp = app.get(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
assert resp.json['data'] == []
assert resp.json['meta'] == {
'date_min_prev': None,
'warning_msg': "No week calendar for activity 'None' on 2023-04-01",
}
def test_get_recurrent_week_no_open_day(family_service, activity_service, con, app, caplog, settings):
family_service.add_soap_response('readFamily', get_xml_file('R_read_family.xml'))
activity_service.add_soap_response(
'getPersonScheduleList',
get_xml_file('R_get_person_schedule_list_with_recurrent_week_no_open_day.xml'),
)
url = get_endpoint('get-recurrent-week')
params = {
'person_id': '613880',
'activity_id': 'A10049327682',
'ref_date': '2023-04-01',
}
con.set_log_level('DEBUG')
ResourceLog.objects.all().delete()
resp = app.get(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "No open day for activity 'A10049327682' on 2023-04-01"
# SOAP request and response are logged
assert '<numDossier>1312</numDossier>' in ResourceLog.objects.all()[2].extra['request_payload']
assert '<isOpen>false</isOpen>' in ResourceLog.objects.all()[2].extra['response_content']
assert '<isOpen>true</isOpen>' not in ResourceLog.objects.all()[2].extra['response_content']
# error is logged
assert ResourceLog.objects.all()[3].message == 'Error occurred while processing request'
assert ResourceLog.objects.all()[3].extra['error_summary'] == [
"passerelle.utils.jsonresponse.APIError: No open day for activity 'A10049327682' on 2023-04-01\n"
]
assert len(caplog.records) == 4
assert caplog.records[3].levelno == logging.ERROR
assert caplog.records[3].message == 'Error occurred while processing request'
def test_get_recurrent_week_not_linked_error(con, app):
@ -6567,7 +6624,7 @@ def test_read_activity_list(con, app, freezer):
freezer.move_to('2024-02-29')
resp = app.get(url)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 8
assert len(resp.json['data']) == 9
activity_text = [x['activity']['text'] for x in resp.json['data']]
assert activity_text == sorted(activity_text)
assert [
@ -6579,6 +6636,7 @@ def test_read_activity_list(con, app, freezer):
for x in resp.json['data']
] == [
('A10056517594-A10056517595-A10056517597', 'plop', None),
('A10056517599-A10056517596-A10056517597', 'plop', None),
('A10056514645-A10056514650-A10053179757', None, None),
('A10056514645-A10056514648-A10053179876', None, None),
('A10056514645-A10056514649-A10053179757', None, None),
@ -6587,15 +6645,26 @@ def test_read_activity_list(con, app, freezer):
('A10051141965-A10051141970-A10053179226', 'A10049329051', 'Sorties'),
('A10051141965-A10051141990-A10053179227', 'A10049329051', 'Sorties'),
]
item = resp.json['data'][4]
# item text differs on so called maelis "standard" and "non-standard" activities
resp = app.get(url)
standard_item = resp.json['data'][0] # activity having only one (standard) unit
non_standard_item = resp.json['data'][2] # activity having many units
assert standard_item['text'] == 'Promenade forêt enchantée, TERRITOIRE OUEST'
assert (
non_standard_item['text']
== 'TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES (MERCREDI - 13h45/17h - 8/15Ans), ARGOULETS'
)
item = resp.json['data'][5]
item['activity'] = 'N/A'
item['unit'] = 'N/A'
item['place'] = 'N/A'
assert item == {
'id': 'A10051141965-A10051141966-A10053179226',
'text': 'Vitrail Fusing 1/2 Je Adultes 2022/2023'
+ ' - Mardi 14h-17h, Vitrail Fusing 1/2 Je Adultes 2022/2023'
+ ' - Mardi 14h-17h, Centre Culturel ALBAN MINVILLE',
+ ' - Mardi 14h-17h (Vitrail Fusing 1/2 Je Adultes 2022/2023'
+ ' - Mardi 14h-17h), Centre Culturel ALBAN MINVILLE',
'activity': 'N/A',
'unit': 'N/A',
'place': 'N/A',
@ -6618,15 +6687,8 @@ def test_read_activity_list(con, app, freezer):
},
'public': {
'text': 'Public',
'data': {
'0': 'Petit enfant (- de 3 ans)',
'1': 'Enfant (3-11 ans)',
'2': 'Ado (12-17 ans)',
'3': 'Jeune (18-25 ans)',
'4': 'Adulte (26-59 ans)',
'5': 'Sénior (60 ans et plus)',
},
'order': ['0', '1', '2', '3', '4', '5'],
'data': {'1': 'Enfant (3-11 ans)', '2': 'Ado (12-17 ans)'},
'order': ['1', '2'],
},
'day': {'text': 'Jours', 'data': {'2': 'Mardi'}, 'order': ['2']},
},
@ -12023,3 +12085,71 @@ def test_trigger_wcs_api_error(family_service, activity_service, wcs_service, co
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.trigger_status() == 'triggered'
assert subscription.wcs_trigger_response == {'err': 1, 'err_class': 'Page non trouvée', 'err_desc': None}
def test_earliest_updated(con):
con.referential.all().delete()
assert con.earliest_updated(con.referential.all()) is None
ref = con.referential.create(referential_name='foo', item_id='1', item_data={})
assert con.earliest_updated(con.referential.all()) == ref.updated
assert con.earliest_updated(con.referential.filter(referential_name='bar')) is None
@pytest.mark.parametrize('ref_name', ['Activity', 'ActivityNatureType', 'Direct', 'Service'])
def test_get_activity_referentials_earliest_timestamp(con, ref_name):
con.referential.all().delete()
assert con.get_activity_referentials_earliest_timestamp() is None
con.referential.create(referential_name='foo', item_id='1', item_data={})
assert con.get_activity_referentials_earliest_timestamp() is None
activity = con.referential.create(referential_name=ref_name, item_id='1', item_data={})
assert con.get_activity_referentials_earliest_timestamp() == activity.updated
@pytest.mark.parametrize('ref_name', ['Activity', 'ActivityNatureType', 'Direct', 'Service'])
def test_is_activity_referentials_too_old(con, ref_name):
con.referential.all().delete()
assert con.is_activity_referentials_too_old() is True
activity = con.referential.create(referential_name=ref_name, item_id='1', item_data={})
assert con.is_activity_referentials_too_old() is False
con.referential.update(updated=activity.updated - datetime.timedelta(hours=6, minutes=1))
assert con.is_activity_referentials_too_old() is True
@pytest.mark.parametrize('ref_name', ['Activity', 'ActivityNatureType', 'Direct', 'Service'])
def test_get_referentials_earliest_timestamp(con, ref_name):
con.referential.all().delete()
assert con.get_referentials_earliest_timestamp() is None
con.referential.create(referential_name=ref_name, item_id='1', item_data={})
assert con.get_referentials_earliest_timestamp() is None
ref = con.referential.create(referential_name='foo', item_id='1', item_data={})
assert con.get_referentials_earliest_timestamp() == ref.updated
def test_is_referentials_too_old(con):
con.referential.all().delete()
assert con.is_referentials_too_old() is True
ref = con.referential.create(referential_name='foo', item_id='1', item_data={})
assert con.is_referentials_too_old() is False
con.referential.update(updated=ref.updated - datetime.timedelta(hours=30, minutes=1))
assert con.is_referentials_too_old() is True
def test_should_update_referentials(con, freezer):
freezer.move_to('2023-12-10T12:01:01')
con.referential.all().delete()
assert con.should_update_referentials() is True
con.referential.create(referential_name='foo', item_id='1', item_data={})
freezer.move_to('2023-12-11T12:00:01')
assert con.should_update_referentials() is False
# after 24 hours update should happen
freezer.move_to('2023-12-11T12:01:01')
assert con.should_update_referentials() is True
con.referential.update(updated=now())
assert con.should_update_referentials() is False
# less than 24 hours, update should not happen
freezer.move_to('2023-12-11T23:59:01')
assert con.should_update_referentials() is False
# but between 00:00 and 06:00 if referential is more thant 7 hours old, update
freezer.move_to('2023-12-12T00:00:01')
assert con.should_update_referentials() is True

View File

@ -1,6 +1,6 @@
[tox]
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/passerelle/{env:RAND_TEST:}
envlist = py3-django32-codestyle-coverage,pylint,vitest
envlist = py3-django32-codestyle-coverage
[testenv]
usedevelop = True