Skip to content

Commit d528ff2

Browse files
authored
ci: merge main to release (#8836)
2 parents 8957afd + 0735aef commit d528ff2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1847
-581
lines changed

.github/workflows/build.yml

+13
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,19 @@ jobs:
444444
PKG_VERSION: ${{needs.prepare.outputs.pkg_version}}
445445

446446
steps:
447+
- name: Refresh Staging DB
448+
uses: the-actions-org/workflow-dispatch@v4
449+
with:
450+
workflow: update-staging-db.yml
451+
repo: ietf-tools/infra-k8s
452+
ref: main
453+
token: ${{ secrets.GH_INFRA_K8S_TOKEN }}
454+
inputs: '{ "sourceDb":"datatracker" }'
455+
wait-for-completion: true
456+
wait-for-completion-timeout: 5m
457+
wait-for-completion-interval: 20s
458+
display-workflow-run-url: false
459+
447460
- name: Deploy to staging
448461
uses: the-actions-org/workflow-dispatch@v4
449462
with:

client/agenda/store.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const useAgendaStore = defineStore('agenda', {
141141
meetingNumber = meetingData.meetingNumber
142142
}
143143

144-
const resp = await fetch(`/api/meeting/${meetingNumber}/agenda-data`, { credentials: 'omit' })
144+
const resp = await fetch(`/api/meeting/${meetingNumber}/agenda-data`)
145145
if (!resp.ok) {
146146
throw new Error(resp.statusText)
147147
}

dev/build/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ghcr.io/ietf-tools/datatracker-app-base:20250402T1611
1+
FROM ghcr.io/ietf-tools/datatracker-app-base:20250421T1600
22
LABEL maintainer="IETF Tools Team <[email protected]>"
33

44
ENV DEBIAN_FRONTEND=noninteractive

dev/build/TARGET_BASE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20250402T1611
1+
20250421T1600

ietf/api/tests.py

+226-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright The IETF Trust 2015-2024, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
import base64
4+
import copy
45
import datetime
56
import json
67
import html
@@ -31,7 +32,7 @@
3132
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, WgRfcFactory
3233
from ietf.group.factories import RoleFactory
3334
from ietf.meeting.factories import MeetingFactory, SessionFactory
34-
from ietf.meeting.models import Session
35+
from ietf.meeting.models import Session, Registration
3536
from ietf.nomcom.models import Volunteer
3637
from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year
3738
from ietf.person.factories import PersonFactory, random_faker, EmailFactory, PersonalApiKeyFactory
@@ -828,6 +829,196 @@ def test_api_new_meeting_registration_nomcom_volunteer(self):
828829
self.assertEqual(volunteer.nomcom, nomcom)
829830
self.assertEqual(volunteer.origin, 'registration')
830831

832+
@override_settings(APP_API_TOKENS={"ietf.api.views.api_new_meeting_registration_v2": ["valid-token"]})
833+
def test_api_new_meeting_registration_v2(self):
834+
meeting = MeetingFactory(type_id='ietf')
835+
person = PersonFactory()
836+
regs = [
837+
{
838+
'affiliation': "Alguma Corporação",
839+
'country_code': 'PT',
840+
'email': person.email().address,
841+
'first_name': person.first_name(),
842+
'last_name': person.last_name(),
843+
'meeting': str(meeting.number),
844+
'reg_type': 'onsite',
845+
'ticket_type': 'week_pass',
846+
'checkedin': False,
847+
'is_nomcom_volunteer': False,
848+
'cancelled': False,
849+
}
850+
]
851+
852+
url = urlreverse('ietf.api.views.api_new_meeting_registration_v2')
853+
#
854+
# Test invalid key
855+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "invalid-token"})
856+
self.assertEqual(r.status_code, 403)
857+
#
858+
# Test invalid data
859+
bad_regs = copy.deepcopy(regs)
860+
del(bad_regs[0]['email'])
861+
r = self.client.post(url, data=json.dumps(bad_regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
862+
self.assertEqual(r.status_code, 400)
863+
#
864+
# Test valid POST
865+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
866+
self.assertContains(r, "Success", status_code=202)
867+
#
868+
# Check record
869+
reg = regs[0]
870+
objects = Registration.objects.filter(email=reg['email'], meeting__number=reg['meeting'])
871+
self.assertEqual(objects.count(), 1)
872+
obj = objects[0]
873+
for key in ['affiliation', 'country_code', 'first_name', 'last_name', 'checkedin']:
874+
self.assertEqual(getattr(obj, key), False if key=='checkedin' else reg.get(key) , "Bad data for field '%s'" % key)
875+
self.assertEqual(obj.tickets.count(), 1)
876+
ticket = obj.tickets.first()
877+
self.assertEqual(ticket.ticket_type.slug, regs[0]['ticket_type'])
878+
self.assertEqual(ticket.attendance_type.slug, regs[0]['reg_type'])
879+
self.assertEqual(obj.person, person)
880+
#
881+
# Test update (switch to remote)
882+
regs = [
883+
{
884+
'affiliation': "Alguma Corporação",
885+
'country_code': 'PT',
886+
'email': person.email().address,
887+
'first_name': person.first_name(),
888+
'last_name': person.last_name(),
889+
'meeting': str(meeting.number),
890+
'reg_type': 'remote',
891+
'ticket_type': 'week_pass',
892+
'checkedin': False,
893+
'is_nomcom_volunteer': False,
894+
'cancelled': False,
895+
}
896+
]
897+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
898+
self.assertContains(r, "Success", status_code=202)
899+
objects = Registration.objects.filter(email=reg['email'], meeting__number=reg['meeting'])
900+
self.assertEqual(objects.count(), 1)
901+
obj = objects[0]
902+
self.assertEqual(obj.tickets.count(), 1)
903+
ticket = obj.tickets.first()
904+
self.assertEqual(ticket.ticket_type.slug, regs[0]['ticket_type'])
905+
self.assertEqual(ticket.attendance_type.slug, regs[0]['reg_type'])
906+
#
907+
# Test multiple
908+
regs = [
909+
{
910+
'affiliation': "Alguma Corporação",
911+
'country_code': 'PT',
912+
'email': person.email().address,
913+
'first_name': person.first_name(),
914+
'last_name': person.last_name(),
915+
'meeting': str(meeting.number),
916+
'reg_type': 'onsite',
917+
'ticket_type': 'one_day',
918+
'checkedin': False,
919+
'is_nomcom_volunteer': False,
920+
'cancelled': False,
921+
},
922+
923+
{
924+
'affiliation': "Alguma Corporação",
925+
'country_code': 'PT',
926+
'email': person.email().address,
927+
'first_name': person.first_name(),
928+
'last_name': person.last_name(),
929+
'meeting': str(meeting.number),
930+
'reg_type': 'remote',
931+
'ticket_type': 'week_pass',
932+
'checkedin': False,
933+
'is_nomcom_volunteer': False,
934+
'cancelled': False,
935+
}
936+
]
937+
938+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
939+
self.assertContains(r, "Success", status_code=202)
940+
objects = Registration.objects.filter(email=reg['email'], meeting__number=reg['meeting'])
941+
self.assertEqual(objects.count(), 1)
942+
obj = objects[0]
943+
self.assertEqual(obj.tickets.count(), 2)
944+
self.assertEqual(obj.tickets.filter(attendance_type__slug='onsite').count(), 1)
945+
self.assertEqual(obj.tickets.filter(attendance_type__slug='remote').count(), 1)
946+
947+
@override_settings(APP_API_TOKENS={"ietf.api.views.api_new_meeting_registration_v2": ["valid-token"]})
948+
def test_api_new_meeting_registration_v2_cancelled(self):
949+
meeting = MeetingFactory(type_id='ietf')
950+
person = PersonFactory()
951+
regs = [
952+
{
953+
'affiliation': "Acme",
954+
'country_code': 'US',
955+
'email': person.email().address,
956+
'first_name': person.first_name(),
957+
'last_name': person.last_name(),
958+
'meeting': str(meeting.number),
959+
'reg_type': 'onsite',
960+
'ticket_type': 'week_pass',
961+
'checkedin': False,
962+
'is_nomcom_volunteer': False,
963+
'cancelled': False,
964+
}
965+
]
966+
url = urlreverse('ietf.api.views.api_new_meeting_registration_v2')
967+
self.assertEqual(Registration.objects.count(), 0)
968+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
969+
self.assertContains(r, "Success", status_code=202)
970+
self.assertEqual(Registration.objects.count(), 1)
971+
regs[0]['cancelled'] = True
972+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
973+
self.assertContains(r, "Success", status_code=202)
974+
self.assertEqual(Registration.objects.count(), 0)
975+
976+
@override_settings(APP_API_TOKENS={"ietf.api.views.api_new_meeting_registration_v2": ["valid-token"]})
977+
def test_api_new_meeting_registration_v2_nomcom(self):
978+
meeting = MeetingFactory(type_id='ietf')
979+
person = PersonFactory()
980+
regs = [
981+
{
982+
'affiliation': "Acme",
983+
'country_code': 'US',
984+
'email': person.email().address,
985+
'first_name': person.first_name(),
986+
'last_name': person.last_name(),
987+
'meeting': str(meeting.number),
988+
'reg_type': 'onsite',
989+
'ticket_type': 'week_pass',
990+
'checkedin': False,
991+
'is_nomcom_volunteer': False,
992+
'cancelled': False,
993+
}
994+
]
995+
996+
url = urlreverse('ietf.api.views.api_new_meeting_registration_v2')
997+
now = datetime.datetime.now()
998+
if now.month > 10:
999+
year = now.year + 1
1000+
else:
1001+
year = now.year
1002+
# create appropriate group and nomcom objects
1003+
nomcom = NomComFactory.create(is_accepting_volunteers=True, **nomcom_kwargs_for_year(year))
1004+
1005+
# first test is_nomcom_volunteer False
1006+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
1007+
self.assertContains(r, "Success", status_code=202)
1008+
# assert no Volunteers exists
1009+
self.assertEqual(Volunteer.objects.count(), 0)
1010+
1011+
# test is_nomcom_volunteer True
1012+
regs[0]['is_nomcom_volunteer'] = True
1013+
r = self.client.post(url, data=json.dumps(regs), content_type='application/json', headers={"X-Api-Key": "valid-token"})
1014+
self.assertContains(r, "Success", status_code=202)
1015+
# assert Volunteer exists
1016+
self.assertEqual(Volunteer.objects.count(), 1)
1017+
volunteer = Volunteer.objects.last()
1018+
self.assertEqual(volunteer.person, person)
1019+
self.assertEqual(volunteer.nomcom, nomcom)
1020+
self.assertEqual(volunteer.origin, 'registration')
1021+
8311022
def test_api_version(self):
8321023
DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=datetime.timezone.utc), host='testapi.example.com',tz='UTC')
8331024
url = urlreverse('ietf.api.views.version')
@@ -973,6 +1164,39 @@ def test_active_email_list(self):
9731164
self.assertCountEqual(result.keys(), ["addresses"])
9741165
self.assertCountEqual(result["addresses"], Email.objects.filter(active=True).values_list("address", flat=True))
9751166

