"""One folder, every bundle under it: the command line's own door (v1.1 F). The server has read a FOLDER of bundles since `okf mcp --root`; the generic skill read one bundle at a time, the one it was pointed at, and could not see the others. These tests hold the two command-line entries the skill now uses -- `okf card ` and `okf consume ` -- to the server's OWN functions: one source, two doors. Both corpora are invented here. """ from __future__ import annotations import json import subprocess import sys from pathlib import Path import pytest from llm_ingestion_okf import mcp_server from llm_ingestion_okf.cli import build BAKERY = { "surdeig.md": ( "# Surdeig\n\n" "## Heving\n\n" "Surdeigen hever i tolv timer ved romtemperatur før den formes.\n\n" "## Steking\n\n" "Brødet stekes i førti minutter på to hundre og tretti grader.\n" ), } GARDEN = { "tomater.md": ( "# Tomater\n\n" "## Vanning\n\n" "Tomatene vannes hver morgen, og jorda skal aldri tørke helt ut.\n\n" "## Oppbinding\n\n" "Plantene bindes opp til en stokk når de er tretti centimeter høye.\n" ), } def _bundle(tmp_path: Path, folder: Path, name: str, documents: dict[str, str]) -> Path: source = tmp_path / f"src-{name}" source.mkdir() for file_name, text in documents.items(): (source / file_name).write_text(text, encoding="utf-8") target = folder / name build(source, target, bundle_id=name, okf_version="0.2") return target @pytest.fixture(scope="module") def folder(tmp_path_factory: pytest.TempPathFactory) -> Path: tmp_path = tmp_path_factory.mktemp("folder-of-bundles") root = tmp_path / "samlinger" root.mkdir() _bundle(tmp_path, root, "bakeri", BAKERY) _bundle(tmp_path, root, "hage", GARDEN) return root def _okf(*argv: str) -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, "-m", "llm_ingestion_okf.cli", *argv], capture_output=True, text=True, check=False, ) def _surface(folder: Path) -> mcp_server.Surface: return mcp_server.build_surface(bundle=None, roots=[folder]) # --- F1: the overview ------------------------------------------------------- def test_the_card_of_a_folder_names_every_bundle_under_it(folder: Path) -> None: run = _okf("card", str(folder)) assert run.returncode == 0, run.stderr overview = json.loads(run.stdout) assert [card["bundle_id"] for card in overview["cards"]] == ["bakeri", "hage"] assert [entry["bundle_id"] for entry in overview["bundles"]] == ["bakeri", "hage"] assert overview["unreadable"] == [] assert overview["shape"] == "one-to-many" # Each card carries the map, which is what the working method reads first. assert all(card["map"] for card in overview["cards"]) def test_the_card_of_a_folder_is_the_servers_own_listing_and_description(folder: Path) -> None: """One source: the bytes the command prints are the server's two replies.""" surface = _surface(folder) listing = mcp_server.call_list(surface, {}) described = mcp_server.call_describe(surface, {}) overview = json.loads(_okf("card", str(folder)).stdout) assert overview["bundles"] == listing["bundles"] assert overview["unreadable"] == listing["unreadable"] assert overview["cards"] == described["cards"] def test_the_card_of_one_bundle_is_unchanged(folder: Path) -> None: """Pointed at one bundle, the command prints that bundle's card, as before.""" bundle = folder / "bakeri" run = _okf("card", str(bundle)) assert run.returncode == 0, run.stderr card = json.loads(run.stdout) assert card == mcp_server.card(bundle.resolve(), profile=mcp_server.okf_consume.DEFAULT_PROFILE) assert "cards" not in card def test_a_folder_holding_no_bundle_is_refused_rather_than_empty(tmp_path: Path) -> None: empty = tmp_path / "tom" empty.mkdir() run = _okf("card", str(empty)) assert run.returncode == 1 assert "bundle_none_served" in run.stderr def test_a_broken_bundle_under_the_folder_is_reported(folder: Path, tmp_path: Path) -> None: root = tmp_path / "med-feil" root.mkdir() for name in ("bakeri", "hage"): (root / name).symlink_to(folder / name) # never followed: not listed broken = root / "odelagt" broken.mkdir() (broken / "index.md").write_text("---\ntitle: x\n---\n", encoding="utf-8") real = root / "ekte" real.mkdir() for source in (folder / "hage").rglob("*"): target = real / source.relative_to(folder / "hage") if source.is_dir(): target.mkdir(parents=True, exist_ok=True) else: target.write_bytes(source.read_bytes()) overview = json.loads(_okf("card", str(root)).stdout) assert [card["bundle_id"] for card in overview["cards"]] == ["hage"] assert overview["unreadable"] == [ {"directory": "odelagt", "reason": "index.md declares no bundle_id"} ] # --- F2: one call across the folder ------------------------------------------ QUESTIONS = ("hvor lenge hever surdeigen", "hvor ofte vannes tomatene") def _ask(folder: Path, *extra: str) -> subprocess.CompletedProcess[str]: argv = ["consume", str(folder)] for question in QUESTIONS: argv += ["--question", question] return _okf(*argv, *extra) def test_one_call_over_a_folder_answers_from_every_bundle(folder: Path) -> None: run = _ask(folder) assert run.returncode == 0, run.stderr reply = json.loads(run.stdout) assert reply["asked"] == ["bakeri", "hage"] assert reply["questions"] == list(QUESTIONS) by_bundle = {answer["bundle_id"]: answer["payload"] for answer in reply["answers"]} assert set(by_bundle) == {"bakeri", "hage"} # Every excerpt names the bundle it came from, and it is the right one. for bundle_id, payload in by_bundle.items(): assert payload["excerpts"], bundle_id assert {excerpt["bundle_id"] for excerpt in payload["excerpts"]} == {bundle_id} delivered = { answer["bundle_id"]: " ".join(excerpt["text"] for excerpt in answer["payload"]["excerpts"]) for answer in reply["answers"] } assert "tolv timer" in delivered["bakeri"] assert "hver morgen" in delivered["hage"] def test_one_call_over_a_folder_is_the_servers_own_ask(folder: Path) -> None: """No ranking of its own: the bytes are `okf_ask`'s with no bundle named.""" reply = json.loads(_ask(folder).stdout) assert reply == mcp_server.call_ask(_surface(folder), {"questions": list(QUESTIONS)}) def test_naming_one_bundle_under_the_folder_asks_only_that_one(folder: Path) -> None: reply = json.loads(_ask(folder, "--bundle-id", "hage").stdout) assert reply["asked"] == ["hage"] assert reply == mcp_server.call_ask( _surface(folder), {"questions": list(QUESTIONS), "bundle_id": "hage"} ) def test_an_unknown_bundle_name_is_refused(folder: Path) -> None: run = _ask(folder, "--bundle-id", "finnes-ikke") assert run.returncode == 1 assert "bundle_unknown" in run.stderr def test_a_reading_flag_the_server_does_not_take_is_refused_over_a_folder(folder: Path) -> None: """A flag that would be silently dropped is refused: the folder door reads exactly as the server reads, and a flag it ignored would make the caller believe in a cut that never happened.""" run = _ask(folder, "--no-source-quota") assert run.returncode == 2 assert "--no-source-quota" in run.stderr def test_bundle_id_on_one_bundle_is_refused(folder: Path) -> None: run = _okf("consume", str(folder / "hage"), "--question", "vanning", "--bundle-id", "hage") assert run.returncode == 2 assert "--bundle-id" in run.stderr def test_one_bundle_is_read_as_before(folder: Path) -> None: """Pointed at one bundle, the payload is the single-bundle payload.""" run = _okf("consume", str(folder / "hage"), "--question", "vanning") assert run.returncode == 0, run.stderr payload = json.loads(run.stdout) assert "answers" not in payload assert payload["bundle"]["bundle_id"] == "hage" # --- F4: the checker reads the folder's reply -------------------------------- def _check(tmp_path: Path, reply: object) -> subprocess.CompletedProcess[str]: from llm_ingestion_okf import skill skill_path = tmp_path / "SKILL.md" skill_path.write_text(skill.render_generic(), encoding="utf-8") payload_path = tmp_path / "reply.json" payload_path.write_text(json.dumps(reply, ensure_ascii=False), encoding="utf-8") return _okf("check", "--skill", str(skill_path), "--payload", str(payload_path)) def _reply(folder: Path) -> dict[str, object]: return mcp_server.call_ask(_surface(folder), {"questions": list(QUESTIONS)}) def test_the_generic_skill_is_conformant_on_a_folders_reply(folder: Path, tmp_path: Path) -> None: run = _check(tmp_path, _reply(folder)) assert run.returncode == 0, run.stdout assert run.stdout.startswith("conformant: ") assert "over 2 payloads" in run.stdout def test_an_answer_labelled_with_another_bundle_is_a_finding(folder: Path, tmp_path: Path) -> None: reply = _reply(folder) answers = reply["answers"] assert isinstance(answers, list) answers[0]["bundle_id"] = "hage" run = _check(tmp_path, reply) assert run.returncode == 1 assert "answer_misattributed" in run.stdout def test_a_defect_in_one_answer_is_named_with_its_bundle(folder: Path, tmp_path: Path) -> None: reply = _reply(folder) answers = reply["answers"] assert isinstance(answers, list) del answers[1]["payload"]["contract"] run = _check(tmp_path, reply) assert run.returncode == 1 findings = [line for line in run.stdout.splitlines() if line.startswith(" ")] assert findings == [ line for line in findings if line.startswith(" contract_unversioned: [hage]") ] assert len(findings) == 1 def test_a_reply_with_no_answer_is_a_finding_not_a_pass(tmp_path: Path) -> None: run = _check(tmp_path, {"asked": [], "answers": []}) assert run.returncode == 1 assert "payload_invalid" in run.stdout