1564c087bf
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.
73 lines
3.0 KiB
Python
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()
|