# -*- coding: utf-8 -*- """Tests for tools/knowledgelint.py -- the host-side gate on the layer files. Loaded by path, like `test_knowledge_drift.py`: the tool is host CPython and must be (it reads YAML with PyYAML, on purpose, because that is the only reader on this side as strict as the YamlDotNet one inside Revit). What matters about a gate is the two answers it gives and that it never gives a third by accident: a file that will not load has to FAIL and set the exit code, and a file that loads has to pass without anybody learning to ignore it. A gate that cannot tell them apart is the silence this whole tool exists against -- 2026-09-01, one duplicate key, one whole plant layer gone, every report normal. """ import os import sys import pytest ROOT = os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__)))) TOOLS = os.path.join(ROOT, 'tools') if TOOLS not in sys.path: sys.path.insert(0, TOOLS) pytest.importorskip('yaml') lint = pytest.importorskip('knowledgelint') def _write(tmp_path, name, text): path = tmp_path / name path.write_text(text, encoding='utf-8') return str(path) # --------------------------------------------------------------- one file def test_a_clean_file_passes(tmp_path): path = _write(tmp_path, 'ok.yaml', u'a:\n b: 1\n c: [1, 2]\n') assert lint.check_file(path) == (True, 'ok') def test_a_duplicate_key_fails_with_the_line_to_delete(tmp_path): """The incident's exact shape: nested, not top level.""" path = _write(tmp_path, 'dup.yaml', u'checks:\n' u' rules:\n' u' cap_label_mismatch_is_view_specific: true\n' u' other: 1\n' u' cap_label_mismatch_is_view_specific: false\n') ok, message = lint.check_file(path) assert ok is False assert message.startswith('Duplicate key') assert 'cap_label_mismatch_is_view_specific' in message assert 'line 5' in message # the SECOND statement of the key def test_an_ordinary_syntax_error_fails_with_its_position(tmp_path): path = _write(tmp_path, 'broken.yaml', u'a:\n - [1, 2\n') ok, message = lint.check_file(path) assert ok is False assert 'line' in message def test_the_same_key_under_two_different_parents_is_not_a_duplicate(tmp_path): path = _write(tmp_path, 'notes.yaml', u'a:\n note: x\nb:\n note: y\n') assert lint.check_file(path)[0] is True def test_a_file_that_is_not_there_is_a_failure_not_a_pass(tmp_path): ok, message = lint.check_file(str(tmp_path / 'nope.yaml')) assert ok is False assert 'cannot open' in message # ------------------------------------------------------------------- the run def test_main_exits_zero_on_a_clean_tree(tmp_path, capsys): _write(tmp_path, 'a.yaml', u'a: 1\n') _write(tmp_path, 'b.yaml', u'b: 2\n') assert lint.main([str(tmp_path)]) == 0 out = capsys.readouterr().out assert '2 layer file(s) parse.' in out assert 'FAILED' not in out def test_main_exits_one_and_names_the_file(tmp_path, capsys): _write(tmp_path, 'a.yaml', u'a: 1\n') bad = _write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n') assert lint.main([str(tmp_path)]) == 1 out = capsys.readouterr().out assert 'FAILED' in out assert bad in out assert 'Duplicate key' in out # the sentence that says what a dropped layer looks like from the outside assert 'agrees with the defaults' in out def test_main_with_nothing_to_check_is_not_a_pass(tmp_path, capsys): """`no files found` printed with exit 0 is a gate that has stopped checking and says it is fine -- which is the failure mode, not a state to report cheerfully.""" assert lint.main([str(tmp_path)]) == 1 assert 'no knowledge layer found' in capsys.readouterr().out def test_json_output_carries_every_result(tmp_path, capsys): import json _write(tmp_path, 'a.yaml', u'a: 1\n') _write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n') assert lint.main([str(tmp_path), '--json']) == 1 payload = json.loads(capsys.readouterr().out) assert payload['checked'] == 2 and payload['failed'] == 1 assert [r['ok'] for r in payload['results']] == [True, False] def test_list_prints_the_targets_and_checks_nothing(tmp_path, capsys): bad = _write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n') assert lint.main([str(tmp_path), '--list']) == 0 out = capsys.readouterr().out assert bad in out assert 'FAILED' not in out # ------------------------------------------------------------------- targets def test_a_folder_argument_is_walked_and_bak_snapshots_are_left_alone( tmp_path): _write(tmp_path, 'a.yaml', u'a: 1\n') _write(tmp_path, 'k.bak-2026-01-01.yaml', u'x: 1\nx: 2\n') nested = tmp_path / 'deeper' nested.mkdir() _write(nested, 'b.yml', u'b: 1\n') found = [os.path.basename(p) for p in lint.walk_yaml(str(tmp_path))] assert found == ['a.yaml', 'b.yml'] def test_default_targets_include_the_catalogue_that_ships_with_the_tool(): """It is the one layer in the repository, so it is the one target that is there on every machine -- including a colleague's clone with no plant or job folder and no environment set.""" assert lint.CATALOGUE in lint.default_targets() def test_default_targets_are_files_that_exist_and_are_not_repeated(): targets = lint.default_targets() assert all(os.path.isfile(p) for p in targets) assert len(set(os.path.realpath(p) for p in targets)) == len(targets) def test_the_plant_root_is_read_the_way_plantpath_reads_it(monkeypatch, tmp_path): """Mirrors `vibe.plantpath.plant_root`, which cannot be imported here. Set to a FOLDER that folder is the root; set to a FILE its directory is, because the machine-wide file lives IN the root.""" monkeypatch.setenv(lint.PLANT_ENV, str(tmp_path)) assert lint.plant_root() == str(tmp_path) monkeypatch.setenv(lint.PLANT_ENV, str(tmp_path / 'knowledge.yaml')) assert lint.plant_root() == str(tmp_path) def test_the_plant_and_job_layers_of_this_machine_are_targets(monkeypatch, tmp_path): plants = tmp_path / 'plants' (plants / 'northgate').mkdir(parents=True) _write(plants / 'northgate', 'knowledge.yaml', u'a: 1\n') _write(plants, 'knowledge.yaml', u'b: 1\n') jobs = tmp_path / 'jobs' (jobs / 'sample_job').mkdir(parents=True) _write(jobs / 'sample_job', 'knowledge.yaml', u'c: 1\n') monkeypatch.setenv(lint.PLANT_ENV, str(plants)) monkeypatch.setenv(lint.JOBS_ENV, str(jobs)) targets = [os.path.realpath(p) for p in lint.default_targets()] for expected in (plants / 'knowledge.yaml', plants / 'northgate' / 'knowledge.yaml', jobs / 'sample_job' / 'knowledge.yaml'): assert os.path.realpath(str(expected)) in targets def test_a_project_config_pointer_is_followed_but_a_dead_one_is_not_a_verdict( monkeypatch, tmp_path): """A pointer to a file that is gone is `knowledge._config_missing` -- the buttons already refuse on it, and the file may simply live on a share this machine is not on today. Not a parse failure, so not this tool's verdict.""" jobs = tmp_path / 'jobs' (jobs / 'sample_job').mkdir(parents=True) _write(jobs / 'sample_job', 'knowledge.yaml', u'c: 1\n') config = _write(tmp_path, 'active-config.yaml', u'knowledge:\n a: 1\n') monkeypatch.setenv(lint.JOBS_ENV, str(jobs)) (jobs / 'sample_job' / 'active-config.txt').write_text( config, encoding='utf-8') assert os.path.realpath(config) in [os.path.realpath(p) for p in lint.default_targets()] (jobs / 'sample_job' / 'active-config.txt').write_text( str(tmp_path / 'gone.yaml'), encoding='utf-8') assert os.path.realpath(str(tmp_path / 'gone.yaml')) not in \ [os.path.realpath(p) for p in lint.default_targets()] # The obvious next test -- "every real layer on this machine parses" -- is # deliberately NOT here. It is the gate's whole job, but the gate is # `python tools/knowledgelint.py`, run by whoever edited a layer; as a pytest # case it would turn a broken gitignored file that no code change touched # into a red suite, and this repository already has one env-dependent test # teaching people that a failure is not necessarily theirs. Run the tool.