Files
FabledScribe/tests/test_platforms.py
T
bvandeusenandClaude Opus 5.5 914f31359c
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 15s
docs(6209): the remaining test files — lessons, reply moment and shape, retrieval specs and scopes, rule doors and judgments, services, snippets, search scope; five tests renamed to their property
Docstrings, section headers and assertion messages drop the record number,
milestone or step that introduced the behaviour, and incident accounts are
restated as the present-tense failure shape. A dead pointer to a design doc
that is not in the repo is removed.

Renamed to the property they check:
  test_the_old_shape_would_be_caught -> test_a_rank_then_scope_query_is_caught
  test_a_ledger_repeat_no_longer_produces_that_case_at_all -> test_a_ledger_repeat_is_counted_not_withheld
  test_the_header_no_longer_promises_once_per_session -> test_the_header_explains_the_seen_marker
  test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file -> test_one_canon_is_not_stamped_onto_a_whole_file
  test_the_repos_own_prose_no_longer_defines_with -> test_the_repos_own_prose_does_not_define_with
  test_the_original_regression_create_note_with_content -> test_the_real_server_refuses_content_on_create_note

Fixture and example ids stay: "note 5" in a supersession chain, "rule 156"
in an illustrative question, `#2275` as the hex-versus-citation case,
`#3244` as the example of a bare number, fixed dates in date-filter tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:44:58 -04:00

146 lines
5.7 KiB
Python

"""Platforms without a database.
Detection is pure — a catalog and a list of repo paths in, platform ids out —
so it is pinned here against fixture trees. What it writes, and what it must
never overwrite, is in tests/test_integration_platforms.py.
"""
from __future__ import annotations
from types import SimpleNamespace
from scribe.mcp.server import _READ_ONLY_TOOLS, _WRITE_TOOLS
from scribe.mcp.tools.projects import _inception_choices
from scribe.services.coverage import scan_archive
from tests.helpers import forge_tarball
from scribe.services.platforms import (
MEMBER_STATES, SETTABLE_STATES, detect, marker_matches, members,
validate_markers, validate_updates,
)
def _platform(pid: int, *markers: str) -> SimpleNamespace:
return SimpleNamespace(id=pid, markers=list(markers))
# --- a single marker ----------------------------------------------------------
def test_a_bare_marker_matches_the_basename_anywhere_in_the_tree():
assert marker_matches("go.mod", "go.mod")
assert marker_matches("go.mod", "services/api/go.mod")
assert marker_matches("AndroidManifest.xml", "app/src/main/AndroidManifest.xml")
assert marker_matches("vite.config.*", "frontend/vite.config.ts")
assert not marker_matches("go.mod", "go.mod.bak")
assert not marker_matches("go.mod", "docs/go.mod.md")
def test_a_marker_with_a_slash_matches_the_whole_path_only():
"""A directory-shaped marker cannot be satisfied by a same-named file
somewhere else — `.github/workflows/*` is about the repo root's CI, not
a vendored copy of some other project's."""
assert marker_matches(".github/workflows/*", ".github/workflows/ci.yml")
assert not marker_matches(".github/workflows/*", "vendor/x/.github/workflows/ci.yml")
assert not marker_matches(".github/workflows/*", "ci.yml")
def test_a_blank_marker_matches_nothing():
assert not marker_matches("", "anything")
assert not marker_matches(" ", "anything")
# --- detection over a tree -------------------------------------------------------
ANDROID_AND_GO = [
"app/build.gradle.kts",
"app/src/main/AndroidManifest.xml",
"server/go.mod",
"server/main.go",
"README.md",
]
def test_detect_finds_every_platform_a_tree_carries_and_nothing_else():
catalog = [
_platform(1, "AndroidManifest.xml"),
_platform(2, "go.mod"),
_platform(3, "Cargo.toml"),
_platform(4, ".github/workflows/*"),
]
assert detect(catalog, ANDROID_AND_GO) == [1, 2]
def test_a_platform_with_no_markers_is_declare_only():
"""An empty marker list is how a platform says "a person must tell you" —
it must not match everything, and it must not match nothing by error."""
catalog = [_platform(1), _platform(2, "go.mod")]
assert detect(catalog, ANDROID_AND_GO) == [2]
assert detect([SimpleNamespace(id=9, markers=None)], ANDROID_AND_GO) == []
def test_detect_on_an_empty_tree_finds_nothing():
assert detect([_platform(1, "go.mod")], []) == []
def test_the_archive_scan_carries_every_path_not_only_the_scannable_ones():
"""The markers that say what a project IS are mostly files the shape scan
skips — a manifest, a go.mod, a gradle script. Detection reads the scan's
paths, so a scan that listed only code files would detect nothing."""
scan = scan_archive(forge_tarball({
"app/src/main/AndroidManifest.xml": b"<manifest/>",
"server/go.mod": b"module x\n",
"server/main.py": b"def main():\n pass\n",
}))
assert set(scan.paths) == {
"app/src/main/AndroidManifest.xml", "server/go.mod", "server/main.py",
}
# The forge's wrapping directory is stripped, as it is for shapes.
assert not any(p.startswith("widget/") for p in scan.paths)
# --- the answers a person may give -------------------------------------------------
def test_detected_is_the_refreshs_to_write_never_a_persons():
assert "detected" in MEMBER_STATES
assert "detected" not in SETTABLE_STATES
assert "rejected" not in MEMBER_STATES
def test_validate_updates():
assert validate_updates({"go": "declared", "rust": "rejected", "python": None}) is None
assert validate_updates({}) is None
assert "slug: state" in validate_updates(["go"])
assert "not a state a person sets" in validate_updates({"go": "detected"})
assert "not a state a person sets" in validate_updates({"go": "maybe"})
assert "named by its slug" in validate_updates({"": "declared"})
def test_validate_markers():
assert validate_markers(None) is None
assert validate_markers(["go.mod", ".github/workflows/*"]) is None
assert "list of glob patterns" in validate_markers("go.mod")
assert "list of glob patterns" in validate_markers([1])
assert "repo-relative" in validate_markers(["/etc/passwd"])
def test_members_drops_the_rejected_answers():
rows = [
{"id": 1, "slug": "go", "name": "Go", "state": "declared"},
{"id": 2, "slug": "rust", "name": "Rust", "state": "rejected"},
{"id": 3, "slug": "python", "name": "Python", "state": "detected"},
]
assert [m["slug"] for m in members(rows)] == ["go", "python"]
# --- the doors ------------------------------------------------------------------------
def test_the_platform_tools_are_classified():
assert "list_platforms" in _READ_ONLY_TOOLS
assert "set_project_platforms" in _WRITE_TOOLS
def test_inception_choices_carry_platforms_only_when_given():
"""Left out, platforms stays UNSTATED (None after normalising) — which is
not the same answer as an empty list, "none of them"."""
assert "platforms" not in _inception_choices(0, True)
assert _inception_choices(0, True, ["go"])["platforms"] == ["go"]
assert _inception_choices(0, True, [])["platforms"] == []