a10889b10b
Three post-smoke refinements to the Matrix email-proposal review:
1. Dash separators (bot): every card/reply is framed with a dash rule top and
bottom so threads stop bleeding together vertically on mobile.
2. Remove decided threads (bot): on a conclusive approve/dismiss from either
surface, the bot redacts the card (client.room_redact) so the room clears
down to only undecided items. Redacting the bot's own card needs no power;
the web->Matrix path now redacts instead of posting a closure note.
3. Clearer note wording (server v91 + bot): the proposed grid note now names who
emailed whom -- "{teammate} emailed {investor}" (outbound) / "{sender} emailed
the team" (inbound) -- instead of an ambiguous "Sent"/"Received". Outbound
detection also matches our corporate domain (public providers excluded), so a
teammate's mail from a non-enrolled @ten31.xyz address no longer reads as
"Received". Going-forward only; no schema change. The card drops its bare
direction label since the note now carries the relationship.
Tests updated; 30/30 green, render-smoke green.
79 lines
3.2 KiB
Python
79 lines
3.2 KiB
Python
"""Offline tests for the email-proposal review logic (card render, framing, reply grammar, note
|
|
revision). The network/Matrix wiring lives in bot.py (live-smoke only); this covers pure functions."""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
import email_proposals # noqa: E402
|
|
|
|
ITEM = {
|
|
"id": "p1", "investor_name": "Acme Capital", "direction": "received",
|
|
"from_name": "Jane Doe", "from_email": "jane@acme.com",
|
|
"email_subject": "Re: Fund III", "email_date": "2026-06-02",
|
|
"snippet": "thanks for the deck — one question on terms",
|
|
"proposed_note": "✉ Jane Doe emailed the team: asked about terms",
|
|
}
|
|
|
|
|
|
def test_interpret_yes_no_else():
|
|
assert email_proposals.interpret("yes") == "approve"
|
|
assert email_proposals.interpret(" Y ") == "approve"
|
|
assert email_proposals.interpret("✅") == "approve"
|
|
assert email_proposals.interpret("no") == "reject"
|
|
assert email_proposals.interpret("skip") == "reject"
|
|
# anything that isn't a clear yes/no is treated as a revision instruction
|
|
assert email_proposals.interpret("say we discussed the Q3 raise") == "revise"
|
|
|
|
|
|
def test_frame_wraps_with_rules():
|
|
out = email_proposals.frame("hello")
|
|
lines = out.split("\n")
|
|
assert lines[0] == email_proposals.RULE and lines[-1] == email_proposals.RULE
|
|
assert "hello" in out
|
|
|
|
|
|
def test_render_card_has_context_note_and_actions():
|
|
card = email_proposals.render_card(ITEM)
|
|
assert "Acme Capital" in card
|
|
assert "Jane Doe" in card
|
|
assert "Re: Fund III" in card and "2026-06-02" in card
|
|
assert "thanks for the deck" in card
|
|
assert "Jane Doe emailed the team: asked about terms" in card # the clear, named note
|
|
assert "yes" in card.lower() and "no" in card.lower()
|
|
|
|
|
|
def test_render_card_is_framed_and_dropless_direction():
|
|
card = email_proposals.render_card(ITEM)
|
|
assert card.startswith(email_proposals.RULE) and card.rstrip().endswith(email_proposals.RULE)
|
|
# the bare Sent/Received label is gone — the note itself names who emailed whom
|
|
assert "(Received)" not in card and "(Sent)" not in card
|
|
|
|
|
|
def test_render_card_truncates_long_snippet():
|
|
card = email_proposals.render_card(dict(ITEM, snippet="x" * 1000))
|
|
assert "…" in card and len(card) < 1000
|
|
|
|
|
|
def test_revise_note_applies_model_output():
|
|
out = email_proposals.revise_note(
|
|
"old note", "make it about the Q3 raise",
|
|
parse_fn=lambda prompt, system=None, max_tokens=400: {"note": "Discussed the Q3 raise."})
|
|
assert out == "Discussed the Q3 raise."
|
|
|
|
|
|
def test_revise_note_noop_or_empty_returns_none():
|
|
# model echoes the same note unchanged -> None so the caller re-prompts (not "Updated")
|
|
assert email_proposals.revise_note("same", "x", parse_fn=lambda *a, **k: {"note": "same"}) is None
|
|
# model returns nothing usable -> None
|
|
assert email_proposals.revise_note("n", "y", parse_fn=lambda *a, **k: {}) is None
|
|
assert email_proposals.revise_note("n", "y", parse_fn=lambda *a, **k: None) is None
|
|
|
|
|
|
if __name__ == "__main__":
|
|
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
|
|
for fn in fns:
|
|
fn()
|
|
print(f"ok {fn.__name__}")
|
|
print(f"\n{len(fns)} passed")
|