import json from unittest import mock from projektmatch import ingest from projektmatch.config import Cfg PROJECT_HTML = ('' 'Projekt Eins') def cfg(tmp_path): return Cfg(imap_password="pw", alert_to="a@x", flow2_id="F2", langflow_api_key="key", data_dir=str(tmp_path)) def run(tmp_path, mails, dup=None, dispatch_result=None): box = mock.Mock() box.unchecked_uids.return_value = list(mails) box.fetch.side_effect = lambda uid: mails[uid] espo = mock.Mock() espo.find_opportunity_by_link.return_value = dup sent = [] with mock.patch("projektmatch.ingest.mailparse.bodies", side_effect=lambda m: (m["html"], "")), \ mock.patch("projektmatch.ingest.mailer.send_mail", lambda *a: sent.append(a)): summary = ingest.run_ingest( cfg(tmp_path), mailbox=box, espo=espo, resolve=lambda url: "https://www.freelancermap.de/projekt/eins", dispatch=lambda item: dispatch_result or {"status": "created"}) return summary, box, sent def mail(subject, html=""): return {"Subject": subject, "html": html} def test_non_project_mail_flagged_not_trashed(tmp_path): mails = {"1": mail("Newsletter", "
Hallo
")} summary, box, sent = run(tmp_path, mails) box.flag_checked.assert_called_once_with("1") box.move_to_trash.assert_not_called() assert summary["mails"] == 0 and not sent def test_own_mail_skipped(tmp_path): mails = {"1": mail("[Projekt-Match] Freelancermap — 90 % — X", PROJECT_HTML)} summary, box, sent = run(tmp_path, mails) box.flag_checked.assert_called_once_with("1") box.move_to_trash.assert_not_called() def test_project_mail_created_and_trashed(tmp_path): mails = {"1": mail("Example 1 for Langflow process", PROJECT_HTML)} summary, box, sent = run(tmp_path, mails) assert summary == {"mails": 1, "projects": 1, "created": 1, "rejected": 0, "duplicate": 0, "failed": 0} box.move_to_trash.assert_called_once_with("1") assert not sent def test_duplicate_skips_dispatch(tmp_path): mails = {"1": mail("Mail", PROJECT_HTML)} summary, box, sent = run(tmp_path, mails, dup={"id": "X"}) assert summary["duplicate"] == 1 and summary["created"] == 0 box.move_to_trash.assert_called_once() def test_failed_dispatch_alerts_and_trashes(tmp_path): mails = {"1": mail("Mail", PROJECT_HTML)} summary, box, sent = run(tmp_path, mails, dispatch_result={"status": "failed", "error": "vLLM down"}) assert summary["failed"] == 1 assert len(sent) == 1 _, _, to, subject, body = sent[0] assert to == "a@x" assert subject == "[Projekt-Match-Fehler] Freelancermap — 1 Projekt(e)" assert "vLLM down" in body box.move_to_trash.assert_called_once() def test_lock_prevents_second_run(tmp_path): lock = ingest.acquire_lock(str(tmp_path)) assert lock is not None summary = ingest.run_ingest(cfg(tmp_path), mailbox=mock.Mock()) assert summary == {"skipped": "locked"} ingest.release_lock(lock) def test_parse_run_result_finds_status_json(): inner = json.dumps({"status": "created", "mustMatch": 90}) payload = {"outputs": [{"outputs": [{"results": {"text": { "data": {"text": inner}}}}]}]} assert ingest.parse_run_result(payload)["status"] == "created"