- Add app/api/endpoints/plex.py with Plex webhook receiver (fail-closed on missing secret) - Add app/services/plex/ with Plex API client and gardening content filter - Wire plex router into routes.py at /plex prefix - Add Plex + cf-video config vars to app/core/config.py and .env.example - Rename /knowledge/ingest endpoint to /knowledge/glean (pipeline verb alignment) - Update tests to use /knowledge/glean endpoint - Add python-multipart to environment.yml for form/webhook body parsing - Add dev/dev-stop/dev-logs commands to manage.sh (hot-reload without Docker) - Fix frontend/tsconfig.node.json to extend tsconfig.json (not tsconfig.node.json) - Add .dev-pids/ to .gitignore - Add frontend/package-lock.json
95 lines
3.7 KiB
Python
95 lines
3.7 KiB
Python
"""Knowledge query endpoint tests."""
|
|
|
|
_SOURCE_A = {
|
|
"video_path": "/media/GW/s59e01.mkv",
|
|
"presenter": "Monty Don",
|
|
"location": "Longmeadow",
|
|
"source_domain": "gardening",
|
|
}
|
|
_SOURCE_B = {
|
|
"video_path": "/media/GW/s59e02.mkv",
|
|
"presenter": "Adam Frost",
|
|
"location": "Adam's Garden",
|
|
"source_domain": "gardening",
|
|
}
|
|
|
|
|
|
def _ingest(client, source, facts):
|
|
return client.post("/api/v1/knowledge/glean", json={"source": source, "facts": facts}).json()
|
|
|
|
|
|
def test_query_all_returns_inserted_facts(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.9, "payload": {"action": "mulch", "subject": "border", "steps": [], "timing": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge")
|
|
assert resp.status_code == 200
|
|
assert len(resp.json()) == 1
|
|
|
|
|
|
def test_query_filter_by_fact_type(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "companion_planting", "confidence": 0.9, "payload": {"plants": ["tomato", "basil"], "relationship": "beneficial", "benefit": None, "spacing": None}},
|
|
{"fact_type": "instruction", "confidence": 0.8, "payload": {"action": "prune", "subject": "roses", "steps": [], "timing": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?fact_type=companion_planting")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["fact_type"] == "companion_planting"
|
|
|
|
|
|
def test_query_filter_by_presenter(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.9, "payload": {"action": "water", "subject": "seedlings", "steps": [], "timing": None}},
|
|
])
|
|
_ingest(client, _SOURCE_B, [
|
|
{"fact_type": "soil_amendment", "confidence": 0.85, "payload": {"amendment": "compost", "purpose": "feed soil", "application_rate": "2cm", "timing": "spring", "target_plants": []}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?presenter=Adam")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert all(d["presenter"] == "Adam Frost" for d in data)
|
|
|
|
|
|
def test_query_filter_by_min_confidence(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.95, "payload": {"action": "sow", "subject": "tithonia", "steps": [], "timing": None}},
|
|
{"fact_type": "medicinal", "confidence": 0.40, "payload": {"plant": "calendula", "use": "topical", "preparation": None, "cautions": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?min_confidence=0.8")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert all(d["confidence"] >= 0.8 for d in data)
|
|
assert len(data) == 1
|
|
|
|
|
|
def test_query_payload_is_dict_not_string(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "history_context", "confidence": 0.7, "payload": {
|
|
"subject": "Wisteria", "origin": "China", "period": None, "summary": "Introduced in 1816.",
|
|
}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge")
|
|
assert resp.status_code == 200
|
|
payload = resp.json()[0]["payload"]
|
|
assert isinstance(payload, dict)
|
|
assert payload["subject"] == "Wisteria"
|
|
|
|
|
|
def test_get_single_fact_by_id(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "harvest_timing", "confidence": 0.88, "payload": {
|
|
"plant": "Courgette", "indicators": ["20cm length"], "harvest_method": "twist off", "post_harvest": None,
|
|
}},
|
|
])
|
|
facts = client.get("/api/v1/knowledge").json()
|
|
fact_id = facts[0]["id"]
|
|
resp = client.get(f"/api/v1/knowledge/{fact_id}")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["payload"]["plant"] == "Courgette"
|
|
|
|
|
|
def test_get_fact_not_found(client):
|
|
resp = client.get("/api/v1/knowledge/9999")
|
|
assert resp.status_code == 404
|