feat(scheduling): add explicit invitation link lifecycle

This commit is contained in:
2026-07-22 03:08:56 +02:00
parent ed828685f6
commit 2cb86c90dc
6 changed files with 785 additions and 122 deletions

View File

@@ -60,6 +60,7 @@ from govoplan_scheduling.backend.service import (
cancel_scheduling_request,
create_scheduling_request,
get_public_scheduling_participation,
issue_scheduling_participant_invitation,
scheduling_request_summary,
scheduling_slot_revision,
submit_scheduling_availability,
@@ -149,8 +150,9 @@ class SchedulingResponseEditingTests(unittest.TestCase):
participants: list[SchedulingParticipantInput] | None = None,
**settings,
) -> tuple[SchedulingRequest, dict[str, str]]:
issue_links = bool(settings.pop("create_participant_invitations", True))
start = datetime(2026, 7, 20, 9, tzinfo=timezone.utc)
return create_scheduling_request(
request, automatic_tokens = create_scheduling_request(
self.session,
tenant_id="tenant-1",
user_id="organizer-1",
@@ -184,6 +186,31 @@ class SchedulingResponseEditingTests(unittest.TestCase):
**settings,
),
)
self.assertEqual(automatic_tokens, {})
tokens = (
{
participant.id: self._issue_copy(request, participant)
for participant in request.participants
}
if issue_links
else {}
)
return request, tokens
def _issue_copy(
self,
request: SchedulingRequest,
participant: SchedulingParticipant,
) -> str:
result = issue_scheduling_participant_invitation(
self.session,
tenant_id=request.tenant_id,
request_id=request.id,
participant_id=participant.id,
action="copy",
)
self.assertIsNotNone(result.action_url)
return str(result.action_url).rsplit("/", 1)[-1]
def _answer(
self,
@@ -475,7 +502,8 @@ class SchedulingResponseEditingTests(unittest.TestCase):
],
),
)
token = tokens[public_request.participants[0].id]
self.assertEqual(tokens, {})
token = self._issue_copy(public_request, public_request.participants[0])
invitation = self.session.query(PollInvitation).filter(
PollInvitation.id == public_request.participants[0].poll_invitation_id
).one()
@@ -568,7 +596,8 @@ class SchedulingResponseEditingTests(unittest.TestCase):
participants=[SchedulingParticipantInput(display_name="Guest")],
),
)
token = tokens[request.participants[0].id]
self.assertEqual(tokens, {})
token = self._issue_copy(request, request.participants[0])
wrong = SchedulingPublicParticipationAccessRequest(password="wrong password")
for attempt in range(10):
@@ -623,7 +652,8 @@ class SchedulingResponseEditingTests(unittest.TestCase):
),
)
participant = request.participants[0]
token = tokens[participant.id]
self.assertEqual(tokens, {})
token = self._issue_copy(request, participant)
answer = SchedulingAvailabilityAnswerInput(
slot_id=request.slots[0].id,
value="available",
@@ -1036,8 +1066,16 @@ class SchedulingResponseEditingTests(unittest.TestCase):
self.assertIsNotNone(removed_slot.deleted_at)
bob = next(item for item in response.participants if item.display_name == "Bob")
self.assertEqual(bob.email, "bob@example.test")
self.assertIsNotNone(bob.poll_invitation_id)
self.assertIsNotNone(bob.invitation_token)
self.assertIsNone(bob.poll_invitation_id)
self.assertIsNone(bob.invitation_token)
bob_model = next(
item
for item in request.participants
if item.deleted_at is None and item.display_name == "Bob"
)
self._issue_copy(request, bob_model)
bob_invitation_id = bob_model.poll_invitation_id
self.assertIsNotNone(bob_invitation_id)
current = api_get_my_scheduling_availability(
request.id,
session=self.session,
@@ -1091,7 +1129,7 @@ class SchedulingResponseEditingTests(unittest.TestCase):
),
)
invitation = self.session.query(PollInvitation).filter(
PollInvitation.id == bob.poll_invitation_id
PollInvitation.id == bob_invitation_id
).one()
self.assertIsNotNone(invitation.revoked_at)