Files
ten31-database/backend/email_integration/test_sync_ready.py
T
Keysat 1564c087bf Remove Instructions/Feedback + lp_profiles; sync retry, purge, mobile fixes (v0.1.0:104)
Removals (net -570 lines):
- Delete the Instructions and Feedback (feature_requests) pages + backend.
- Retire lp_profiles + investor_type across server, ingest, and seeds; migration
  0008 drops both empty tables (a sanctioned one-off exception to
  never-hard-delete). 0001's lp_profiles ALTER is removed so a fresh DB doesn't
  break the migration chain (live DBs already applied it).

Fixes:
- Email sync: a transient timeout no longer terminally parks a mailbox; the
  scheduler retries 'retrying' each cycle and re-includes errored accounts on an
  hourly backoff, so stuck mailboxes self-heal.
- Mobile Contacts: page through the full directory (server caps 500/page) -- one
  fetch silently truncated at 720, hiding people from the list and from search.
- Mobile email review: clock icon to set a reminder inline; approval cards show
  date/time.

New:
- Admin-only purge of soft-deleted rows (Settings -> Admin; type-to-confirm,
  refuses any row still linked to live data).

Tests: 45/45 (adds test_sync_ready + test_purge_soft_deleted). Reviewer pass
applied (NULL reminders.contact_id on contact purge). Bumped to v0.1.0:104.
2026-06-20 20:06:11 -05:00

73 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Regression test for list_sync_ready_accounts (v0.1.0:104).
Guards the Bug-B fix: a transient network timeout used to flip a mailbox to terminal
sync_status='error', which the old `IN ('pending','active')` filter excluded forever —
so a single blip dark-listed a mailbox until a manual kick. The new filter:
* always includes 'pending' / 'active' / 'retrying' (the transient-retry state), and
* re-includes 'error' accounts whose last attempt was over an hour ago (gentle backoff,
so a fixed credential self-heals and the pre-fix stuck mailboxes recover on deploy).
Synthetic data only (guardrail #9).
Run: cd backend && python3 email_integration/test_sync_ready.py
"""
import os
import sqlite3
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from email_integration import db as edb # noqa: E402
FAILS = []
def check(cond, msg):
print((" PASS " if cond else " FAIL ") + msg)
if not cond:
FAILS.append(msg)
def make_account(conn, email, status, *, enabled=1, last_synced_sql="NULL"):
aid = edb.upsert_account(conn, user_id="u-" + email,
email_address=email, auth_method="dwd")
conn.execute(
f"UPDATE email_accounts SET sync_status=?, sync_enabled=?, "
f"last_synced_at={last_synced_sql} WHERE id=?",
(status, enabled, aid),
)
conn.commit()
return aid
def main():
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
edb.apply_migrations(conn.cursor())
make_account(conn, "active@t.xyz", "active", last_synced_sql="datetime('now','-5 minutes')")
make_account(conn, "retrying@t.xyz", "retrying", last_synced_sql="datetime('now','-5 minutes')")
make_account(conn, "pending@t.xyz", "pending", last_synced_sql="NULL")
make_account(conn, "error_stale@t.xyz", "error", last_synced_sql="datetime('now','-2 hours')")
make_account(conn, "error_recent@t.xyz", "error", last_synced_sql="datetime('now','-5 minutes')")
make_account(conn, "error_neversync@t.xyz", "error", last_synced_sql="NULL")
make_account(conn, "disabled@t.xyz", "active", enabled=0, last_synced_sql="datetime('now','-5 minutes')")
ready = {r["email_address"] for r in edb.list_sync_ready_accounts(conn)}
check("active@t.xyz" in ready, "healthy 'active' is ready")
check("retrying@t.xyz" in ready, "transient 'retrying' is ready (fast retry)")
check("pending@t.xyz" in ready, "'pending' is ready")
check("error_stale@t.xyz" in ready, "'error' last attempted >1h ago is ready (backoff elapsed → recovers stuck mailbox)")
check("error_neversync@t.xyz" in ready, "'error' never synced (NULL last attempt) is ready")
check("error_recent@t.xyz" not in ready, "'error' attempted <1h ago is NOT ready (gentle backoff, no hammering)")
check("disabled@t.xyz" not in ready, "sync_enabled=0 is never ready")
print()
if FAILS:
print(f"{len(FAILS)} FAILED")
sys.exit(1)
print("ALL PASS (sync ready filter)")
if __name__ == "__main__":
main()