Part of the "Add an automated backend test suite" parent issue. Depends on the pytest-scaffolding sub-issue.
Background
backend/app/intake/extractor.py turns raw brief text into a structured {customer_id, customer_name, sections, overview, notes} dict. It has a pure regex path (extract_customer_id_regex) and an LLM path (extract_brief) that normalizes the model's JSON (fills all canonical sections, strips/caps the customer id, falls back to the regex).
Problem / Goal
Add unit tests for the extractor's deterministic behavior, stubbing the LLM router so tests stay offline.
Where to look
backend/app/intake/extractor.py:35-46 — _CUSTOMER_RE, extract_customer_id_regex.
extractor.py:66-128 — extract_brief: short-circuit for tiny input (< 20 chars), JSON-parse fallback, section normalization (CANONICAL_SECTIONS = ["LAN","WAN","EDGE","CLOUD","QOS"]), customer-id fallback to regex, 64-char cap.
extractor.py:92-98 — it calls router.stream(...); monkeypatch this to yield canned output.
Suggested approach
Write backend/tests/test_extractor.py:
extract_customer_id_regex — matches Customer: ACME, customer - foo, is case-insensitive/multiline, strips trailing punctuation, returns None when absent.
extract_brief short-circuit: input under 20 chars returns the empty-sections stub with the "too short" note, and never calls the router.
extract_brief normalization: monkeypatch router.stream to yield a fenced JSON with only some sections; assert all five canonical keys are present and stripped.
- Customer-id fallback: model returns
customer_id: null but the text has a Customer: line → regex value is used; assert the 64-char cap.
- Router returns unparseable text → the best-effort fallback (regex id, whole text in
overview, failure note) is returned.
Use monkeypatch to replace router.stream with an async generator; mark tests async (scaffolding sub-issue enables pytest-asyncio).
Acceptance criteria
test_extractor.py covers the five cases above and passes offline.
- No real LLM call is made (router is stubbed).
Testing
cd backend && pytest tests/test_extractor.py -q.
Out of scope
- The intake API endpoints and background tasks (
api/intake.py) — logic-level extractor tests only here.
Part of the "Add an automated backend test suite" parent issue. Depends on the pytest-scaffolding sub-issue.
Background
backend/app/intake/extractor.pyturns raw brief text into a structured{customer_id, customer_name, sections, overview, notes}dict. It has a pure regex path (extract_customer_id_regex) and an LLM path (extract_brief) that normalizes the model's JSON (fills all canonical sections, strips/caps the customer id, falls back to the regex).Problem / Goal
Add unit tests for the extractor's deterministic behavior, stubbing the LLM router so tests stay offline.
Where to look
backend/app/intake/extractor.py:35-46—_CUSTOMER_RE,extract_customer_id_regex.extractor.py:66-128—extract_brief: short-circuit for tiny input (< 20chars), JSON-parse fallback, section normalization (CANONICAL_SECTIONS = ["LAN","WAN","EDGE","CLOUD","QOS"]), customer-id fallback to regex, 64-char cap.extractor.py:92-98— it callsrouter.stream(...); monkeypatch this to yield canned output.Suggested approach
Write
backend/tests/test_extractor.py:extract_customer_id_regex— matchesCustomer: ACME,customer - foo, is case-insensitive/multiline, strips trailing punctuation, returnsNonewhen absent.extract_briefshort-circuit: input under 20 chars returns the empty-sections stub with the "too short" note, and never calls the router.extract_briefnormalization: monkeypatchrouter.streamto yield a fenced JSON with only some sections; assert all five canonical keys are present and stripped.customer_id: nullbut the text has aCustomer:line → regex value is used; assert the 64-char cap.overview, failure note) is returned.Use
monkeypatchto replacerouter.streamwith an async generator; mark tests async (scaffolding sub-issue enablespytest-asyncio).Acceptance criteria
test_extractor.pycovers the five cases above and passes offline.Testing
cd backend && pytest tests/test_extractor.py -q.Out of scope
api/intake.py) — logic-level extractor tests only here.