Add CalDAV outbox recovery UI
This commit is contained in:
@@ -7,6 +7,8 @@ from collections.abc import Iterator
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from govoplan_calendar.backend.service import CalendarError, http_request
|
||||
from govoplan_calendar.backend.router import _require_worker_principal
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -24,6 +26,17 @@ def running_http_server(handler: type[BaseHTTPRequestHandler]) -> Iterator[str]:
|
||||
|
||||
|
||||
class CalendarHttpSecurityTests(unittest.TestCase):
|
||||
def test_worker_routes_reject_interactive_principals(self) -> None:
|
||||
class Principal:
|
||||
auth_method = "session"
|
||||
|
||||
with self.assertRaises(HTTPException) as raised:
|
||||
_require_worker_principal(Principal()) # type: ignore[arg-type]
|
||||
self.assertEqual(403, raised.exception.status_code)
|
||||
|
||||
Principal.auth_method = "service_account"
|
||||
_require_worker_principal(Principal()) # type: ignore[arg-type]
|
||||
|
||||
def test_cross_origin_redirect_does_not_forward_authorization(self) -> None:
|
||||
forwarded_authorization: list[str | None] = []
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from govoplan_calendar.backend.caldav import (
|
||||
from govoplan_calendar.backend.db.models import CalendarOutboxOperation
|
||||
from govoplan_calendar.backend.outbox import (
|
||||
SqlCalendarOutboxProvider,
|
||||
calendar_outbox_operation_action_states,
|
||||
claim_next_calendar_outbox_operation,
|
||||
discard_calendar_outbox_operation,
|
||||
dispatch_calendar_outbox,
|
||||
@@ -508,6 +509,66 @@ class CalendarOutboxTests(unittest.TestCase):
|
||||
operation_id=first.id,
|
||||
)
|
||||
|
||||
def test_recovery_action_state_is_batched_and_respects_source_state(self) -> None:
|
||||
event = self.create_local_event(summary="First")
|
||||
self.session.commit()
|
||||
operation = self.session.query(CalendarOutboxOperation).one()
|
||||
|
||||
state = calendar_outbox_operation_action_states(
|
||||
self.session,
|
||||
[operation],
|
||||
)[operation.id]
|
||||
self.assertFalse(state["retry"]["allowed"])
|
||||
self.assertTrue(state["reconcile"]["allowed"])
|
||||
self.assertTrue(state["discard"]["allowed"])
|
||||
|
||||
operation.status = "conflict"
|
||||
self.source.sync_enabled = False
|
||||
self.session.flush()
|
||||
disabled = calendar_outbox_operation_action_states(
|
||||
self.session,
|
||||
[operation],
|
||||
)[operation.id]
|
||||
self.assertFalse(disabled["retry"]["allowed"])
|
||||
self.assertIn("disabled", str(disabled["retry"]["reason"]))
|
||||
self.assertFalse(disabled["reconcile"]["allowed"])
|
||||
self.assertTrue(disabled["discard"]["allowed"])
|
||||
|
||||
self.source.sync_enabled = True
|
||||
update_event(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
user_id=None,
|
||||
event_id=event.id,
|
||||
payload=CalendarEventUpdateRequest(summary="Second"),
|
||||
)
|
||||
self.session.flush()
|
||||
stale = calendar_outbox_operation_action_states(
|
||||
self.session,
|
||||
[operation],
|
||||
)[operation.id]
|
||||
self.assertFalse(any(item["allowed"] for item in stale.values()))
|
||||
self.assertIn("newer desired state", str(stale["discard"]["reason"]))
|
||||
|
||||
def test_manual_retry_cannot_be_repeated_while_pending(self) -> None:
|
||||
self.create_local_event()
|
||||
self.session.commit()
|
||||
operation = self.session.query(CalendarOutboxOperation).one()
|
||||
operation.status = "dead"
|
||||
self.session.flush()
|
||||
|
||||
retry_calendar_outbox_operation(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
operation_id=operation.id,
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "pending"):
|
||||
retry_calendar_outbox_operation(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
operation_id=operation.id,
|
||||
)
|
||||
|
||||
def test_discard_latest_cancels_attempted_predecessor_chain(self) -> None:
|
||||
event = self.create_local_event(summary="First")
|
||||
self.session.commit()
|
||||
|
||||
Reference in New Issue
Block a user