waxwing/tests/test_knowledge_query.py
pyr0ball 5f5cb99db6 feat: bootstrap Waxwing Phase 1 scaffold
Plant registry, grow event calendar, and gardening knowledge base
populated via cf-harvest ingest endpoint. All 23 tests passing.

Stack:
- FastAPI + SQLite (cf-core migrations) on port 8522
- Vue 3 + Vite + Pinia SPA on port 8521
- 5 SQL migrations: locations → plants → grow_events, knowledge_sources → knowledge_facts
- Earthy green theme (--color-primary: #4a7c59) matching CF theme system

Knowledge ingest contract:
- POST /api/v1/knowledge/ingest — idempotent on fact_hash (sha256 of type+payload+video_path)
- 8 fact types: companion_planting, soil_amendment, propagation, pest_diagnosis,
  harvest_timing, instruction, medicinal, history_context
- Partial batch failure: malformed facts collected in rejected[], valid facts inserted
- Re-ingesting same video batch: facts_skipped_duplicate > 0, no duplicates

Wired views: RegistryView (plant CRUD), KnowledgeView (facts + type filter chips)
Stub views: CalendarView, SettingsView
2026-06-10 00:03:41 -07:00

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/ingest", 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