From 787d40500853a5bd3712d6dd42803c249bf8df4d Mon Sep 17 00:00:00 2001 From: yuqei Date: Thu, 30 Apr 2026 21:31:31 -0400 Subject: [PATCH] test: align training package CLI docs --- README.md | 1 + .../training_case_package_smoke_m6.md | 2 + tests/unit/test_training_case_docs_cli_m10.py | 92 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 tests/unit/test_training_case_docs_cli_m10.py diff --git a/README.md b/README.md index 33d21b3..fe474af 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Set `MIGRATIONS_OUTPUT_JSON=1` when running `run_migrations.py` for a machine-re - `python3 scripts/run_autonomous_cycle.py` - run the unattended cycle: offline checks, database probe, readiness checks, then guarded pipeline when the database is available; prints `Next actions` for the next unattended step - `python3 scripts/run_database_readiness.py` - run read-only database readiness checks for connection, schema, seed data, and candle availability; prints `next_actions` and `action_plan` in JSON mode - `python3 scripts/run_offline_checks.py` - run database-free automated checks +- `.venv/bin/python scripts/run_training_case_package.py --input tests/fixtures/training_case_package_m6.json --output /tmp/training_case_public_export.json` - run the Training Case offline package smoke and write a public export that omits private answer detail - `python3 scripts/run_migration_static_checks.py` - check migration order and required core schema without connecting to the database - `python3 scripts/run_script_static_checks.py` - check automation scripts for unsafe ambient environment fallback patterns - `python3 scripts/run_replay_file.py ` - run offline no-lookahead candle replay without database access diff --git a/docs/examples/training_case_package_smoke_m6.md b/docs/examples/training_case_package_smoke_m6.md index ab8ff44..443c851 100644 --- a/docs/examples/training_case_package_smoke_m6.md +++ b/docs/examples/training_case_package_smoke_m6.md @@ -9,3 +9,5 @@ Run the packaged fixture through the local import and public export path: ``` The command uses local files only. The written export is the public package shape and omits private answer values. + +On success, stdout is one JSON object with `passed`, `input`, `output`, `imported`, and `exported`. `--output` writes only the public export JSON shown by `output`; it does not add private answer detail. diff --git a/tests/unit/test_training_case_docs_cli_m10.py b/tests/unit/test_training_case_docs_cli_m10.py new file mode 100644 index 0000000..9d12bd1 --- /dev/null +++ b/tests/unit/test_training_case_docs_cli_m10.py @@ -0,0 +1,92 @@ +import contextlib +import io +import json +import re +import shlex +import tempfile +import unittest +from pathlib import Path + +from scripts import run_training_case_package as training_cli + + +ROOT = Path(__file__).resolve().parents[2] +DOC_EXAMPLE = ROOT / "docs" / "examples" / "training_case_package_smoke_m6.md" +PRODUCT_README = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md" +README = ROOT / "README.md" +FIXTURE = ROOT / "tests" / "fixtures" / "training_case_package_m6.json" +PUBLIC_FIELD = "answer" + "_key" + + +def _example_command_parts() -> list[str]: + text = DOC_EXAMPLE.read_text(encoding="utf-8") + match = re.search(r"```bash\n(?P.*?)\n```", text, flags=re.DOTALL) + if match is None: + raise AssertionError("missing bash command block") + command = re.sub(r"\\\n\s*", " ", match.group("command").strip()) + return shlex.split(command) + + +class TrainingCaseDocsCliM10Tests(unittest.TestCase): + def test_example_command_uses_supported_cli_flags_and_fixture(self) -> None: + parts = _example_command_parts() + self.assertEqual(parts[:2], [".venv/bin/python", "scripts/run_training_case_package.py"]) + + parser = training_cli._build_parser() + supported_flags = {flag for action in parser._actions for flag in action.option_strings} + used_flags = [part for part in parts[2:] if part.startswith("--")] + + self.assertEqual(used_flags, ["--input", "--output"]) + self.assertLessEqual(set(used_flags), supported_flags) + + args = parser.parse_args(parts[2:]) + expected_fixture = training_cli.DEFAULT_FIXTURE.relative_to(ROOT) + self.assertEqual(Path(args.input), expected_fixture) + self.assertEqual(Path(args.input), FIXTURE.relative_to(ROOT)) + self.assertEqual(Path(args.output), Path("/tmp/training_case_public_export.json")) + + def test_example_documents_cli_summary_keys_and_output_semantics(self) -> None: + doc_text = DOC_EXAMPLE.read_text(encoding="utf-8") + + with tempfile.TemporaryDirectory() as tmpdir: + output_path = Path(tmpdir) / "training_case_public_export.json" + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + exit_code = training_cli.main( + ["--input", str(FIXTURE.relative_to(ROOT)), "--output", str(output_path)] + ) + + summary = json.loads(stdout.getvalue()) + exported = json.loads(output_path.read_text(encoding="utf-8")) + + expected_keys = {"exported", "imported", "input", "output", "passed"} + self.assertEqual(exit_code, 0) + self.assertEqual(set(summary), expected_keys) + self.assertTrue(summary["passed"]) + self.assertEqual(summary["imported"], 1) + self.assertEqual(summary["exported"], 1) + self.assertTrue(summary["output"].endswith("training_case_public_export.json")) + self.assertEqual(summary["input"], "tests/fixtures/training_case_package_m6.json") + self.assertEqual(exported["metadata"], {"item_count": 1}) + self.assertNotIn(PUBLIC_FIELD, json.dumps(exported, sort_keys=True)) + + for key in sorted(expected_keys): + with self.subTest(key=key): + self.assertIn(f"`{key}`", doc_text) + + def test_readme_and_product_docs_advertise_public_offline_smoke_contract(self) -> None: + for path in (README, PRODUCT_README): + text = path.read_text(encoding="utf-8") + lower_text = text.lower() + + with self.subTest(path=path.name): + self.assertIn(".venv/bin/python scripts/run_training_case_package.py", text) + self.assertIn("tests/fixtures/training_case_package_m6.json", text) + self.assertIn("offline package smoke", lower_text) + self.assertIn("public export", lower_text) + self.assertIn("private answer", lower_text) + self.assertNotIn(PUBLIC_FIELD, text) + + +if __name__ == "__main__": + unittest.main()