Adds IngestRequest model and POST /api/sft/ingest route to app/data/corrections.py. Sibling CF products (Peregrine, Kiwi, etc.) can push pre-approved corrections via Bearer token auth (AVOCET_INGESTION_SECRET). Records land as status=approved in both sft_candidates.jsonl and sft_approved.jsonl immediately. 7 tests in tests/test_data_corrections.py cover 503 (secret unset), 401 (missing/malformed header), 403 (wrong secret), happy-path writes to both files, and optional label field.
102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
"""Tests for app/data/corrections.py -- POST /api/sft/ingest.
|
|
|
|
The corrections router is mounted at prefix="/api/sft" via the app/sft.py
|
|
backward-compat shim, so ingest lives at /api/sft/ingest.
|
|
"""
|
|
import json
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_globals(tmp_path):
|
|
from app.data import corrections as corr_module
|
|
corr_module.set_data_dir(tmp_path)
|
|
corr_module.set_config_dir(tmp_path)
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
from app.api import app
|
|
return TestClient(app)
|
|
|
|
|
|
_VALID_PAYLOAD = {
|
|
"source": "peregrine",
|
|
"task_type": "email_classification",
|
|
"prompt": "Classify this email: ...",
|
|
"response": "skip",
|
|
"correction": "action_required",
|
|
"label": "action_required",
|
|
}
|
|
|
|
_SECRET = "test-secret-abc123"
|
|
|
|
|
|
def test_ingest_503_when_secret_not_configured(client, monkeypatch):
|
|
monkeypatch.delenv("AVOCET_INGESTION_SECRET", raising=False)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD,
|
|
headers={"Authorization": f"Bearer {_SECRET}"})
|
|
assert r.status_code == 503
|
|
|
|
|
|
def test_ingest_401_when_no_auth_header(client, monkeypatch):
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD)
|
|
assert r.status_code == 401
|
|
|
|
|
|
def test_ingest_401_when_malformed_header(client, monkeypatch):
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD,
|
|
headers={"Authorization": "Token bad-format"})
|
|
assert r.status_code == 401
|
|
|
|
|
|
def test_ingest_403_when_wrong_secret(client, monkeypatch):
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD,
|
|
headers={"Authorization": "Bearer wrong-secret"})
|
|
assert r.status_code == 403
|
|
|
|
|
|
def test_ingest_creates_approved_record(client, monkeypatch, tmp_path):
|
|
from app.data import corrections as corr_module
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
corr_module.set_data_dir(tmp_path)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD,
|
|
headers={"Authorization": f"Bearer {_SECRET}"})
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert data["ok"] is True
|
|
assert "id" in data
|
|
candidates = corr_module.read_jsonl(corr_module._candidates_file())
|
|
assert len(candidates) == 1
|
|
rec = candidates[0]
|
|
assert rec["status"] == "approved"
|
|
assert rec["source"] == "peregrine"
|
|
assert rec["corrected_response"] == "action_required"
|
|
assert rec["id"] == data["id"]
|
|
|
|
|
|
def test_ingest_also_writes_to_approved_file(client, monkeypatch, tmp_path):
|
|
from app.data import corrections as corr_module
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
corr_module.set_data_dir(tmp_path)
|
|
r = client.post("/api/sft/ingest", json=_VALID_PAYLOAD,
|
|
headers={"Authorization": f"Bearer {_SECRET}"})
|
|
assert r.status_code == 200
|
|
approved = corr_module.read_jsonl(corr_module._approved_file())
|
|
assert len(approved) == 1
|
|
assert approved[0]["id"] == r.json()["id"]
|
|
|
|
|
|
def test_ingest_without_label_is_accepted(client, monkeypatch, tmp_path):
|
|
from app.data import corrections as corr_module
|
|
monkeypatch.setenv("AVOCET_INGESTION_SECRET", _SECRET)
|
|
corr_module.set_data_dir(tmp_path)
|
|
payload = {**_VALID_PAYLOAD, "label": None}
|
|
r = client.post("/api/sft/ingest", json=payload,
|
|
headers={"Authorization": f"Bearer {_SECRET}"})
|
|
assert r.status_code == 200
|