diff --git a/dev-api.py b/dev-api.py index 3ec042d..ad99278 100644 --- a/dev-api.py +++ b/dev-api.py @@ -445,6 +445,60 @@ def reclassify_signal(signal_id: int, body: ReclassifyBody): return {"ok": True} +# ── Digest queue models ─────────────────────────────────────────────────── + +class DigestQueueBody(BaseModel): + job_contact_id: int + + +# ── GET /api/digest-queue ───────────────────────────────────────────────── + +@app.get("/api/digest-queue") +def list_digest_queue(): + db = _get_db() + rows = db.execute( + """SELECT dq.id, dq.job_contact_id, dq.created_at, + jc.subject, jc.from_addr, jc.received_at, jc.body + FROM digest_queue dq + JOIN job_contacts jc ON jc.id = dq.job_contact_id + ORDER BY dq.created_at DESC""" + ).fetchall() + db.close() + return [ + { + "id": r["id"], + "job_contact_id": r["job_contact_id"], + "created_at": r["created_at"], + "subject": r["subject"], + "from_addr": r["from_addr"], + "received_at": r["received_at"], + "body": _strip_html(r["body"]), + } + for r in rows + ] + + +# ── POST /api/digest-queue ──────────────────────────────────────────────── + +@app.post("/api/digest-queue") +def add_to_digest_queue(body: DigestQueueBody): + db = _get_db() + exists = db.execute( + "SELECT 1 FROM job_contacts WHERE id = ?", (body.job_contact_id,) + ).fetchone() + if not exists: + db.close() + raise HTTPException(404, "job_contact_id not found") + result = db.execute( + "INSERT OR IGNORE INTO digest_queue (job_contact_id) VALUES (?)", + (body.job_contact_id,), + ) + db.commit() + created = result.rowcount > 0 + db.close() + return {"ok": True, "created": created} + + # ── POST /api/jobs/{id}/move ─────────────────────────────────────────────────── STATUS_TIMESTAMP_COL = { diff --git a/tests/test_dev_api_digest.py b/tests/test_dev_api_digest.py new file mode 100644 index 0000000..66b1aa2 --- /dev/null +++ b/tests/test_dev_api_digest.py @@ -0,0 +1,110 @@ +"""Tests for digest queue API endpoints.""" +import sqlite3 +import os +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture() +def tmp_db(tmp_path): + """Create minimal schema in a temp dir with one job_contacts row.""" + db_path = str(tmp_path / "staging.db") + con = sqlite3.connect(db_path) + con.executescript(""" + CREATE TABLE jobs ( + id INTEGER PRIMARY KEY, + title TEXT, company TEXT, url TEXT UNIQUE, location TEXT, + is_remote INTEGER DEFAULT 0, salary TEXT, + match_score REAL, keyword_gaps TEXT, status TEXT DEFAULT 'pending', + date_found TEXT, description TEXT, source TEXT + ); + CREATE TABLE job_contacts ( + id INTEGER PRIMARY KEY, + job_id INTEGER, + subject TEXT, + received_at TEXT, + stage_signal TEXT, + suggestion_dismissed INTEGER DEFAULT 0, + body TEXT, + from_addr TEXT + ); + CREATE TABLE digest_queue ( + id INTEGER PRIMARY KEY, + job_contact_id INTEGER NOT NULL REFERENCES job_contacts(id), + created_at TEXT DEFAULT (datetime('now')), + UNIQUE(job_contact_id) + ); + INSERT INTO jobs (id, title, company, url, status, source, date_found) + VALUES (1, 'Engineer', 'Acme', 'https://acme.com/job/1', 'applied', 'test', '2026-03-19'); + INSERT INTO job_contacts (id, job_id, subject, received_at, stage_signal, body, from_addr) + VALUES ( + 10, 1, 'TechCrunch Jobs Weekly', '2026-03-19T10:00:00', 'digest', + 'Apply at Senior Engineer or Staff Designer. Unsubscribe: https://unsubscribe.example.com/remove', + 'digest@techcrunch.com' + ); + """) + con.close() + return db_path + + +@pytest.fixture() +def client(tmp_db, monkeypatch): + monkeypatch.setenv("STAGING_DB", tmp_db) + import importlib + import dev_api + importlib.reload(dev_api) + return TestClient(dev_api.app) + + +# ── GET /api/digest-queue ─────────────────────────────────────────────────── + +def test_digest_queue_list_empty(client): + resp = client.get("/api/digest-queue") + assert resp.status_code == 200 + assert resp.json() == [] + + +def test_digest_queue_list_with_entry(client, tmp_db): + con = sqlite3.connect(tmp_db) + con.execute("INSERT INTO digest_queue (job_contact_id) VALUES (10)") + con.commit() + con.close() + + resp = client.get("/api/digest-queue") + assert resp.status_code == 200 + entries = resp.json() + assert len(entries) == 1 + assert entries[0]["job_contact_id"] == 10 + assert entries[0]["subject"] == "TechCrunch Jobs Weekly" + assert entries[0]["from_addr"] == "digest@techcrunch.com" + assert "body" in entries[0] + assert "created_at" in entries[0] + + +# ── POST /api/digest-queue ────────────────────────────────────────────────── + +def test_digest_queue_add(client, tmp_db): + resp = client.post("/api/digest-queue", json={"job_contact_id": 10}) + assert resp.status_code == 200 + data = resp.json() + assert data["ok"] is True + assert data["created"] is True + + con = sqlite3.connect(tmp_db) + row = con.execute("SELECT * FROM digest_queue WHERE job_contact_id = 10").fetchone() + con.close() + assert row is not None + + +def test_digest_queue_add_duplicate(client): + client.post("/api/digest-queue", json={"job_contact_id": 10}) + resp = client.post("/api/digest-queue", json={"job_contact_id": 10}) + assert resp.status_code == 200 + data = resp.json() + assert data["ok"] is True + assert data["created"] is False + + +def test_digest_queue_add_missing_contact(client): + resp = client.post("/api/digest-queue", json={"job_contact_id": 9999}) + assert resp.status_code == 404