File size: 3,097 Bytes
831da15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""
check_callback_edit.py -- offline: a callback date/time can be set from the Edit form, and it reads back as
the exact wall-clock time that was typed (user, 2026-09-24).

    uv run python scripts/check_callback_edit.py

No database, no network -- throwaway in-memory SQLite. Covers:
  * a Callback Scheduled recorded WITHOUT a time (what the old production button did) leaves callback_at empty
  * UpdateLeadInput now accepts callback_at, and update_lead() saves it
  * LeadOut serializes it as naive wall-clock ISO (no "Z"/offset) -- it used to go through _dt_utc, which made
    the browser shift a typed 3:00 PM to 8:00 PM in PKT
  * an unrelated edit does NOT clear an existing callback_at (set-only, like every field on that model)
"""

from __future__ import annotations

import sys
from datetime import datetime
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from sqlalchemy import create_engine  # noqa: E402
from sqlalchemy.orm import sessionmaker  # noqa: E402
from sqlalchemy.pool import StaticPool  # noqa: E402

import app.models  # noqa: E402,F401
from app.crud import leads as crud_leads  # noqa: E402
from app.database import Base  # noqa: E402
from app.schemas.lead import LeadOut, UpdateLeadInput  # noqa: E402

FAILED = 0


def check(label: str, ok: bool, detail: str = "") -> None:
    global FAILED
    FAILED += 0 if ok else 1
    print(f"  [{'ok  ' if ok else 'FAIL'}] {label}" + ("" if ok else f"   {detail}"))


def main() -> int:
    engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
    Base.metadata.create_all(engine)
    db = sessionmaker(bind=engine)()

    lead = crud_leads.add_lead_dict(db, {"company_name": "Callback Co"})
    crud_leads.apply_call_outcome(db, lead, "callback_scheduled")  # no callback_at, like the old button
    print("\nCallback recorded without a time")
    check("callback_at is empty", lead.callback_at is None)
    check("last_call_outcome is callback_scheduled", lead.last_call_outcome == "callback_scheduled")

    print("\nSet a time from the Edit form (UpdateLeadInput.callback_at)")
    typed = datetime(2026, 9, 25, 15, 0, 0)
    lead = crud_leads.update_lead(db, lead, UpdateLeadInput(callback_at=typed))
    check("callback_at saved as typed", lead.callback_at == typed, str(lead.callback_at))
    out = LeadOut.from_model(lead)
    check("LeadOut.callback_at is naive wall-clock ISO", out.callback_at == "2026-09-25T15:00:00", out.callback_at)
    check("no Z / UTC offset on it", "Z" not in out.callback_at and "+" not in out.callback_at)
    check("stage untouched by setting it", lead.pipeline_stage == "followup_due", lead.pipeline_stage)

    print("\nUnrelated edit leaves the callback alone")
    lead = crud_leads.update_lead(db, lead, UpdateLeadInput(subject="New subject"))
    check("callback_at still set", lead.callback_at == typed, str(lead.callback_at))

    print(f"\n{'FAILED: ' + str(FAILED) if FAILED else 'All checks passed.'}")
    return 1 if FAILED else 0


if __name__ == "__main__":
    sys.exit(main())