Files
Conflict_Checker/tests/api/test_review_email_flow.py
T
John Wilganowski 1c1d2ff21b
Docker Release / build-and-push (push) Successful in 1m10s
Docker Release / release (push) Skipped
Add required human review gate to the Agent pipeline.
Agent web jobs now stop after Brain consolidation and enter needs_review
with a persisted review queue (blocking: high-severity, low-confidence,
sensitive-category findings; audit sample of clean clusters). Humans
decide confirm/reject/unsure/needs_clarification via new review API and
frontend queue; a finalizer applies decisions (rejections suppressed with
reason codes), performs bounded targeted reruns for clarifications,
drafts RFIs only for kept issues, and only then marks the job done and
sends the final email. Two-phase email (review-required, then final
report), per-decision feedback labels with redacted aggregate metrics,
restart recovery from job artifacts, and CLI --no-review bypass.
Classic pipeline unchanged. 65 non-LLM tests.
2026-07-28 19:23:57 +00:00

107 lines
4.3 KiB
Python

"""Tests for the two-phase email flow: review-required notice, then final report."""
import threading
import backend.jobs
from backend.email_sender import send_review_required
class _SyncThread:
"""Drop-in threading.Thread replacement that runs the target inline."""
def __init__(self, target=None, args=(), **kwargs):
self._target = target
self._args = args
def start(self):
self._target(*self._args)
def test_review_required_email_skips_without_smtp(monkeypatch):
monkeypatch.setattr("backend.email_sender._smtp_ready", lambda: False)
assert send_review_required("user@example.com", {"source": "set.pdf", "summary": {}}, "http://localhost:8099/?job=abc") is False
def test_review_required_email_sends_with_smtp(monkeypatch):
"""With SMTP ready, the message goes out with recipient, review URL, and
the required-item count (0 when the report has no review summary)."""
sent = []
monkeypatch.setattr("backend.email_sender._smtp_ready", lambda: True)
monkeypatch.setattr("backend.email_sender._send",
lambda msg: sent.append(msg) or True)
review_url = "http://localhost:8099/?job=abc"
report = {"source": "set.pdf", "summary": {"review": {"required": 3}}}
assert send_review_required("user@example.com", report, review_url) is True
assert len(sent) == 1
msg = sent[0]
assert msg["To"] == "user@example.com"
assert "review" in msg["Subject"].lower()
body = msg.get_content()
assert "review" in body.lower()
assert review_url in body
assert "3" in body
# Missing review summary -> required count defaults to 0.
sent.clear()
assert send_review_required("user@example.com", {"source": "set.pdf", "summary": {}}, review_url) is True
assert "0" in sent[0].get_content()
def test_agent_needs_review_sends_review_email_not_report(monkeypatch, tmp_path):
"""An agent job entering needs_review emails the review-required notice
exactly once and never sends the final conflict report."""
review_emails = []
report_emails = []
monkeypatch.setattr("backend.config.OUTPUT_DIR", str(tmp_path))
monkeypatch.setattr("backend.jobs.run_agent_pipeline", lambda pdf_path, **kw: {
"summary": {"agent_status": "needs_review"}, "conflicts": [],
})
monkeypatch.setattr("backend.jobs.send_review_required",
lambda *a, **kw: review_emails.append((a, kw)))
monkeypatch.setattr("backend.jobs.send_conflict_report",
lambda *a, **kw: report_emails.append((a, kw)))
monkeypatch.setattr(threading, "Thread", _SyncThread)
pdf = tmp_path / "upload.pdf"
pdf.write_bytes(b"%PDF-1.4 dummy")
job_id = backend.jobs.create_job(str(pdf), source_filename="set.pdf",
email="arch@example.com", pipeline_mode="agent")
try:
job = backend.jobs.get_job(job_id)
assert job["status"] == "needs_review"
assert len(review_emails) == 1
args, _ = review_emails[0]
assert args[0] == "arch@example.com"
assert f"/?job={job_id}" in args[2]
assert report_emails == []
finally:
backend.jobs._jobs.pop(job_id, None)
def test_classic_job_sends_only_conflict_report(monkeypatch, tmp_path):
"""Classic pipeline is untouched: only the final report email fires."""
review_emails = []
report_emails = []
monkeypatch.setattr("backend.config.OUTPUT_DIR", str(tmp_path))
monkeypatch.setattr("backend.jobs.run_pipeline", lambda pdf_path, **kw: {
"summary": {}, "conflicts": [],
})
monkeypatch.setattr("backend.jobs.send_review_required",
lambda *a, **kw: review_emails.append((a, kw)))
monkeypatch.setattr("backend.jobs.send_conflict_report",
lambda *a, **kw: report_emails.append((a, kw)))
monkeypatch.setattr(threading, "Thread", _SyncThread)
pdf = tmp_path / "upload.pdf"
pdf.write_bytes(b"%PDF-1.4 dummy")
job_id = backend.jobs.create_job(str(pdf), source_filename="set.pdf",
email="arch@example.com", pipeline_mode="classic")
try:
assert backend.jobs.get_job(job_id)["status"] == "done"
assert len(report_emails) == 1
assert review_emails == []
finally:
backend.jobs._jobs.pop(job_id, None)