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.
107 lines
4.3 KiB
Python
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)
|