Communications tab: show matched investors only (v0.1.0:81)
The email-activity panel surfaced every captured message, including cold/ unknown-sender email with no investor association. Gate query_email_activity on EXISTS(email_investor_links) so the panel shows only email tied to a known investor/contact. Capture is unchanged — unmatched email is still stored (metadata-only) and will appear automatically if its sender is later added as an investor; this is a read-side filter only. Graveyard investors are unaffected (their email has a link), so they remain visible/searchable as an audit surface, hidden only from the filter picker.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test the admin-only email-activity panel (Communications tab, v0.1.0:80).
|
||||
|
||||
Covers the pure query (`db.query_email_activity`): investor/mailbox/search/direction
|
||||
Covers the pure query (`db.query_email_activity`): matched-only scope (unmatched
|
||||
cold/unknown-sender email is never surfaced), investor/mailbox/search/direction
|
||||
filters, per-sighting soft-delete, direction at the email level, mailbox + investor
|
||||
roll-ups (incl. unmatched fallback to the matched address), and the filter facets.
|
||||
roll-ups, and the filter facets.
|
||||
Also asserts the route handler enforces admin server-side. Synthetic data only.
|
||||
|
||||
Run: cd backend && python3 email_integration/test_email_activity_panel.py
|
||||
@@ -55,7 +56,7 @@ def make_db():
|
||||
# e1 outbound (from us) -> Harbor, seen by grant
|
||||
# e2 inbound (from LP) -> Harbor, seen by grant + jonathan
|
||||
# e3 inbound (from LP) -> Pacific via contact link, seen by jonathan
|
||||
# e4 inbound, UNMATCHED (no investor link), seen by grant
|
||||
# e4 inbound, UNMATCHED (no investor link), seen by grant -> must be excluded (matched-only)
|
||||
# e5 inbound, only sighting is tombstoned -> must be excluded
|
||||
conn.executemany(
|
||||
"INSERT INTO emails (id,subject,from_name,from_email,sent_at,snippet,has_attachments,is_matched,match_status) VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
@@ -98,10 +99,12 @@ def ids(res):
|
||||
def main():
|
||||
conn = make_db()
|
||||
|
||||
# --- baseline: live emails only, newest first, tombstoned excluded ---
|
||||
# --- baseline: matched live emails only, newest first, tombstoned excluded ---
|
||||
res = _db.query_email_activity(conn)
|
||||
check(ids(res) == ["e4", "e3", "e2", "e1", "e6"], f"live emails newest-first, e5 (tombstoned) excluded; got {ids(res)}")
|
||||
check(res["count"] == 5 and res["truncated"] is False, "count + not truncated")
|
||||
check(ids(res) == ["e3", "e2", "e1", "e6"],
|
||||
f"matched live emails newest-first; e5 (tombstoned) + e4 (unmatched) excluded; got {ids(res)}")
|
||||
check(res["count"] == 4 and res["truncated"] is False, "count + not truncated")
|
||||
check("e4" not in ids(res), "unmatched email (no investor link) never surfaces in the panel")
|
||||
|
||||
# --- direction at the email level ---
|
||||
e1 = next(e for e in res["emails"] if e["id"] == "e1")
|
||||
@@ -111,8 +114,8 @@ def main():
|
||||
check(_db.query_email_activity(conn, direction="outbound")["emails"][0]["id"] == "e1"
|
||||
and len(_db.query_email_activity(conn, direction="outbound")["emails"]) == 1,
|
||||
"direction=outbound returns only e1")
|
||||
check(ids(_db.query_email_activity(conn, direction="inbound")) == ["e4", "e3", "e2", "e6"],
|
||||
"direction=inbound excludes the outbound e1")
|
||||
check(ids(_db.query_email_activity(conn, direction="inbound")) == ["e3", "e2", "e6"],
|
||||
"direction=inbound excludes the outbound e1 (and unmatched e4)")
|
||||
|
||||
# --- mailbox roll-up + per-account filter ---
|
||||
check(set(e2["mailboxes"]) == {"grant@ten31.xyz", "jonathan@ten31.xyz"}, "e2 seen by both mailboxes")
|
||||
@@ -129,13 +132,14 @@ def main():
|
||||
check(e1["investors"] == [{"id": "inv-harbor", "name": "Harbor & Vine"}], "e1 investor resolved to name")
|
||||
e3 = next(e for e in res["emails"] if e["id"] == "e3")
|
||||
check(e3["investors"] == [{"id": "inv-pacific", "name": "Pacific Capital"}], "e3 investor resolved via contact")
|
||||
e4 = next(e for e in res["emails"] if e["id"] == "e4")
|
||||
check(e4["investors"] == [] and e4["investor_labels"] == [], "e4 unmatched -> no investor, no link")
|
||||
check("e4" not in ids(res), "e4 unmatched -> excluded from the matched-only panel")
|
||||
|
||||
# --- free-text search over subject / snippet / sender ---
|
||||
check(set(ids(_db.query_email_activity(conn, search="Fund III"))) == {"e1", "e2"}, "search subject")
|
||||
check(ids(_db.query_email_activity(conn, search="pacificcap")) == ["e3"], "search sender address")
|
||||
check(ids(_db.query_email_activity(conn, search="buy now")) == ["e4"], "search snippet")
|
||||
check(ids(_db.query_email_activity(conn, search="deck")) == ["e1"], "search snippet (matched email)")
|
||||
check(ids(_db.query_email_activity(conn, search="buy now")) == [],
|
||||
"unmatched email never surfaces, even by free-text search")
|
||||
|
||||
# --- facets ---
|
||||
check([a["email_address"] for a in res["accounts"]] == ["grant@ten31.xyz", "jonathan@ten31.xyz"],
|
||||
|
||||
Reference in New Issue
Block a user