#!/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()