feat: backdated reported date for old active tickets
Wahab can now enter historical tickets that keep their true reported date: - Add nullable tickets.reported_at (DateTime) via alembic d5e0f2a1c3b4; backfill existing rows from created_at so nothing shows empty. - TicketCreate.reported_at (optional) is persisted by create_ticket and defaults to now when omitted, so existing create behavior is unchanged. - New-issue form gains a 'Reported Date' date picker (defaults to today, past dates allowed, future blocked) and sends reported_at in the payload. - List and detail pages show 'Reported' next to 'Created' (date-only, labeled) so backdated tickets are obvious; SLA deadline is unchanged and still runs from creation time so backfilling never instantly breaches. - Tests: backdated create persists + is exposed on list/detail; omitted reported_at defaults to now; SLA window unchanged; full datetime round trip.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""Tests for backdated reported-date support (Wahab demo).
|
||||
|
||||
Anchors:
|
||||
* A ticket created with a past ``reported_at`` persists that date and it is
|
||||
exposed on list + detail responses — this is how Wahab enters old tickets
|
||||
that stay active in the normal workflow.
|
||||
* A ticket created without ``reported_at`` defaults to "now", so existing
|
||||
create behavior is unchanged.
|
||||
* The reported date is metadata only: SLA deadlines still run from creation
|
||||
time and no age/backdate restriction kicks in.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
def _naive(iso: str) -> datetime:
|
||||
"""Parse an ISO datetime and strip any tz offset for safe comparison."""
|
||||
dt = datetime.fromisoformat(iso)
|
||||
return dt.replace(tzinfo=None) if dt.tzinfo is not None else dt
|
||||
|
||||
|
||||
async def _login(client, email="wahab@denya.com", password="denya123") -> str:
|
||||
resp = await client.post(
|
||||
"/api/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["access_token"]
|
||||
|
||||
|
||||
async def _create_ticket(client, token: str, **overrides) -> dict:
|
||||
payload = {
|
||||
"unit_id": 2,
|
||||
"category_id": 3,
|
||||
"priority": "medium",
|
||||
"reporter": "Backdate Test",
|
||||
"reported_via": "walk-in",
|
||||
"description": "backdate test ticket",
|
||||
**overrides,
|
||||
}
|
||||
resp = await client.post(
|
||||
"/api/tickets",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def test_create_with_backdated_reported_at_persists(client):
|
||||
"""Wahab (Admin/Wahab) can enter an old ticket and its date sticks."""
|
||||
token = await _login(client)
|
||||
ticket = await _create_ticket(
|
||||
client,
|
||||
token,
|
||||
reported_at="2026-07-20",
|
||||
description="Old plumbing issue reported weeks ago",
|
||||
)
|
||||
assert ticket["reported_at"] is not None
|
||||
assert ticket["reported_at"].startswith("2026-07-20")
|
||||
|
||||
# Still an active ticket in the normal workflow — no age restriction.
|
||||
assert ticket["status"] in {"New", "Logged"}
|
||||
|
||||
# Detail endpoint exposes the reported date.
|
||||
detail = await client.get(f"/api/tickets/{ticket['id']}")
|
||||
assert detail.status_code == 200
|
||||
assert detail.json()["reported_at"].startswith("2026-07-20")
|
||||
|
||||
# List endpoint exposes it too.
|
||||
listing = await client.get("/api/tickets")
|
||||
assert listing.status_code == 200
|
||||
listed = next(t for t in listing.json()["items"] if t["id"] == ticket["id"])
|
||||
assert listed["reported_at"].startswith("2026-07-20")
|
||||
|
||||
|
||||
async def test_create_without_reported_at_defaults_to_now(client):
|
||||
"""Omitting reported_at behaves exactly as before: reported == created."""
|
||||
token = await _login(client)
|
||||
ticket = await _create_ticket(client, token, description="normal today ticket")
|
||||
assert ticket["reported_at"] is not None
|
||||
reported = _naive(ticket["reported_at"])
|
||||
created = _naive(ticket["created_at"])
|
||||
assert abs((reported - created).total_seconds()) < 60
|
||||
|
||||
|
||||
async def test_reported_at_does_not_shift_sla_deadline(client):
|
||||
"""SLA computation is unchanged: deadlines run from creation time."""
|
||||
token = await _login(client)
|
||||
ticket = await _create_ticket(
|
||||
client,
|
||||
token,
|
||||
priority="urgent",
|
||||
reported_at="2026-01-01",
|
||||
description="old urgent ticket",
|
||||
)
|
||||
assert ticket["sla_deadline"] is not None
|
||||
created = _naive(ticket["created_at"])
|
||||
deadline = _naive(ticket["sla_deadline"])
|
||||
hours = (deadline - created).total_seconds() / 3600
|
||||
assert 3.5 <= hours <= 4.5 # urgent → 4 h resolution window from creation
|
||||
|
||||
|
||||
async def test_reported_at_round_trips_full_datetime(client):
|
||||
"""A precise datetime (not just a date) survives the round trip."""
|
||||
token = await _login(client)
|
||||
reported = "2026-07-20T14:30:00"
|
||||
ticket = await _create_ticket(client, token, reported_at=reported, description="datetime round trip")
|
||||
assert ticket["reported_at"] is not None
|
||||
parsed = _naive(ticket["reported_at"])
|
||||
assert parsed.date().isoformat() == "2026-07-20"
|
||||
assert parsed.hour == 14 and parsed.minute == 30
|
||||
Reference in New Issue
Block a user