system-prompts-and-models-o.../dealix/tests/unit/test_revenue_memory.py
2026-05-01 14:03:52 +03:00

371 lines
16 KiB
Python

"""Smoke tests for the event-sourced Revenue Memory layer."""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import pytest
from auto_client_acquisition.revenue_memory.audit import dsr_export, full_audit_export
from auto_client_acquisition.revenue_memory.event_store import InMemoryEventStore
from auto_client_acquisition.revenue_memory.events import (
EVENT_TYPES,
event_from_dict,
event_to_dict,
make_event,
)
from auto_client_acquisition.revenue_memory.projections import (
build_account_timeline,
build_agent_ledger,
build_campaign_performance,
build_compliance_audit,
build_customer_roi,
build_deal_health,
)
from auto_client_acquisition.revenue_memory.replay import (
replay_agent_ledger,
replay_campaign,
replay_compliance_audit,
replay_deal_health,
replay_for_account,
replay_for_customer,
)
from auto_client_acquisition.revenue_memory.retention import (
LEGAL_HOLD_TYPES,
apply_retention,
classify_retention_tier,
is_expired,
retention_summary,
)
from auto_client_acquisition.revenue_memory.timeline import (
render_timeline_markdown,
timeline_to_dashboard_dict,
)
def _now():
return datetime.now(timezone.utc).replace(tzinfo=None)
# ── events.py ─────────────────────────────────────────────────────
def test_make_event_assigns_unique_id():
e1 = make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1")
e2 = make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1")
assert e1.event_id != e2.event_id
assert e1.event_id.startswith("evt_")
def test_make_event_unknown_type_raises():
with pytest.raises(ValueError):
make_event(event_type="totally.fake", customer_id="c", subject_type="x", subject_id="y")
def test_event_serialization_round_trip():
e = make_event(
event_type="deal.won",
customer_id="c1",
subject_type="deal",
subject_id="d1",
payload={"value_sar": 50000},
)
e2 = event_from_dict(event_to_dict(e))
assert e2.event_id == e.event_id
assert e2.payload == e.payload
assert e2.event_type == "deal.won"
def test_event_taxonomy_no_duplicates():
assert len(EVENT_TYPES) == len(set(EVENT_TYPES))
# ── event_store.py ────────────────────────────────────────────────
def test_event_store_append_and_count():
store = InMemoryEventStore()
e = make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1")
store.append(e)
assert store.count() == 1
assert store.count(customer_id="c1") == 1
assert store.count(customer_id="c2") == 0
def test_event_store_filters_by_subject():
store = InMemoryEventStore()
store.append(make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1"))
store.append(make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a2"))
a1_events = list(store.read_for_subject("account", "a1", customer_id="c1"))
assert len(a1_events) == 1
assert a1_events[0].subject_id == "a1"
def test_event_store_export_import():
store = InMemoryEventStore()
store.append(make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1"))
store.append(make_event(event_type="message.sent", customer_id="c1", subject_type="account", subject_id="a1"))
dump = store.export_all()
new_store = InMemoryEventStore()
new_store.import_all(dump)
assert new_store.count() == 2
# ── projections.py — account timeline ────────────────────────────
def test_account_timeline_replays_metrics():
n = _now()
events = [
make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n - timedelta(days=10)),
make_event(event_type="message.sent", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n - timedelta(days=8)),
make_event(event_type="reply.received", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n - timedelta(days=7)),
make_event(event_type="meeting.booked", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n - timedelta(days=5)),
make_event(event_type="signal.detected", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n - timedelta(days=4)),
]
timeline = build_account_timeline(customer_id="c1", account_id="a1", events=events)
assert timeline.n_messages_sent == 1
assert timeline.n_replies == 1
assert timeline.n_meetings == 1
assert timeline.n_signals == 1
assert timeline.first_seen is not None
assert timeline.last_activity > timeline.first_seen
def test_account_timeline_empty_returns_empty():
timeline = build_account_timeline(customer_id="c1", account_id="a1", events=[])
assert timeline.n_messages_sent == 0
assert timeline.first_seen is None
def test_timeline_markdown_render():
n = _now()
events = [
make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n),
]
timeline = build_account_timeline(customer_id="c1", account_id="a1", events=events)
md = render_timeline_markdown(timeline)
assert "Timeline" in md
assert "a1" in md
def test_timeline_dashboard_dict():
n = _now()
events = [make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n)]
timeline = build_account_timeline(customer_id="c1", account_id="a1", events=events)
d = timeline_to_dashboard_dict(timeline)
assert "metrics" in d
assert d["account_id"] == "a1"
# ── projections.py — deal health ──────────────────────────────────
def test_deal_health_basic():
n = _now()
events = [
make_event(event_type="deal.created", customer_id="c1", subject_type="deal", subject_id="d1",
occurred_at=n - timedelta(days=5), payload={"value_sar": 100000, "stage": "discovery"}),
make_event(event_type="deal.stage_changed", customer_id="c1", subject_type="deal", subject_id="d1",
occurred_at=n - timedelta(days=2), payload={"to_stage": "proposal"}),
]
proj = build_deal_health(customer_id="c1", deal_id="d1", events=events, now=n)
assert proj.value_sar == 100000
assert proj.current_stage == "proposal"
assert proj.days_in_current_stage == 2
assert len(proj.stage_history) == 2
def test_deal_health_stalled_flag():
n = _now()
events = [
make_event(event_type="deal.created", customer_id="c1", subject_type="deal", subject_id="d1",
occurred_at=n - timedelta(days=30), payload={"value_sar": 50000, "stage": "open"}),
]
proj = build_deal_health(customer_id="c1", deal_id="d1", events=events, now=n)
assert any(f.startswith("in_stage_") for f in proj.risk_flags)
def test_deal_health_won_zeroes_risk():
n = _now()
events = [
make_event(event_type="deal.created", customer_id="c1", subject_type="deal", subject_id="d1",
occurred_at=n - timedelta(days=10), payload={"value_sar": 100000}),
make_event(event_type="deal.won", customer_id="c1", subject_type="deal", subject_id="d1",
occurred_at=n, payload={}),
]
proj = build_deal_health(customer_id="c1", deal_id="d1", events=events, now=n)
assert proj.health_score == 100
assert proj.current_stage == "won"
# ── projections.py — campaign ────────────────────────────────────
def test_campaign_performance():
events = []
for _ in range(10):
events.append(make_event(event_type="message.sent", customer_id="c1", subject_type="campaign", subject_id="camp1",
payload={"campaign_id": "camp1"}))
for _ in range(2):
events.append(make_event(event_type="reply.received", customer_id="c1", subject_type="campaign", subject_id="camp1",
payload={"campaign_id": "camp1"}))
proj = build_campaign_performance(customer_id="c1", campaign_id="camp1", events=events)
assert proj.sent == 10
assert proj.replied == 2
assert proj.reply_rate == 0.2
# ── projections.py — agent ledger ────────────────────────────────
def test_agent_ledger_counts():
events = [
make_event(event_type="agent.action_requested", customer_id="c1", subject_type="agent_task", subject_id="t1",
payload={"agent_id": "outreach", "task_id": "t1", "requires_approval": True}),
make_event(event_type="agent.action_approved", customer_id="c1", subject_type="agent_task", subject_id="t1",
payload={"agent_id": "outreach", "task_id": "t1"}),
make_event(event_type="agent.action_executed", customer_id="c1", subject_type="agent_task", subject_id="t1",
payload={"agent_id": "outreach", "task_id": "t1"}),
]
ledger = build_agent_ledger(customer_id="c1", events=events)
assert ledger.by_agent.get("outreach") == 3
assert ledger.by_status.get("action_executed") == 1
assert ledger.requires_review == 1
# ── projections.py — compliance audit ────────────────────────────
def test_compliance_audit_counts_opt_outs():
events = [
make_event(event_type="compliance.consent_recorded", customer_id="c1", subject_type="contact", subject_id="x"),
make_event(event_type="compliance.opt_out_received", customer_id="c1", subject_type="contact", subject_id="y"),
make_event(event_type="compliance.blocked", customer_id="c1", subject_type="message", subject_id="m1",
payload={"reason": "no_consent"}),
]
audit = build_compliance_audit(customer_id="c1", events=events)
assert audit.consent_recorded == 1
assert audit.opt_outs == 1
assert audit.blocked_messages == 1
assert audit.last_block_reason == "no_consent"
# ── projections.py — customer ROI ────────────────────────────────
def test_customer_roi_aggregates():
events = [
make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1"),
make_event(event_type="meeting.booked", customer_id="c1", subject_type="meeting", subject_id="m1"),
make_event(event_type="deal.created", customer_id="c1", subject_type="deal", subject_id="d1",
payload={"value_sar": 50000}),
make_event(event_type="deal.won", customer_id="c1", subject_type="deal", subject_id="d1",
payload={"value_sar": 50000}),
]
roi = build_customer_roi(customer_id="c1", events=events)
assert roi.n_leads == 1
assert roi.n_meetings == 1
assert roi.n_deals_won == 1
assert roi.revenue_won_sar == 50000
# ── replay.py end-to-end ─────────────────────────────────────────
def test_replay_for_account_via_store():
store = InMemoryEventStore()
n = _now()
store.append(make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n))
store.append(make_event(event_type="message.sent", customer_id="c1", subject_type="account", subject_id="a1", occurred_at=n))
timeline = replay_for_account(customer_id="c1", account_id="a1", store=store)
assert timeline.n_messages_sent == 1
def test_replay_for_customer_via_store():
store = InMemoryEventStore()
store.append(make_event(event_type="deal.won", customer_id="c1", subject_type="deal", subject_id="d1",
payload={"value_sar": 25000}))
roi = replay_for_customer(customer_id="c1", store=store)
assert roi.revenue_won_sar == 25000
# ── retention.py ─────────────────────────────────────────────────
def test_retention_classifies_legal_hold():
assert classify_retention_tier("compliance.consent_recorded") == "legal_hold"
assert classify_retention_tier("compliance.opt_out_received") == "legal_hold"
def test_retention_classifies_operational():
assert classify_retention_tier("message.opened") == "operational"
assert classify_retention_tier("signal.detected") == "operational"
def test_retention_default_is_business_record():
assert classify_retention_tier("lead.created") == "business_record"
assert classify_retention_tier("deal.won") == "business_record"
def test_retention_legal_hold_never_expires():
n = _now()
e = make_event(
event_type="compliance.consent_recorded",
customer_id="c1", subject_type="contact", subject_id="x",
occurred_at=n - timedelta(days=365 * 100), # 100 years
)
assert is_expired(e, now=n) is False
def test_apply_retention_keeps_legal_hold_drops_old_business():
n = _now()
legal = make_event(
event_type="compliance.opt_out_received",
customer_id="c1", subject_type="contact", subject_id="x",
occurred_at=n - timedelta(days=365 * 5),
)
old_lead = make_event(
event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a",
occurred_at=n - timedelta(days=365 * 5),
)
fresh = make_event(
event_type="lead.created", customer_id="c1", subject_type="account", subject_id="b",
)
kept, removed = apply_retention([legal, old_lead, fresh], now=n)
kept_ids = {e.event_id for e in kept}
assert legal.event_id in kept_ids
assert fresh.event_id in kept_ids
assert old_lead.event_id in removed
def test_apply_retention_tombstones_old_operational():
n = _now()
old_op = make_event(
event_type="message.opened",
customer_id="c1", subject_type="message", subject_id="m1",
occurred_at=n - timedelta(days=200),
payload={"ip": "192.0.2.1"},
)
kept, removed = apply_retention([old_op], now=n)
assert len(kept) == 1
assert kept[0].event_type.endswith(".tombstoned")
assert kept[0].payload.get("_tombstoned") is True
# Original PII (ip) is gone
assert "ip" not in kept[0].payload
def test_retention_summary_counts():
n = _now()
events = [
make_event(event_type="message.opened", customer_id="c1", subject_type="message", subject_id="m"),
make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a"),
make_event(event_type="compliance.consent_recorded", customer_id="c1", subject_type="contact", subject_id="x"),
]
s = retention_summary(events)
assert s["operational"] == 1
assert s["business_record"] == 1
assert s["legal_hold"] == 1
# ── audit.py ─────────────────────────────────────────────────────
def test_full_audit_export_filters_customer():
events = [
make_event(event_type="lead.created", customer_id="c1", subject_type="account", subject_id="a"),
make_event(event_type="lead.created", customer_id="c2", subject_type="account", subject_id="b"),
]
out = full_audit_export(customer_id="c1", events=events)
assert len(out) == 1
assert out[0]["customer_id"] == "c1"
def test_dsr_export_finds_subject_by_id_or_payload():
events = [
make_event(event_type="lead.created", customer_id="c1", subject_type="contact", subject_id="ali@example.sa"),
make_event(event_type="message.sent", customer_id="c1", subject_type="message", subject_id="m1",
payload={"contact_id": "ali@example.sa"}),
]
out = dsr_export(customer_id="c1", data_subject_id="ali@example.sa", events=events)
assert out["n_events"] == 2
assert out["right_invoked"].startswith("Right of access")