import importlib.util import json import tempfile import unittest from pathlib import Path HERE = Path(__file__).resolve().parents[1] PROJECT = HERE.parent def load_probe_module(): p = HERE / 'tools/decision_probe.py' spec = importlib.util.spec_from_file_location('decision_probe_testmod', p) mod = importlib.util.module_from_spec(spec) assert spec.loader is not None spec.loader.exec_module(mod) return mod class ProbeTests(unittest.TestCase): def test_question_count_and_unique(self): q = json.loads((HERE/'config/questions.json').read_text(encoding='utf-8'))['questions'] ids = [x['id'] for x in q] self.assertEqual(18, len(ids)) self.assertEqual(len(ids), len(set(ids))) def test_every_question_has_test(self): q = json.loads((HERE/'config/questions.json').read_text(encoding='utf-8'))['questions'] self.assertTrue(all(x['tests'] for x in q)) def test_fixed_manifest_is_minimal_and_exact(self): m = json.loads((HERE/'config/fixture_manifest.json').read_text(encoding='utf-8')) self.assertEqual('exact-whitelist-no-recursive-discovery', m['selection_policy']) self.assertEqual([ 'Maps/Town Defence/Town Defence.dat', 'Maps/Town Defence/Town Defence.map', 'data/sprites/Tileset.rxx', ], [x['path'] for x in m['primary']]) self.assertEqual(1, len(m['conditional_fallbacks'])) self.assertFalse(m['conditional_fallbacks'][0]['enabled_by_default']) self.assertEqual([ 'Maps/The Barbarian King/TBK01/TBK01.dat', 'Maps/The Barbarian King/TBK01/TBK01.map', ], [x['path'] for x in m['conditional_fallbacks'][0]['files']]) def test_binary_selection_ignores_decoys_and_fallback_is_opt_in(self): mod = load_probe_module() with tempfile.TemporaryDirectory() as td: root = Path(td) assets = root/'private-assets' (assets/'a').mkdir(parents=True) (assets/'decoys').mkdir(parents=True) (assets/'a/one.dat').write_bytes(b'abc') (assets/'a/two.map').write_bytes(b'defg') (assets/'a/three.rxx').write_bytes(b'hijkl') (assets/'a/fallback.map').write_bytes(b'mnopqr') (assets/'decoys/should-never-be-read.map').write_bytes(b'X'*100) manifest = root/'manifest.json' manifest.write_text(json.dumps({ 'selection_policy':'exact-whitelist-no-recursive-discovery', 'primary':[ {'id':'d','role':'dat','path':'a/one.dat','expected_size':3}, {'id':'m','role':'map','path':'a/two.map','expected_size':4}, {'id':'r','role':'rxx','path':'a/three.rxx','expected_size':5}, ], 'conditional_fallbacks':[ {'id':'layer-map','enabled_by_default':False,'files':[ {'id':'fm','role':'map','path':'a/fallback.map','expected_size':6} ]} ] }), encoding='utf-8') mod.ASSETS = assets mod.MANIFEST = manifest mod.INCLUDE_FALLBACKS = set() primary = [p.relative_to(assets).as_posix() for _,p in mod.selected_files({'.dat','.map','.rxx'})] self.assertEqual(['a/one.dat','a/two.map','a/three.rxx'], primary) self.assertNotIn('decoys/should-never-be-read.map', primary) mod.INCLUDE_FALLBACKS = {'layer-map'} with_fallback = [p.relative_to(assets).as_posix() for _,p in mod.selected_files({'.dat','.map','.rxx'})] self.assertEqual(['a/one.dat','a/two.map','a/three.rxx','a/fallback.map'], with_fallback) def test_independent_inspector_does_not_import_kam(self): s = (HERE/'tools/decision_probe.py').read_text(encoding='utf-8') self.assertNotIn('import KM_', s) self.assertNotIn('from KM_', s) def test_no_recursive_binary_asset_scan_in_probe(self): s = (HERE/'tools/decision_probe.py').read_text(encoding='utf-8') self.assertNotIn('ASSETS.rglob', s) self.assertNotIn('ASSETS.glob', s) self.assertNotIn("rglob('*')", s) self.assertNotIn("glob('**", s) def test_integrated_paths_do_not_replace_old_runner(self): self.assertTrue((PROJECT/'run-open-question-tests.sh').is_file()) compatibility = (HERE/'COMPATIBILITY.md').read_text(encoding='utf-8') self.assertIn('build-android.sh', compatibility) self.assertIn('does not overwrite', compatibility) def test_no_production_claim(self): s = (HERE/'README.md').read_text(encoding='utf-8') self.assertIn('does not replace', s) if __name__ == '__main__': unittest.main()