feat(scheduling): bound public cancellation notices
This commit is contained in:
@@ -19,6 +19,7 @@ from govoplan_core.db.base import Base
|
||||
from govoplan_poll.backend.db.models import (
|
||||
Poll,
|
||||
PollInvitation,
|
||||
PollLifecycleTransition,
|
||||
PollOption,
|
||||
PollParticipationSubmission,
|
||||
PollResponse,
|
||||
@@ -84,6 +85,7 @@ class SchedulingResponseEditingTests(unittest.TestCase):
|
||||
PollResponse.__table__,
|
||||
PollInvitation.__table__,
|
||||
PollParticipationSubmission.__table__,
|
||||
PollLifecycleTransition.__table__,
|
||||
SchedulingRequest.__table__,
|
||||
SchedulingCandidateSlot.__table__,
|
||||
SchedulingParticipant.__table__,
|
||||
@@ -104,6 +106,7 @@ class SchedulingResponseEditingTests(unittest.TestCase):
|
||||
SchedulingRequest.__table__,
|
||||
PollParticipationSubmission.__table__,
|
||||
PollInvitation.__table__,
|
||||
PollLifecycleTransition.__table__,
|
||||
PollResponse.__table__,
|
||||
PollOption.__table__,
|
||||
Poll.__table__,
|
||||
@@ -600,6 +603,17 @@ class SchedulingResponseEditingTests(unittest.TestCase):
|
||||
token = self._issue_copy(request, request.participants[0])
|
||||
wrong = SchedulingPublicParticipationAccessRequest(password="wrong password")
|
||||
|
||||
for _attempt in range(20):
|
||||
with self.assertRaises(SchedulingPublicParticipationError) as missing:
|
||||
get_public_scheduling_participation(
|
||||
self.session,
|
||||
request_id=request.id,
|
||||
token=token,
|
||||
payload=SchedulingPublicParticipationAccessRequest(),
|
||||
client_address="192.0.2.20",
|
||||
)
|
||||
self.assertEqual(missing.exception.retry_after_seconds, 0)
|
||||
|
||||
for attempt in range(10):
|
||||
with self.assertRaises(SchedulingPublicParticipationError) as raised:
|
||||
get_public_scheduling_participation(
|
||||
@@ -1322,6 +1336,92 @@ class SchedulingResponseEditingTests(unittest.TestCase):
|
||||
self.assertIsNotNone(cancelled.cancelled_at)
|
||||
self.assertEqual(poll.status, "draft")
|
||||
|
||||
def test_cancellation_link_becomes_bounded_notice_without_request_details(self) -> None:
|
||||
request, tokens = self._request_and_tokens()
|
||||
participant = request.participants[0]
|
||||
invitation = self.session.query(PollInvitation).filter(
|
||||
PollInvitation.id == participant.poll_invitation_id
|
||||
).one()
|
||||
token = tokens[participant.id]
|
||||
cancelled_at = datetime(2026, 7, 22, 12, tzinfo=timezone.utc)
|
||||
|
||||
with (
|
||||
patch.object(scheduling_service, "_now", return_value=cancelled_at),
|
||||
patch.object(
|
||||
scheduling_service,
|
||||
"get_settings",
|
||||
return_value=SimpleNamespace(
|
||||
scheduling_cancellation_notice_days=7
|
||||
),
|
||||
),
|
||||
):
|
||||
cancelled = cancel_scheduling_request(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
request_id=request.id,
|
||||
)
|
||||
|
||||
notice_until = cancelled_at + timedelta(days=7)
|
||||
self.assertEqual(
|
||||
scheduling_service.response_datetime(
|
||||
cancelled.cancellation_notice_until
|
||||
),
|
||||
notice_until,
|
||||
)
|
||||
self.assertEqual(
|
||||
scheduling_service.response_datetime(invitation.expires_at),
|
||||
notice_until,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
scheduling_service,
|
||||
"_now",
|
||||
return_value=cancelled_at + timedelta(days=1),
|
||||
):
|
||||
notice = get_public_scheduling_participation(
|
||||
self.session,
|
||||
request_id=request.id,
|
||||
token=token,
|
||||
payload=SchedulingPublicParticipationAccessRequest(),
|
||||
client_address="192.0.2.30",
|
||||
)
|
||||
self.assertTrue(notice["cancellation_notice_only"])
|
||||
self.assertEqual(notice["status"], "cancelled")
|
||||
for private_field in (
|
||||
"description",
|
||||
"location",
|
||||
"deadline_at",
|
||||
"comment",
|
||||
"slots",
|
||||
"answers",
|
||||
):
|
||||
self.assertNotIn(private_field, notice)
|
||||
|
||||
with patch.object(
|
||||
scheduling_service,
|
||||
"_now",
|
||||
return_value=notice_until + timedelta(seconds=1),
|
||||
):
|
||||
with self.assertRaises(SchedulingPublicParticipationError):
|
||||
get_public_scheduling_participation(
|
||||
self.session,
|
||||
request_id=request.id,
|
||||
token=token,
|
||||
payload=SchedulingPublicParticipationAccessRequest(),
|
||||
client_address="192.0.2.30",
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulingError,
|
||||
"cancellation notice has expired",
|
||||
):
|
||||
issue_scheduling_participant_invitation(
|
||||
self.session,
|
||||
tenant_id=request.tenant_id,
|
||||
request_id=request.id,
|
||||
participant_id=participant.id,
|
||||
action="copy",
|
||||
)
|
||||
|
||||
def test_fully_invalidated_response_becomes_unanswered(self) -> None:
|
||||
request = self._request()
|
||||
alice = self._principal(
|
||||
|
||||
Reference in New Issue
Block a user