1167+
@override_settings(APP_API_TOKENS={"ietf.api.views.related_email_list": ["valid-token"]})
1168+
def test_related_email_list(self):
1169+
joe = EmailFactory(address='[email protected]')
1170+
EmailFactory(address='[email protected]', person=joe.person)
1171+
EmailFactory(address='jò[email protected]', person=joe.person)
1172+
url = urlreverse("ietf.api.views.related_email_list", kwargs={'email': '[email protected]'})
1173+
# no api key
1174+
r = self.client.get(url, headers={})
1175+
self.assertEqual(r.status_code, 403)
1176+
# invalid api key
1177+
r = self.client.get(url, headers={"X-Api-Key": "not-the-valid-token"})
1178+
self.assertEqual(r.status_code, 403)
1179+
# wrong method
1180+
r = self.client.post(url, headers={"X-Api-Key": "valid-token"})
1181+
self.assertEqual(r.status_code, 405)
1182+
# valid
1183+
r = self.client.get(url, headers={"X-Api-Key": "valid-token"})
1184+
self.assertEqual(r.status_code, 200)
1185+
self.assertEqual(r.headers["Content-Type"], "application/json")
1186+
result = json.loads(r.content)
1187+
self.assertCountEqual(result.keys(), ["addresses"])
1188+
self.assertCountEqual(result["addresses"], joe.person.email_set.exclude(address='[email protected]').values_list("address", flat=True))
1189+
# non-ascii
1190+
non_ascii_url = urlreverse("ietf.api.views.related_email_list", kwargs={'email': 'jò[email protected]'})
1191+
r = self.client.get(non_ascii_url, headers={"X-Api-Key": "valid-token"})
1192+
self.assertEqual(r.status_code, 200)
1193+
result = json.loads(r.content)
1194+
self.assertTrue('[email protected]' in result["addresses"])
1195+
# email not found
1196+
not_found_url = urlreverse("ietf.api.views.related_email_list", kwargs={'email': '[email protected]'})
1197+
r = self.client.get(not_found_url, headers={"X-Api-Key": "valid-token"})
1198+
self.assertEqual(r.status_code, 404)
1199+
9761200
@override_settings(APP_API_TOKENS={"ietf.api.views.role_holder_addresses": ["valid-token"]})
9771201
def test_role_holder_addresses(self):
9781202
url = urlreverse("ietf.api.views.role_holder_addresses")
@@ -1440,7 +1664,7 @@ def test_api_top_level(self):
14401664
resource_list = r.json()
14411665

14421666
for name in self.apps:
1443-
if not name in self.apps:
1667+
if not name in resource_list:
14441668
sys.stderr.write("Expected a REST API resource for %s, but didn't find one\n" % name)
14451669

14461670
for name in self.apps:

ietf/api/urls.py

+3
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,15 @@
6666
# Let MeetEcho upload session polls
6767
url(r'^notify/session/polls/?$', meeting_views.api_upload_polls),
6868
# Let the registration system notify us about registrations
69+
url(r'^notify/meeting/registration/v2/?', api_views.api_new_meeting_registration_v2),
6970
url(r'^notify/meeting/registration/?', api_views.api_new_meeting_registration),
7071
# OpenID authentication provider
7172
url(r'^openid/$', TemplateView.as_view(template_name='api/openid-issuer.html'), name='ietf.api.urls.oidc_issuer'),
7273
url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
7374
# Email alias listing
7475
url(r'^person/email/$', api_views.active_email_list),
76+
# Related Email listing
77+
url(r'^person/email/(?P<email>[^/\x00]+)/related/$', api_views.related_email_list),
7578
# Draft submission API
7679
url(r'^submit/?$', submit_views.api_submit_tombstone),
7780
# Draft upload API

0 commit comments

Comments
 (0)