Eight cases, one per build-brief 6 state, dated relative to 2026-09-15. One of them (havbris) carries frontmatter that deliberately disagrees with its log, so the derived-truth rule has a fixture instead of only a docstring. One (vaeroy-sjomat) is named for an employer with ae, o-slash and a-ring, so the ASCII path rule has teeth on macOS NFD. Six beslutninger.jsonl fixtures for M6's counting rules are written now, in M2, because the store is append-only: type, skjema, delscore and vekt_hash have to be right at the first write or they are wrong forever. Co-Authored-By: Claude <claude-opus-5>
133 lines
5.8 KiB
Python
133 lines
5.8 KiB
Python
"""The eight-case fixture workspace every later milestone reads (plan Step 19).
|
|
|
|
A corpus is a contract with the milestones that have not been written yet.
|
|
M3 ingests into it, M4 drafts against it, M6 counts from it, and each of those
|
|
will assume the corpus says what this file asserts it says. So the assertions
|
|
here are about the corpus itself -- that each case really does resolve to the
|
|
state its name claims, that the one case built to disagree with its own
|
|
frontmatter actually disagrees, and that nothing in it carries a real name, a
|
|
real employer or a resolvable host.
|
|
|
|
The decision fixtures are M6's, written now on purpose. `beslutninger.jsonl`
|
|
is append-only, so its schema is irreversible: `type` and `skjema` have to be
|
|
right at the first write or they are wrong forever (risk H10). Writing M6's
|
|
counting fixtures in M2 is what forces that schema to be decided while it can
|
|
still be changed.
|
|
|
|
The ASCII assertion is not decoration. macOS hands back filenames in NFD, so a
|
|
Norwegian employer name can produce two directories that look identical and
|
|
are different byte strings (risk H8). One case in the corpus is named for an
|
|
employer with three of the four characters that provoke it.
|
|
|
|
Style note: this file follows tests/test_fixture_hygiene.py.
|
|
"""
|
|
|
|
import os
|
|
|
|
import test_fixture_hygiene as hygiene
|
|
|
|
import sak_status
|
|
from jobbsok_lib import jsonl
|
|
|
|
FIXTURES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures")
|
|
KORPUS = os.path.join(FIXTURES, "workspace")
|
|
BESLUTNINGER = os.path.join(FIXTURES, "beslutninger")
|
|
|
|
TODAY = "2026-09-15"
|
|
|
|
#: sak-id -> (status, ventende_part, flags). One case per state in build-brief
|
|
#: 6, dated relative to the frozen today.
|
|
FORVENTET = {
|
|
"2026-09-aurland-maritim-as-prosjektleder": ("trukket", "ingen", []),
|
|
"2026-09-bratthaug-industri-as-systemarkitekt": ("intervju", "dem", ["intervju_10"]),
|
|
"2026-09-havbris-energi-as-losningsarkitekt": ("sendt", "dem", ["sendt_14"]),
|
|
"2026-09-myrvang-teknologi-as-utvikler": ("avslag", "ingen", []),
|
|
"2026-09-nordlys-data-as-ai-radgiver": ("vurderer", "meg", []),
|
|
"2026-09-solvang-helse-as-teknisk-radgiver": ("tilbud", "meg", []),
|
|
"2026-09-storelva-kommune-fagleder-digitalisering": ("dialog", "dem", ["dialog_7"]),
|
|
"2026-09-vaeroy-sjomat-as-dataingenior": ("soker", "meg", []),
|
|
}
|
|
|
|
#: The one case whose cached frontmatter is deliberately wrong, and the keys
|
|
#: it is wrong about.
|
|
UENIG = "2026-09-havbris-energi-as-losningsarkitekt"
|
|
UENIG_NOKLER = ["status", "ventende_part"]
|
|
|
|
#: M6's counting fixtures. The name says what each one is for, because a
|
|
#: counting rule tested against a file called `data2.jsonl` is a rule nobody
|
|
#: can check.
|
|
BESLUTNINGSFIKSTURER = (
|
|
"ni-beslutninger.jsonl",
|
|
"ti-beslutninger.jsonl",
|
|
"tjue-lonnsdominert.jsonl",
|
|
"tjue-faa-ja.jsonl",
|
|
"med-korrigering.jsonl",
|
|
"med-utfall.jsonl",
|
|
)
|
|
|
|
|
|
def test_each_of_the_eight_cases_resolves_to_the_state_its_name_claims():
|
|
funnet = sak_status.alle_saker(KORPUS)
|
|
assert funnet == sorted(FORVENTET), (
|
|
"the corpus holds %r, the contract names %r" % (funnet, sorted(FORVENTET))
|
|
)
|
|
for sak_id, (status, ventende, flagg) in sorted(FORVENTET.items()):
|
|
resultat = sak_status.avgjor(sak_status.les_sak(KORPUS, sak_id)[2], TODAY)
|
|
assert resultat["status"] == status, sak_id
|
|
assert resultat["ventende_part"] == ventende, sak_id
|
|
assert resultat["flagg"] == flagg, sak_id
|
|
|
|
|
|
def test_the_case_built_to_disagree_with_its_frontmatter_reports_the_divergence():
|
|
avvik = sak_status.divergenser(KORPUS, TODAY)
|
|
assert [post["sak_id"] for post in avvik] == [UENIG], (
|
|
"exactly one case is supposed to disagree; found %r"
|
|
% ([post["sak_id"] for post in avvik],)
|
|
)
|
|
nokler = sorted(d["nokkel"] for d in avvik[0]["divergens"])
|
|
assert nokler == UENIG_NOKLER
|
|
resultat = sak_status.status_for_sak(KORPUS, UENIG, TODAY)
|
|
assert resultat["status"] == "sendt", "the log wins over the cache (risk H3)"
|
|
|
|
|
|
def test_every_generated_path_component_in_the_corpus_is_pure_ascii():
|
|
komponenter = []
|
|
for dirpath, dirnames, filenames in os.walk(KORPUS):
|
|
dirnames.sort()
|
|
komponenter.extend(dirnames)
|
|
komponenter.extend(sorted(filenames))
|
|
assert komponenter, "walked an empty corpus; the assertion below proves nothing"
|
|
for navn in komponenter:
|
|
assert navn.encode("ascii", "ignore").decode("ascii") == navn, (
|
|
"%r is not pure ASCII; on macOS its NFD twin is a second directory "
|
|
"(risk H8)" % navn
|
|
)
|
|
# The corpus must actually contain a name that could have gone wrong.
|
|
assert any("vaeroy" in navn for navn in komponenter), (
|
|
"no Norwegian-derived slug in the corpus, so the rule has no teeth"
|
|
)
|
|
|
|
|
|
def test_the_hygiene_scan_passes_over_the_new_corpus_and_states_its_denominator():
|
|
resultat = hygiene.scan(KORPUS)
|
|
assert resultat["findings"] == [], "hygiene findings: %r" % (resultat["findings"],)
|
|
melding = hygiene.report(resultat, KORPUS)
|
|
assert resultat["files"] == 2 * len(FORVENTET), (
|
|
"expected a sak.md and a logg.jsonl per case, scanned %d files"
|
|
% resultat["files"]
|
|
)
|
|
assert resultat["bytes"] > 0 and str(resultat["files"]) in melding
|
|
|
|
|
|
def test_every_decision_fixture_parses_and_carries_type_and_skjema():
|
|
funnet = sorted(os.listdir(BESLUTNINGER))
|
|
assert funnet == sorted(BESLUTNINGSFIKSTURER), funnet
|
|
for navn in funnet:
|
|
linjer = jsonl.read_lines(os.path.join(BESLUTNINGER, navn))
|
|
assert linjer, "%s is empty" % navn
|
|
for nummer, linje in enumerate(linjer, start=1):
|
|
assert linje.get("type") in jsonl.TYPES, (navn, nummer, linje.get("type"))
|
|
assert linje.get("skjema") == jsonl.SKJEMA, (navn, nummer)
|
|
# fold() is M6's reader; a fixture it cannot fold is a fixture that
|
|
# will fail in M6 instead of here.
|
|
jsonl.fold(linjer)
|