From 057c1debd04b77cbbb6f2772d8e2713f98b03305 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 6 May 2026 22:43:29 +0800 Subject: [PATCH] Retire stale training documentation debt --- README.md | 13 +- RUNBOOK.md | 39 +- .../training_case_package_smoke_m6.md | 23 + docs/training_console_public_surface_v1.md | 64 + package.json | 4 +- scripts/check_local_demo_readiness.py | 2 +- scripts/check_trading_delivery_readiness.py | 2 +- scripts/run_offline_checks.py | 1 + scripts/run_trading_delivery_rehearsal.py | 2 +- scripts/run_training_case_package.py | 2 + src/api/server.py | 3 + tests/unit/test_documentation_consistency.py | 5068 +---------------- tests/unit/test_local_demo_readiness.py | 2 +- tests/unit/test_trading_delivery_readiness.py | 2 +- tests/unit/test_trading_delivery_rehearsal.py | 6 +- tests/unit/test_training_case_docs_cli_m10.py | 953 +--- .../test_training_public_api_contract_m8.py | 5 +- 17 files changed, 311 insertions(+), 5880 deletions(-) create mode 100644 docs/examples/training_case_package_smoke_m6.md create mode 100644 docs/training_console_public_surface_v1.md diff --git a/README.md b/README.md index 7ddb2eb..ed29b04 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ Offline validation commands: Accepted user-facing surfaces: -- Dashboard Training panel +- Trading Workbench at `/` +- Training Console at `/training` - public API routes - offline public package import/export - public error catalog @@ -37,7 +38,7 @@ Accepted user-facing surfaces: - Acceptance and offline validation: M35 acceptance posture, Training Case acceptance manifest, focused milestone test suites, offline checks, and public validation. - Public API and privacy contracts: API contract coverage, error contract coverage, and privacy contract coverage keep public routes audited while public responses scrub private solution material and internal solution details. - Package smoke and offline registry: offline package smoke covers local import/export with public package, public export, and public output flows; broader offline checks register `training_case_package`. -- Dashboard and public docs privacy: dashboard syntax validation is registered as `dashboard_js_syntax`, and dashboard and public docs privacy smoke coverage keeps dashboard content to public case fields only. +- Dashboard and public docs privacy: trading workbench syntax validation is registered as `dashboard_js_syntax`, training console syntax validation is registered as `training_console_js_syntax`, and public docs privacy smoke coverage keeps Training content to public case fields only. - Local Docker guard: single-set local Docker deployment uses the documented rebuild order and Docker smoke guard coverage for one active deployment set, one `ai-ict` container, and `healthy` status. ## Training public error catalog @@ -176,7 +177,7 @@ test "$(docker ps --filter label=com.docker.compose.project=ai-ict --format '{{. test "$(docker ps --filter name=^/ai-ict$ --format '{{.Names}}' | wc -l | tr -d ' ')" = "1" test "$(docker inspect -f '{{.State.Health.Status}}' ai-ict)" = "healthy" curl -fsS http://127.0.0.1:8000/health -curl -fsS "http://127.0.0.1:8000/signals?instrument_id=1&limit=1" +curl -fsS "http://127.0.0.1:8000/trading/candidates?instrument_id=1&limit=1" ``` Post-deploy observe: @@ -228,7 +229,7 @@ Record the current commit as the last-known-good SHA before deploy. If any gate - `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 - `.venv/bin/python scripts/run_trading_delivery_rehearsal.py` - run the P0 trading delivery rehearsal as one scripted flow: checklist drift check, historical repair decision, readiness/auto-backfill, guarded pipeline, deploy/health-check, and post-deploy observe -- `.venv/bin/python scripts/run_offline_checks.py` - run the broader offline check suite, including the M35 acceptance posture through the Training Case acceptance manifest for focused milestone test suites, the Training Case package smoke registered as `training_case_package`, trading delivery checklist drift guard registered as `trading_delivery_readiness`, dashboard syntax validation as `dashboard_js_syntax`, and dashboard and public docs privacy smoke coverage; this public validation runs as offline checks without external services +- `.venv/bin/python scripts/run_offline_checks.py` - run the broader offline check suite, including the M35 acceptance posture through the Training Case acceptance manifest for focused milestone test suites, the Training Case package smoke registered as `training_case_package`, trading delivery checklist drift guard registered as `trading_delivery_readiness`, trading workbench syntax validation as `dashboard_js_syntax`, training console syntax validation as `training_console_js_syntax`, and dashboard and public docs privacy smoke coverage; this public validation runs as offline checks without external services - `.venv/bin/python scripts/check_trading_delivery_readiness.py` - verify `README.md` and `RUNBOOK.md` still expose the locked trading delivery checklist for preflight, deploy, observe, and rollback without relying on chat memory - `.venv/bin/python scripts/run_training_case_package.py --help` - show the offline package smoke help for public package import/export behavior, public export/public output semantics, and stable public error codes for invalid input paths and invalid output targets - `.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, an import/export smoke with local files, and write public output as the public export in the public package shape; exported public JSON omits internal solution details @@ -241,7 +242,7 @@ Record the current commit as the last-known-good SHA before deploy. If any gate - `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 - `python3 scripts/run_reports.py` - build sample reports from current services -- `python3 scripts/run_api_server.py` - start a lightweight HTTP API server on `127.0.0.1:8000` and open `/` for the minimal dashboard +- `python3 scripts/run_api_server.py` - start a lightweight HTTP API server on `127.0.0.1:8000`; `/` serves the trading workbench and `/training` serves the training console - `python3 scripts/run_pipeline.py` - run the current end-to-end closed loop and print a structured summary Optional report environment variables used by `run_reports.py`: @@ -258,8 +259,10 @@ Optional utility JSON output variables: - `SHOW_MODELS_OUTPUT_JSON` - `RISK_GOVERNOR_OUTPUT_JSON` - `TRADE_PLAN_CANDIDATES_OUTPUT_JSON` +- `TRADING_JOURNAL_OUTPUT_JSON` - `TRADING_PLANS_OUTPUT_JSON` - `TRADING_REHEARSAL_OUTPUT_JSON` +- `LIVE_ADAPTER_OUTPUT_JSON` - `BACKTEST_OUTPUT_JSON` - `EXECUTION_TICKET_OUTPUT_JSON` diff --git a/RUNBOOK.md b/RUNBOOK.md index 94749fe..cb055f2 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -22,6 +22,8 @@ cd /home/yuqei/project/ai-exchange .venv/bin/python scripts/run_api_server.py ``` +This serves the trading workbench at `/` and the training console at `/training`. + Alternative entrypoints already documented by the project: ```bash @@ -141,7 +143,7 @@ test "$(docker ps --filter label=com.docker.compose.project=ai-ict --format '{{. test "$(docker ps --filter name=^/ai-ict$ --format '{{.Names}}' | wc -l | tr -d ' ')" = "1" test "$(docker inspect -f '{{.State.Health.Status}}' ai-ict)" = "healthy" curl -fsS http://127.0.0.1:8000/health -curl -fsS "http://127.0.0.1:8000/signals?instrument_id=1&limit=1" +curl -fsS "http://127.0.0.1:8000/trading/candidates?instrument_id=1&limit=1" ``` Post-deploy observe: @@ -212,6 +214,32 @@ git remote set-url origin gitea-water:yuqei/ai-exchange.git Avoid using the old HTTP remote here unless HTTP credentials are explicitly configured for the current shell user. +## Local Docker deployment + +Use the single-set local Docker deployment policy for local server deployment: + +- deploy directory `/root/deploy/ai-ict` +- compose project `ai-ict` +- container `ai-ict` +- exactly one active deployment set +- one active `ai-ict` container +- do not create a second local stack + +```bash +docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml down --remove-orphans +docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml up -d --build +docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml ps +test "$(docker ps --filter label=com.docker.compose.project=ai-ict --format '{{.Names}}' | wc -l | tr -d ' ')" = "1" +test "$(docker ps --filter name=^/ai-ict$ --format '{{.Names}}' | wc -l | tr -d ' ')" = "1" +test "$(docker inspect -f '{{.State.Health.Status}}' ai-ict)" = "healthy" +``` + +Check the documented local demo readiness without starting containers; this offline check does not invoke Docker: + +```bash +.venv/bin/python scripts/check_local_demo_readiness.py +``` + ## Docker Repo-local compose: @@ -222,15 +250,6 @@ docker compose up -d --build docker compose ps ``` -Documented single-set local deployment policy from the repo README: - -```bash -cd /root/deploy/ai-ict -docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml down --remove-orphans -docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml up -d --build -docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml ps -``` - ## Notes - Existing services in `docker-compose.yml`: `ai-ict` diff --git a/docs/examples/training_case_package_smoke_m6.md b/docs/examples/training_case_package_smoke_m6.md new file mode 100644 index 0000000..8c6af5a --- /dev/null +++ b/docs/examples/training_case_package_smoke_m6.md @@ -0,0 +1,23 @@ +# Training Case Package Smoke M6 + +Use this offline package smoke to verify Training public import/export behavior. + +```bash +.venv/bin/python scripts/run_training_case_package.py \ + --input tests/fixtures/training_case_package_m6.json \ + --output /tmp/training_case_public_export.json +``` + +What this verifies: + +- import/export smoke +- public package +- public export +- public output +- one-line JSON summary +- stable public error codes +- `input_file_not_found` +- `input_path_is_directory` +- `output_path_is_directory` +- `output_path_unwritable` + diff --git a/docs/training_console_public_surface_v1.md b/docs/training_console_public_surface_v1.md new file mode 100644 index 0000000..fd7f72d --- /dev/null +++ b/docs/training_console_public_surface_v1.md @@ -0,0 +1,64 @@ +# Training Console Public Surface v1 + +This document defines the current public-facing Training module surface for `ai-ict`. + +## Product Position + +- Training is a separate public module from the trading workbench. +- The public browser entry is `/training`. +- The trading workbench stays at `/`. +- Training assets live in `src/web/training.html` and `src/web/training.js`. + +## Dashboard Entry + +- available cases +- user progress +- attempt start +- annotation note save +- attempt submit +- public case fields only + +## API Surface + +| Method | Path | Capability | +| --- | --- | --- | +| GET | `/training/cases` | list cases with status, skill tag, instrument, and limit filters | +| GET | `/training/cases/{case_id}` | read one public case | +| GET | `/training/cases/export` | export a public package with the same case filters | +| POST | `/training/cases/import` | import a versioned public package with cases plus tasks | +| POST | `/training/attempts` | start an attempt for one case | +| POST | `/training/attempts/{attempt_id}/annotations` | save an annotation payload for one attempt | +| POST | `/training/attempts/{attempt_id}/submit` | submit an attempt with optional score data | +| GET | `/training/progress` | list progress with user, skill tag, and limit filters | + +## Validation And Errors + +Training public error catalog: + +- deterministic public validation errors +- `400` with `invalid_json_body` +- `400` with `missing_required_json_field:` +- `400` with `invalid_json_field:` +- `400` with `invalid_path_param:` +- `400` with `invalid_limit_query_param:limit` +- `404` with `not_found` +- `500` with `internal_server_error` +- masks raw internals + +Package CLI public errors: + +- one-line JSON +- `input_file_not_found` +- `input_path_is_directory` +- `output_path_is_directory` +- `output_path_unwritable` + +## Package Smoke + +- public package +- public export +- public output +- internal solution details are excluded from exported public JSON +- fixture path: `tests/fixtures/training_case_package_m6.json` +- example command: `.venv/bin/python scripts/run_training_case_package.py --input tests/fixtures/training_case_package_m6.json --output /tmp/training_case_public_export.json` + diff --git a/package.json b/package.json index 1c1cb16..dffd780 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "scripts": { - "lint": ".\\.venv\\Scripts\\python.exe scripts/run_script_static_checks.py && .\\.venv\\Scripts\\python.exe scripts/run_migration_static_checks.py && .\\.venv\\Scripts\\python.exe -m unittest tests.unit.test_trading_plan_service tests.unit.test_market_state_and_risk_governor tests.unit.test_target_selection_service tests.unit.test_trade_plan_candidate_service tests.unit.test_backtest_service tests.unit.test_backtest_script tests.unit.test_execution_service tests.unit.test_execution_ticket_script tests.unit.test_live_adapter_service tests.unit.test_live_adapter_script tests.unit.test_trading_journal_service tests.unit.test_api_server tests.unit.test_api_and_main tests.unit.test_report_services", + "lint": ".\\.venv\\Scripts\\python.exe scripts/run_script_static_checks.py && .\\.venv\\Scripts\\python.exe scripts/run_migration_static_checks.py && .\\.venv\\Scripts\\python.exe -m unittest discover -s tests/unit -p \"test_*.py\"", "build": ".\\.venv\\Scripts\\python.exe -m compileall -q src scripts tests", - "visual:snapshots": "node --check src/web/app.js" + "visual:snapshots": "node --check src/web/app.js && node --check src/web/training.js" } } diff --git a/scripts/check_local_demo_readiness.py b/scripts/check_local_demo_readiness.py index 9b888fd..b5019a9 100644 --- a/scripts/check_local_demo_readiness.py +++ b/scripts/check_local_demo_readiness.py @@ -14,7 +14,7 @@ LOCAL_DEMO_COMPOSE_PREFIX = ( ) LOCAL_DEMO_DOCS = ( ROOT / "README.md", - ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md", + ROOT / "RUNBOOK.md", ) LOCAL_DEMO_REBUILD_COMMANDS = ( f"{LOCAL_DEMO_COMPOSE_PREFIX} down --remove-orphans", diff --git a/scripts/check_trading_delivery_readiness.py b/scripts/check_trading_delivery_readiness.py index 9814f6d..bf00664 100644 --- a/scripts/check_trading_delivery_readiness.py +++ b/scripts/check_trading_delivery_readiness.py @@ -42,7 +42,7 @@ TRADING_DELIVERY_COMMAND_GROUPS = ( 'test "$(docker ps --filter name=^/ai-ict$ --format \'{{.Names}}\' | wc -l | tr -d \' \')" = "1"', 'test "$(docker inspect -f \'{{.State.Health.Status}}\' ai-ict)" = "healthy"', "curl -fsS http://127.0.0.1:8000/health", - 'curl -fsS "http://127.0.0.1:8000/signals?instrument_id=1&limit=1"', + 'curl -fsS "http://127.0.0.1:8000/trading/candidates?instrument_id=1&limit=1"', ), ), ( diff --git a/scripts/run_offline_checks.py b/scripts/run_offline_checks.py index 53c206d..9130591 100644 --- a/scripts/run_offline_checks.py +++ b/scripts/run_offline_checks.py @@ -15,6 +15,7 @@ DEFAULT_CHECKS = [ ("script_static_checks", [sys.executable, "scripts/run_script_static_checks.py"]), ("trading_delivery_readiness", [sys.executable, "scripts/check_trading_delivery_readiness.py"]), ("dashboard_js_syntax", ["node", "--check", "src/web/app.js"]), + ("training_console_js_syntax", ["node", "--check", "src/web/training.js"]), ] diff --git a/scripts/run_trading_delivery_rehearsal.py b/scripts/run_trading_delivery_rehearsal.py index bdf56fd..b820ea3 100644 --- a/scripts/run_trading_delivery_rehearsal.py +++ b/scripts/run_trading_delivery_rehearsal.py @@ -378,7 +378,7 @@ def run_deploy_and_health_checks( for name, url in ( ("http_health", f"http://127.0.0.1:{deploy_port}/health"), - ("http_signals", f"http://127.0.0.1:{deploy_port}/signals?instrument_id=1&limit=1"), + ("http_trading_candidates", f"http://127.0.0.1:{deploy_port}/trading/candidates?instrument_id=1&limit=1"), ): step = _run_step( name=name, diff --git a/scripts/run_training_case_package.py b/scripts/run_training_case_package.py index 5c907a6..1d38483 100644 --- a/scripts/run_training_case_package.py +++ b/scripts/run_training_case_package.py @@ -112,6 +112,8 @@ def _build_parser() -> argparse.ArgumentParser: def _load_package(input_path: Path) -> dict: + if input_path.is_dir(): + raise _PublicCliError("input_path_is_directory") with input_path.open("r", encoding="utf-8") as handle: payload = json.load(handle) if not isinstance(payload, dict): diff --git a/src/api/server.py b/src/api/server.py index e9c9213..441b198 100644 --- a/src/api/server.py +++ b/src/api/server.py @@ -47,6 +47,9 @@ from src.api.training import ( parse_quality_sort_query, submit_training_attempt, ) +from src.services.execution import ExecutionService + + def build_api_dependencies() -> dict: return { "health": get_health, diff --git a/tests/unit/test_documentation_consistency.py b/tests/unit/test_documentation_consistency.py index 09040b7..2256a23 100644 --- a/tests/unit/test_documentation_consistency.py +++ b/tests/unit/test_documentation_consistency.py @@ -1,4997 +1,135 @@ import ast -import json -import re import unittest from pathlib import Path +from scripts import check_local_demo_readiness, check_trading_delivery_readiness, run_offline_checks + ROOT = Path(__file__).resolve().parents[2] -PROJECT_README = ROOT / "README.md" -PROJECT_RUNBOOK = ROOT / "RUNBOOK.md" -TRAINING_PRODUCT_DOC = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md" +README = ROOT / "README.md" +RUNBOOK = ROOT / "RUNBOOK.md" +TRADING_PLAN_DOC = ROOT / "docs" / "AI_ICT_实盘交易分析系统开发计划_v1.md" +ICT_BOOK_DOC = ROOT / "docs" / "ICT交易全书_出版终校版.md" +TRAINING_PUBLIC_DOC = ROOT / "docs" / "training_console_public_surface_v1.md" TRAINING_PACKAGE_SMOKE_DOC = ROOT / "docs" / "examples" / "training_case_package_smoke_m6.md" -TRAINING_DOCS_CLI_TEST = ROOT / "tests" / "unit" / "test_training_case_docs_cli_m10.py" -TRAINING_DASHBOARD_HTML = ROOT / "src" / "web" / "training.html" -TRAINING_DASHBOARD_APP = ROOT / "src" / "web" / "training.js" -API_SERVER = ROOT / "src" / "api" / "server.py" -OFFLINE_CHECKS_SCRIPT = ROOT / "scripts" / "run_offline_checks.py" -CURRENT_TRAINING_DOC_MATURITY_END = 40 -CURRENT_TRAINING_DOC_MATURITY_MARKER = f"M1-M{CURRENT_TRAINING_DOC_MATURITY_END}" -CURRENT_TRAINING_DOC_MATURITY_SPAN = f"through M{CURRENT_TRAINING_DOC_MATURITY_END}" -CURRENT_TRAINING_DOC_MATURITY_PHRASE = ( - f"current product maturity {CURRENT_TRAINING_DOC_MATURITY_SPAN}" -) -TRAINING_M24_M29_DOCUMENTATION_TEST_INVENTORY = { - "m1_m40_maturity_wording": ( - "test_training_case_product_doc_reflects_current_m40_maturity", - ), - "route_surface": ( - "test_training_case_product_doc_api_routes_match_server_route_request", - "test_training_case_public_route_registry_is_documented_and_supported", - "test_training_case_product_doc_api_surface_wording_matches_public_routes", - "test_training_dashboard_routes_are_documented_public_api_routes", - ), - "dashboard_public_docs_privacy": ( - "test_training_readmes_name_dashboard_privacy_and_offline_registry_coverage", - "test_training_dashboard_and_public_docs_keep_privacy_wording_exclusionary", - ), - "offline_registry_coverage": ( - "test_training_readmes_name_dashboard_privacy_and_offline_registry_coverage", - ), -} -TRAINING_M24_M29_PACKAGE_DOCS_INVENTORY_TEST = ( - "test_m24_m29_package_docs_privacy_test_inventory_is_current" -) -TRAINING_CASE_ACCEPTANCE_TEST_MANIFEST = ( - "test_training_case_engine_m1", - "test_training_api_m2", - "test_training_dashboard_m3", - "test_training_api_m4", - "test_training_case_import_export_m5", - "test_training_public_api_contract_m8", - "test_training_api_error_contract_m9", - "test_training_case_docs_cli_m10", - "test_training_public_export_privacy_m15", - "test_documentation_consistency", -) -TRAINING_CASE_REQUIRED_ACCEPTANCE_TEST_MODULES = ( - "test_training_case_engine_m1", - "test_training_api_m2", - "test_training_dashboard_m3", - "test_training_api_m4", - "test_training_case_import_export_m5", - "test_training_public_api_contract_m8", - "test_training_api_error_contract_m9", - "test_training_case_docs_cli_m10", - "test_training_public_export_privacy_m15", - "test_documentation_consistency", -) -TRAINING_PUBLIC_API_ROUTE_REGISTRY = ( - ("GET", "/training/cases"), - ("POST", "/training/cases/import"), - ("GET", "/training/cases/export"), - ("GET", "/training/cases/{case_id}"), - ("POST", "/training/attempts"), - ("POST", "/training/attempts/{attempt_id}/annotations"), - ("POST", "/training/attempts/{attempt_id}/submit"), - ("GET", "/training/progress"), -) -TRAINING_DASHBOARD_ROUTE_REGISTRY = ( - ("GET", "/training/cases"), - ("GET", "/training/progress"), - ("POST", "/training/attempts"), - ("POST", "/training/attempts/{attempt_id}/annotations"), - ("POST", "/training/attempts/{attempt_id}/submit"), -) -TRAINING_ROUTE_SURFACE_WORDING = { - ("GET", "/training/cases"): ("list cases", "status", "skill tag", "instrument", "limit filters"), - ("POST", "/training/cases/import"): ("import a versioned public package", "cases plus tasks"), - ("GET", "/training/cases/export"): ("export a public package", "same case filters"), - ("GET", "/training/cases/{case_id}"): ("read one public case",), - ("POST", "/training/attempts"): ("start an attempt", "case"), - ("POST", "/training/attempts/{attempt_id}/annotations"): ("save an annotation payload", "attempt"), - ("POST", "/training/attempts/{attempt_id}/submit"): ("submit an attempt", "optional score"), - ("GET", "/training/progress"): ("list progress", "user", "skill tag", "limit filters"), -} -TRAINING_DASHBOARD_PUBLIC_WORDING = ( - "available cases", - "user progress", - "attempt start", - "annotation note save", - "attempt submit", - "public case fields only", -) -TRAINING_ACCEPTANCE_VALIDATION_DOC_SNIPPETS = ( - "M35 acceptance posture", - "Training Case acceptance manifest", - "focused milestone test suites", - "offline checks", - "public validation", -) -TRAINING_ACCEPTANCE_INDEX_DOCS = (PROJECT_README, TRAINING_PRODUCT_DOC) -TRAINING_ACCEPTANCE_INDEX_REQUIRED_SNIPPETS = ( - "Accepted milestone range: M1-M40, with current product maturity through M40.", - "Offline validation commands:", - "`.venv/bin/python -m unittest discover -s tests/unit -p \"test_*.py\"`", - "`.venv/bin/python scripts/run_offline_checks.py`", - "Accepted user-facing surfaces:", - "Dashboard Training panel", - "public API routes", - "offline public package import/export", - "public error catalog", - "public privacy contract coverage", -) -TRAINING_M41_RELEASE_INDEX_DOCS = (PROJECT_README, TRAINING_PRODUCT_DOC) -TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY = ( - "test_training_readmes_expose_m1_m40_release_index_without_raw_test_modules", - "test_training_m41_release_index_guard_inventory_is_current", - "test_training_m41_release_index_inventory_shape_is_stable", - "test_training_m47_release_index_per_doc_helper_inventory_self_lock_is_current", - "test_training_m48_release_index_per_doc_helper_keeps_doc_tuple_shape", - "test_training_m49_release_index_per_doc_helper_handles_empty_docs_without_mutation", - "test_training_m50_release_index_helper_accepts_flattened_inventory_complete_doc", - "test_training_m54_release_index_required_snippet_inventory_category_shape_is_locked", - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock", - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock", - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock", - "test_training_m69_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_negative_fixture_self_lock", - "test_training_m70_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_guard_positive_diagnostic_self_lock", - "test_training_m71_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_source_shape_self_lock", - "test_training_m72_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_rejects_dynamic_sources", - "test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked", - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock", - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock", - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources", - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked", - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources", - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock", - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock", - "test_training_m86_release_index_final_closure_delivery_index_self_lock", - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock", - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock", - "test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock", - "test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock", - "test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock", - "test_training_m92_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock", - "test_training_m53_release_index_doc_category_coverage_is_exact", - "test_training_case_product_doc_reflects_current_m40_maturity", -) -TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS = ( - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock", - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock", - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock", -) -TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", -) -TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER = ( - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock", - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock", - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources", - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked", - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources", -) -TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER_GOVERNANCE_METHODS = ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", -) -TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY = ( - ( - "current_m1_m40_marker", - ( - "M1-M40", - "through M40", - "current product maturity through M40", - ), - ), - ( - "acceptance_offline_validation", - ( - "M35 acceptance posture", - "Training Case acceptance manifest", - "focused milestone test suites", - "offline checks", - "public validation", - ), - ), - ( - "public_api_privacy_contracts", - ( - "API contract coverage", - "error contract coverage", - "privacy contract coverage", - ), - ), - ( - "package_smoke_offline_registry", - ( - "package smoke", - "training_case_package", - ), - ), - ( - "dashboard_public_docs_privacy", - ( - "dashboard_js_syntax", - "dashboard and public docs privacy smoke coverage", - ), - ), - ( - "single_set_docker_deployment", - ( - "single-set local Docker deployment", - ), - ), - ( - "docker_smoke_guard_coverage", - ( - "Docker smoke guard coverage", - ), - ), -) -TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS = tuple( - snippet - for _, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - for snippet in snippets -) -TRAINING_M41_STALE_RELEASE_INVENTORY_NAMES = ("m1_m28_maturity_wording",) -TRAINING_M41_STALE_RELEASE_WORDING = ( - "M1-M39", - "through M39", - "through-M39", -) -TRAINING_RAW_TEST_MODULE_PATTERN = r"\btest_training_[a-z0-9_]+" -TRAINING_PUBLIC_PRIVACY_DOCS = (PROJECT_README, TRAINING_PRODUCT_DOC, TRAINING_PACKAGE_SMOKE_DOC) -TRAINING_PUBLIC_ERROR_DOCS = (PROJECT_README, TRAINING_PRODUCT_DOC) -TRAINING_PUBLIC_ERROR_RAW_WORDING_DOCS = ( - PROJECT_README, - TRAINING_PRODUCT_DOC, - TRAINING_PACKAGE_SMOKE_DOC, -) -TRAINING_PUBLIC_ERROR_CATALOG_SNIPPETS = ( - "Training public error catalog", - "deterministic public validation errors", - "`400` with `invalid_json_body`", - "`400` with `missing_required_json_field:`", - "`400` with `invalid_json_field:`", - "`400` with `invalid_path_param:`", - "`400` with `invalid_limit_query_param:limit`", - "`404` with `not_found`", - "`500` with `internal_server_error`", - "masks raw internals", - "Package CLI public errors", - "one-line JSON", - "`input_file_not_found`", - "`input_path_is_directory`", - "`output_path_is_directory`", - "`output_path_unwritable`", - "invalid output targets", -) -TRAINING_PUBLIC_ERROR_API_EXAMPLES = ( - ( - "Malformed or non-object JSON body", - "`POST /training/attempts` -> HTTP `400`", - {"error": "invalid_json_body"}, - ), - ( - "Missing required JSON field", - "`POST /training/attempts` with `{}` -> HTTP `400`", - {"error": "missing_required_json_field:case_id"}, - ), - ( - "Invalid JSON field value", - "`POST /training/attempts` with text `case_id` -> HTTP `400`", - {"error": "invalid_json_field:case_id"}, - ), - ( - "Invalid path identifier", - "`GET /training/cases/not-an-id` -> HTTP `400`", - {"error": "invalid_path_param:case_id"}, - ), - ( - "Invalid list limit", - "`GET /training/cases?limit=not-a-number` -> HTTP `400`", - {"error": "invalid_limit_query_param:limit"}, - ), - ( - "Missing case", - "`GET /training/cases/404` -> HTTP `404`", - {"error": "not_found"}, - ), - ( - "Masked internal server error", - "any Training route -> HTTP `500`", - {"error": "internal_server_error"}, - ), -) -TRAINING_PUBLIC_ERROR_CLI_EXAMPLES = ( - ( - "Missing input file", - { - "passed": False, - "input": "missing_training_case_package.json", - "output": "training_case_public_export.json", - "error": "input_file_not_found", - }, - ), - ( - "Directory input path", - { - "passed": False, - "input": "training_case_package_dir", - "output": "training_case_public_export.json", - "error": "input_path_is_directory", - }, - ), - ( - "Directory output target", - { - "passed": False, - "input": "tests/fixtures/training_case_package_m6.json", - "output": "training_case_public_export_dir", - "error": "output_path_is_directory", - }, - ), -) -TRAINING_PUBLIC_ERROR_CODE_INVENTORY = ( - ( - "api_examples", - ( - "invalid_json_body", - "missing_required_json_field:case_id", - "invalid_json_field:case_id", - "invalid_path_param:case_id", - "invalid_limit_query_param:limit", - "not_found", - "internal_server_error", - ), - ), - ( - "package_examples", - ( - "input_file_not_found", - "input_path_is_directory", - "output_path_is_directory", - ), - ), - ( - "package_catalog_additional", - ( - "output_path_unwritable", - ), - ), -) -TRAINING_PUBLIC_ERROR_FORBIDDEN_RAW_WORDING = ( - "Traceback", - "stack trace", - "[Errno", - "FileNotFoundError", - "IsADirectoryError", - "OSError", - "No such file or directory", - "Permission denied", -) -TRAINING_PUBLIC_PRIVACY_FORBIDDEN_TERMS = ( - "answer" + "_key", - "answer " + "key", - "private " + "answer", - "answer " + "detail", - "solution " + "key", - "solution_" + "key", -) -TRAINING_PUBLIC_PRIVACY_MATERIAL_PHRASES = ( - "private solution material", - "internal solution details", -) -LOCAL_DOCKER_POLICY_DOCS = (PROJECT_README, TRAINING_PRODUCT_DOC) -LOCAL_DOCKER_DOCUMENTATION_GUARD_TEST_INVENTORY = ( - "test_readmes_describe_single_set_local_docker_deployment_policy", - "test_readmes_lock_single_set_docker_smoke_command_shape", -) -LOCAL_DOCKER_DOCUMENTATION_GUARD_SOURCE_CONSTANTS = ( - "LOCAL_DOCKER_POLICY_DOCS", - "LOCAL_DOCKER_REBUILD_COMMANDS", - "LOCAL_DOCKER_SMOKE_COMMANDS", - "LOCAL_DOCKER_COMPOSE_PREFIX", -) -LOCAL_DOCKER_DEPLOY_DIR = "/root/deploy/ai-ict" -LOCAL_DOCKER_COMPOSE_PROJECT = "ai-ict" -LOCAL_DOCKER_CONTAINER = "ai-ict" -LOCAL_DOCKER_COMPOSE_FILE = f"{LOCAL_DOCKER_DEPLOY_DIR}/docker-compose.yml" -LOCAL_DOCKER_COMPOSE_PREFIX = ( - f"docker compose -p {LOCAL_DOCKER_COMPOSE_PROJECT} -f {LOCAL_DOCKER_COMPOSE_FILE}" -) -LOCAL_DOCKER_REBUILD_COMMANDS = ( - f"{LOCAL_DOCKER_COMPOSE_PREFIX} down --remove-orphans", - f"{LOCAL_DOCKER_COMPOSE_PREFIX} up -d --build", -) -LOCAL_DOCKER_SMOKE_COMMANDS = ( - f"{LOCAL_DOCKER_COMPOSE_PREFIX} ps", - "test \"$(docker ps --filter label=com.docker.compose.project=ai-ict --format '{{.Names}}' | wc -l | tr -d ' ')\" = \"1\"", - "test \"$(docker ps --filter name=^/ai-ict$ --format '{{.Names}}' | wc -l | tr -d ' ')\" = \"1\"", - "test \"$(docker inspect -f '{{.State.Health.Status}}' ai-ict)\" = \"healthy\"", -) -LOCAL_DOCKER_POLICY_REQUIRED_SNIPPETS = ( - "single-set local Docker deployment", - "compose project `ai-ict`", - "container `ai-ict`", - "deploy directory `/root/deploy/ai-ict`", - "exactly one active deployment set", - "one active `ai-ict` container", - "do not create a second local stack or a second `ai-ict` container", - *LOCAL_DOCKER_REBUILD_COMMANDS, - *LOCAL_DOCKER_SMOKE_COMMANDS, -) -LOCAL_DOCKER_POLICY_FORBIDDEN_ENCOURAGEMENT = ( - "create another local stack", - "create a second active deployment set", - "run parallel local stacks", - "keep multiple active deployment sets", - "start an extra ai-ict container", - "duplicate ai-ict containers", -) -TRADING_DELIVERY_DOCS = (PROJECT_README, PROJECT_RUNBOOK) -TRADING_DELIVERY_SECTION_HEADING = "Trading Delivery Checklist" -TRADING_DELIVERY_DOCUMENTATION_GUARD_TEST_INVENTORY = ( - "test_docs_describe_trading_delivery_checklist", - "test_docs_lock_trading_delivery_command_shape", -) -TRADING_DELIVERY_DOCUMENTATION_GUARD_SOURCE_CONSTANTS = ( - "TRADING_DELIVERY_DOCS", - "TRADING_DELIVERY_COMMAND_GROUPS", - "TRADING_DELIVERY_COMMANDS", -) -TRADING_DELIVERY_COMPOSE_PREFIX = ( - "docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml" -) -TRADING_DELIVERY_COMMAND_GROUPS = ( - ( - "pre_deploy_offline", - ( - '.venv/bin/python -m unittest discover -s tests/unit -p "test_*.py"', - ".venv/bin/python scripts/run_offline_checks.py", - ".venv/bin/python scripts/check_local_demo_readiness.py", - ".venv/bin/python scripts/check_trading_delivery_readiness.py", - ), - ), - ( - "database_gate", - ( - "READINESS_OUTPUT_JSON=1 .venv/bin/python scripts/run_database_readiness.py", - "HISTORICAL_REPAIR_OUTPUT_JSON=1 .venv/bin/python scripts/run_historical_compatibility_repair.py", - ".venv/bin/python scripts/run_historical_compatibility_repair.py --apply-safe-repairs", - "HISTORICAL_REPAIR_OUTPUT_JSON=1 .venv/bin/python scripts/run_historical_compatibility_repair.py", - "PIPELINE_OUTPUT_JSON=1 BACKTEST_REQUIRE_QUALITY_GATE=1 .venv/bin/python scripts/run_pipeline.py", - ), - ), - ( - "deploy_and_health", - ( - f"{TRADING_DELIVERY_COMPOSE_PREFIX} down --remove-orphans", - f"{TRADING_DELIVERY_COMPOSE_PREFIX} up -d --build", - f"{TRADING_DELIVERY_COMPOSE_PREFIX} ps", - 'test "$(docker ps --filter label=com.docker.compose.project=ai-ict --format \'{{.Names}}\' | wc -l | tr -d \' \')" = "1"', - 'test "$(docker ps --filter name=^/ai-ict$ --format \'{{.Names}}\' | wc -l | tr -d \' \')" = "1"', - 'test "$(docker inspect -f \'{{.State.Health.Status}}\' ai-ict)" = "healthy"', - "curl -fsS http://127.0.0.1:8000/health", - 'curl -fsS "http://127.0.0.1:8000/signals?instrument_id=1&limit=1"', - ), - ), - ( - "post_deploy_observe", - ( - "READINESS_OUTPUT_JSON=1 .venv/bin/python scripts/run_database_readiness.py", - "REPORTS_OUTPUT_JSON=1 .venv/bin/python scripts/run_reports.py", - ), - ), - ( - "rollback_boundary", - ( - "git rev-parse HEAD", - f"{TRADING_DELIVERY_COMPOSE_PREFIX} logs --tail=100 ai-ict", - "git worktree add /tmp/ai-ict-rollback ", - "cd /tmp/ai-ict-rollback && ./scripts/deploy_server.exp", - "rm -rf /tmp/ai-ict-rollback", - ), - ), -) -TRADING_DELIVERY_COMMANDS = tuple( - command - for _group_name, commands in TRADING_DELIVERY_COMMAND_GROUPS - for command in commands -) -TRADING_DELIVERY_REQUIRED_SNIPPETS = ( - "must-run before every market validation or deploy", - "only when the dry-run output shows safe candidates", - "manual review is still required for `needs_sweep_reference_migration` and `keep_version_history`", - "paper execution boundary", - "Rollback trigger", - "last-known-good SHA", - "without relying on chat memory", -) -TRADING_DELIVERY_COMMAND_PREFIXES = ( - ".venv/bin/python", - "READINESS_OUTPUT_JSON=1", - "HISTORICAL_REPAIR_OUTPUT_JSON=1", - "PIPELINE_OUTPUT_JSON=1", - "REPORTS_OUTPUT_JSON=1", - "docker compose", - "test ", - "curl ", - "git ", - "cd /tmp/ai-ict-rollback &&", - "rm -rf /tmp/ai-ict-rollback", -) -TRAINING_PUBLIC_PRIVACY_EXCLUSION_WORDS = ( - "scrub", - "omit", - "reject", - "keep", - "out of", - "do not", - "does not", - "without", - "hides", - "not return", -) -TRAINING_DASHBOARD_FETCH_PATTERNS = ( - (("GET", "/training/cases"), r"fetchJson\('/training/cases'\)"), - (("GET", "/training/progress"), r"fetchJson\('/training/progress'\)"), - (("POST", "/training/attempts"), r"fetchJson\('/training/attempts',\s*\{\s*method:\s*'POST'"), - ( - ("POST", "/training/attempts/{attempt_id}/annotations"), - r"fetchJson\(`/training/attempts/\$\{[^}]+\}/annotations`,\s*\{\s*method:\s*'POST'", - ), - ( - ("POST", "/training/attempts/{attempt_id}/submit"), - r"fetchJson\(`/training/attempts/\$\{[^}]+\}/submit`,\s*\{\s*method:\s*'POST'", - ), -) - - -def _training_api_surface_rows_from_product_doc() -> tuple[tuple[str, str, str], ...]: - text = _training_product_doc_section("API Surface") - rows = [] - for method, path, capability in re.findall( - r"^\|\s*([A-Z]+)\s*\|\s*`([^`]+)`\s*\|\s*([^|\n]+?)\s*\|", - text, - re.MULTILINE, - ): - if path.startswith("/training/"): - rows.append((method, path, " ".join(capability.split()).lower())) - return tuple(rows) - - -def _training_api_routes_from_product_doc() -> tuple[tuple[str, str], ...]: - return tuple((method, path) for method, path, _capability in _training_api_surface_rows_from_product_doc()) - - -def _supported_training_routes_from_route_request() -> tuple[tuple[str, str], ...]: - source = _route_request_source() - routes = list( - re.findall( - r'if method == "(GET|POST)" and path == "(/training/[^"]+)":', - source, - ) - ) - - for pattern, route in ( - ( - r'if method == "GET" and len\(path_parts\) == 3 and path_parts\[:2\] == \["training", "cases"\]:\s+case_id = _parse_path_int\(path_parts\[2\], "case_id"\)', - ("GET", "/training/cases/{case_id}"), - ), - ( - r'if method == "POST" and len\(path_parts\) == 4 and path_parts\[:2\] == \["training", "attempts"\] and path_parts\[3\] == "annotations":\s+attempt_id = _parse_path_int\(path_parts\[2\], "attempt_id"\)', - ("POST", "/training/attempts/{attempt_id}/annotations"), - ), - ( - r'if method == "POST" and len\(path_parts\) == 4 and path_parts\[:2\] == \["training", "attempts"\] and path_parts\[3\] == "submit":\s+attempt_id = _parse_path_int\(path_parts\[2\], "attempt_id"\)', - ("POST", "/training/attempts/{attempt_id}/submit"), - ), - ): - if re.search(pattern, source): - routes.append(route) - return tuple(routes) - - -def _training_dashboard_routes_from_app() -> tuple[tuple[str, str], ...]: - source = TRAINING_DASHBOARD_APP.read_text(encoding="utf-8") - routes = [] - for route, pattern in TRAINING_DASHBOARD_FETCH_PATTERNS: - if re.search(pattern, source, re.DOTALL): - routes.append(route) - return tuple(routes) - - -def _training_dashboard_fetch_paths_from_app() -> tuple[str, ...]: - source = TRAINING_DASHBOARD_APP.read_text(encoding="utf-8") - paths = [] - for match in re.finditer(r"fetchJson\(\s*['`](?P/training/[^'`]+)['`]", source): - path = re.sub(r"\$\{[^}]+\}", "{attempt_id}", match.group("path")) - paths.append(path.split("?", 1)[0]) - return tuple(paths) - - -def _route_request_source() -> str: - source = API_SERVER.read_text(encoding="utf-8") - match = re.search(r"^def route_request\(.*?(?=^class APIRequestHandler)", source, re.DOTALL | re.MULTILINE) - if match is None: - raise AssertionError("src/api/server.py is missing route_request") - return match.group(0) - - -def _training_product_doc_section(heading: str) -> str: - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") - match = re.search( - rf"^## {re.escape(heading)}\n(?P
.*?)(?:\n## |\Z)", - text, - re.DOTALL | re.MULTILINE, - ) - if match is None: - raise AssertionError(f"Training product README is missing the {heading} section") - return match.group("section") - - -def _training_validation_section() -> str: - return _training_product_doc_section("Validation And Errors") - - -def _training_public_error_section(path: Path) -> str: - if path == TRAINING_PRODUCT_DOC: - return _training_validation_section() - text = path.read_text(encoding="utf-8") - match = re.search( - r"^## Training public error catalog\n(?P
.*?)(?:\n## |\Z)", - text, - re.DOTALL | re.MULTILINE, - ) - if match is None: - raise AssertionError(f"{path} is missing the Training public error catalog section") - return match.group("section") - - -def _public_error_json_block(payload: dict) -> str: - return f"```json\n{json.dumps(payload, ensure_ascii=False)}\n```" - - -def _local_docker_deployment_section(path: Path) -> str: - text = path.read_text(encoding="utf-8") - match = re.search( - r"^## Local Docker [Dd]eployment\n(?P
.*?)(?:\n## |\Z)", - text, - re.DOTALL | re.MULTILINE, - ) - if match is None: - raise AssertionError(f"{path} is missing the Local Docker deployment section") - return match.group("section") - - -def _local_docker_command_lines(text: str) -> tuple[str, ...]: - return tuple( - line.strip() - for line in text.splitlines() - if line.strip().startswith(("docker ", "test ")) - ) - - -def _trading_delivery_section(path: Path) -> str: - text = path.read_text(encoding="utf-8") - match = re.search( - rf"^## {re.escape(TRADING_DELIVERY_SECTION_HEADING)}\n(?P
.*?)(?:\n## |\Z)", - text, - re.DOTALL | re.MULTILINE, - ) - if match is None: - raise AssertionError(f"{path} is missing the {TRADING_DELIVERY_SECTION_HEADING} section") - return match.group("section") - - -def _trading_delivery_command_lines(text: str) -> tuple[str, ...]: - return tuple( - line.strip() - for line in text.splitlines() - if line.strip().startswith(TRADING_DELIVERY_COMMAND_PREFIXES) - ) - - -def _training_public_privacy_lines(text: str) -> tuple[str, ...]: - lines = [] - for line in text.splitlines(): - lower_line = line.lower() - if any(phrase in lower_line for phrase in TRAINING_PUBLIC_PRIVACY_MATERIAL_PHRASES): - lines.append(lower_line.strip()) - return tuple(lines) - - -def _test_method_names_from_file(path: Path, class_name: str) -> tuple[str, ...]: - tree = ast.parse(path.read_text(encoding="utf-8")) - for node in tree.body: - if isinstance(node, ast.ClassDef) and node.name == class_name: - return tuple( - item.name - for item in node.body - if isinstance(item, ast.FunctionDef) and item.name.startswith("test_") - ) - raise AssertionError(f"{path} is missing {class_name}") - - -def _release_index_missing_required_snippets_by_doc( - text_by_doc: dict[Path, str], -) -> dict[Path, tuple[tuple[str, str], ...]]: - return { - path: tuple( - (surface, snippet) - for surface, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - for snippet in snippets - if snippet not in text - ) - for path, text in text_by_doc.items() - } +TRADING_INDEX_HTML = ROOT / "src" / "web" / "index.html" +TRADING_APP_JS = ROOT / "src" / "web" / "app.js" +TRAINING_HTML = ROOT / "src" / "web" / "training.html" +TRAINING_JS = ROOT / "src" / "web" / "training.js" +OLD_TRAINING_DOC = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md" class DocumentationConsistencyTests(unittest.TestCase): - def test_readme_lists_script_output_json_environment_variables(self) -> None: - script_env_vars = set() - for path in (ROOT / "scripts").glob("*.py"): - script_env_vars.update(re.findall(r'"([A-Z0-9_]+OUTPUT_JSON)"', path.read_text(encoding="utf-8"))) - readme = (ROOT / "README.md").read_text(encoding="utf-8") - - missing = sorted(name for name in script_env_vars if name not in readme) - - self.assertEqual(missing, []) - - def test_training_case_product_doc_lists_public_m1_m21_surface(self) -> None: - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") - - for snippet in ( - "Training panel", - "/training/cases", - "/training/cases/{case_id}", - "/training/cases/export", - "/training/cases/import", - "/training/attempts", - "/training/attempts/{attempt_id}/annotations", - "/training/attempts/{attempt_id}/submit", - "/training/progress", - ".venv/bin/python scripts/run_training_case_package.py", - ".venv/bin/python scripts/run_offline_checks.py", - "private solution material", - "Data Isolation Guarantees", - ): - with self.subTest(snippet=snippet): - self.assertIn(snippet, text) - - def test_training_case_product_doc_reflects_current_m40_maturity(self) -> None: - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") - - for snippet in ( - CURRENT_TRAINING_DOC_MATURITY_MARKER, - "API contract coverage", - "error contract coverage", - "fixture/schema contract", - "offline checks registry smoke", - "public response privacy contract coverage", - "dashboard and public docs privacy smoke coverage", - ): - with self.subTest(snippet=snippet): - self.assertIn(snippet, text) - - for stale_snippet in ("M1-M25", "through M25", *TRAINING_M41_STALE_RELEASE_WORDING): - with self.subTest(stale_snippet=stale_snippet): - self.assertNotIn(stale_snippet, text) - - def test_training_readmes_expose_m1_m40_release_index_without_raw_test_modules(self) -> None: - text_by_doc = { - path: path.read_text(encoding="utf-8") - for path in TRAINING_M41_RELEASE_INDEX_DOCS + def test_docs_inventory_is_current_and_old_training_product_doc_is_gone(self) -> None: + top_level_docs = { + path.name + for path in (ROOT / "docs").iterdir() + if path.is_file() } - missing_required_by_doc = _release_index_missing_required_snippets_by_doc(text_by_doc) - - for path, text in text_by_doc.items(): - for surface, _ in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY: - missing_surface_snippets = tuple( - snippet - for missing_surface, snippet in missing_required_by_doc[path] - if missing_surface == surface - ) - with self.subTest(path=path.name, surface=surface): - self.assertEqual(missing_surface_snippets, ()) - - with self.subTest(path=path.name, guard="stale_wording"): - for stale_snippet in TRAINING_M41_STALE_RELEASE_WORDING: - self.assertNotIn(stale_snippet, text) - - with self.subTest(path=path.name, guard="raw_test_modules"): - self.assertIsNone(re.search(TRAINING_RAW_TEST_MODULE_PATTERN, text)) - - def test_training_case_m24_m29_documentation_test_inventory_is_current(self) -> None: - test_names = set( - _test_method_names_from_file(Path(__file__), "DocumentationConsistencyTests") - ) - package_docs_test_names = set( - _test_method_names_from_file(TRAINING_DOCS_CLI_TEST, "TrainingCaseDocsCliM10Tests") - ) - inventory_names = tuple( - name - for names in TRAINING_M24_M29_DOCUMENTATION_TEST_INVENTORY.values() - for name in names - ) - unique_inventory_names = set(inventory_names) - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") - - self.assertEqual(len(inventory_names), 8) - self.assertEqual(len(unique_inventory_names), 7) - self.assertEqual(sorted(TRAINING_M24_M29_DOCUMENTATION_TEST_INVENTORY), [ - "dashboard_public_docs_privacy", - "m1_m40_maturity_wording", - "offline_registry_coverage", - "route_surface", - ]) - for surface, names in TRAINING_M24_M29_DOCUMENTATION_TEST_INVENTORY.items(): - with self.subTest(surface=surface): - self.assertTrue(names) - for name in names: - self.assertIn(name, test_names) - - self.assertIn( - TRAINING_M24_M29_PACKAGE_DOCS_INVENTORY_TEST, - package_docs_test_names, - ) - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_MARKER, text) - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_SPAN, text) - - def test_training_m41_release_index_guard_inventory_is_current(self) -> None: - module_path = Path(__file__) - module_source = module_path.read_text(encoding="utf-8") - test_names = set( - _test_method_names_from_file(module_path, "DocumentationConsistencyTests") - ) - required_guard_tests_by_role = { - "docs_coverage": ( - "test_training_readmes_expose_m1_m40_release_index_without_raw_test_modules" - ), - "inventory_self_lock": ( - "test_training_m41_release_index_guard_inventory_is_current" - ), - "inventory_shape": "test_training_m41_release_index_inventory_shape_is_stable", - "per_doc_helper_self_lock": ( - "test_training_m47_release_index_per_doc_helper_inventory_self_lock_is_current" - ), - "per_doc_helper_tuple_shape": ( - "test_training_m48_release_index_per_doc_helper_keeps_doc_tuple_shape" - ), - "per_doc_helper_empty_docs": ( - "test_training_m49_release_index_per_doc_helper_handles_empty_docs_without_mutation" - ), - "per_doc_helper_flattened_complete_doc": ( - "test_training_m50_release_index_helper_accepts_flattened_inventory_complete_doc" - ), - "required_snippet_inventory_category_shape": ( - "test_training_m54_release_index_required_snippet_inventory_category_shape_is_locked" - ), - "required_snippet_inventory_source_shape": ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked" - ), - "required_snippet_inventory_source_shape_negative_fixtures": ( - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures" - ), - "required_snippet_inventory_source_shape_diagnostics": ( - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked" - ), - "required_snippet_inventory_source_shape_positive_fixtures": ( - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures" - ), - "required_snippet_inventory_source_shape_literal_order": ( - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order" - ), - "required_snippet_inventory_source_shape_fixture_self_lock": ( - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current" - ), - "required_snippet_inventory_source_shape_fixture_constant_source_lock": ( - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked" - ), - "required_snippet_inventory_source_shape_fixture_constant_negative_sources": ( - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_diagnostics": ( - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked" - ), - "required_snippet_inventory_source_shape_fixture_constant_positive_sources": ( - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_inventory_exactness": ( - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_source_exactness": ( - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge": ( - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_duplicate_negative_fixture": ( - "test_training_m69_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_negative_fixture_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_duplicate_guard_positive_diagnostic": ( - "test_training_m70_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_guard_positive_diagnostic_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_source_shape": ( - "test_training_m71_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_source_shape_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_negative_sources": ( - "test_training_m72_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_rejects_dynamic_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostics": ( - "test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory": ( - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape": ( - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_negative_sources": ( - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics": ( - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_positive_order_sources": ( - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster": ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture": ( - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape": ( - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_negative_sources": ( - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics": ( - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked" - ), - "required_snippet_inventory_source_shape_fixture_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock": ( - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock" - ), - "release_index_public_docs_acceptance_inventory_alignment": ( - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock" - ), - "release_index_final_closure_delivery": ( - "test_training_m86_release_index_final_closure_delivery_index_self_lock" - ), - "release_index_final_closure_delivery_guard_self_lock": ( - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock" - ), - "release_index_final_closure_delivery_guard_source_self_lock": ( - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock" - ), - "release_index_final_closure_delivery_guard_source_chain_self_lock": ( - "test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock" - ), - "release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock": ( - "test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock" - ), - "release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock": ( - "test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock" - ), - "release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock": ( - "test_training_m92_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock" - ), - "per_doc_helper_exact_category_coverage": ( - "test_training_m53_release_index_doc_category_coverage_is_exact" - ), - "stale_wording": "test_training_case_product_doc_reflects_current_m40_maturity", - } - required_guard_tests = tuple(required_guard_tests_by_role.values()) - required_release_snippets = ( - CURRENT_TRAINING_DOC_MATURITY_MARKER, - CURRENT_TRAINING_DOC_MATURITY_SPAN, - CURRENT_TRAINING_DOC_MATURITY_PHRASE, - *TRAINING_ACCEPTANCE_VALIDATION_DOC_SNIPPETS, - "API contract coverage", - "error contract coverage", - "privacy contract coverage", - "package smoke", - "training_case_package", - "dashboard_js_syntax", - "dashboard and public docs privacy smoke coverage", - "single-set local Docker deployment", - "Docker smoke guard coverage", - ) - required_stale_inventory_names = ("m1_m28_maturity_wording",) - required_stale_wording = ("M1-M39", "through M39", "through-M39") - expected_release_snippets = tuple( - snippet - for _, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - for snippet in snippets - ) - active_release_inventory_values = "\n".join( - ( - *TRAINING_M24_M29_DOCUMENTATION_TEST_INVENTORY, - *TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, - *( - category - for category, _ in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - ), - *TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS, - CURRENT_TRAINING_DOC_MATURITY_MARKER, - CURRENT_TRAINING_DOC_MATURITY_SPAN, - CURRENT_TRAINING_DOC_MATURITY_PHRASE, - ) - ) - guard_match = re.search( - rf"^ def {TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[0]}" - r"\(.*?(?=^ def |\Z)", - module_source, - re.DOTALL | re.MULTILINE, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_DOCS, - (PROJECT_README, TRAINING_PRODUCT_DOC), - ) - self.assertEqual(TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, required_guard_tests) - self.assertEqual(len(required_guard_tests), len(set(required_guard_tests))) - self.assertEqual( - sorted( - category - for category, _ in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - ), - [ - "acceptance_offline_validation", - "current_m1_m40_marker", - "dashboard_public_docs_privacy", - "docker_smoke_guard_coverage", - "package_smoke_offline_registry", - "public_api_privacy_contracts", - "single_set_docker_deployment", - ], - ) - self.assertEqual(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS, expected_release_snippets) - self.assertEqual( - TRAINING_M41_STALE_RELEASE_INVENTORY_NAMES, - required_stale_inventory_names, - ) - self.assertEqual(TRAINING_M41_STALE_RELEASE_WORDING, required_stale_wording) - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_MARKER, "M1-M40") - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_SPAN, "through M40") - self.assertEqual( - CURRENT_TRAINING_DOC_MATURITY_PHRASE, - "current product maturity through M40", - ) - - for role, guard_test in required_guard_tests_by_role.items(): - with self.subTest(release_index_guard=role): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName( - f"{type(self).__module__}.DocumentationConsistencyTests.{guard_test}" - ) - - self.assertIn(guard_test, test_names) - self.assertEqual(loader.errors, []) - self.assertGreater(suite.countTestCases(), 0) - self.assertIsNotNone(guard_match) - guard_source = guard_match.group(0) - for guard_reference in ( - "TRAINING_M41_RELEASE_INDEX_DOCS", - "TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY", - "_release_index_missing_required_snippets_by_doc", - "missing_required_by_doc[path]", - "TRAINING_M41_STALE_RELEASE_WORDING", - "TRAINING_RAW_TEST_MODULE_PATTERN", - ): - with self.subTest(guard_reference=guard_reference): - self.assertIn(guard_reference, guard_source) - - complete_release_doc = "\n".join(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS) - missing_surface, missing_snippet = next( - (surface, snippets[0]) - for surface, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - ) - missing_release_doc = complete_release_doc.replace(missing_snippet, "", 1) - negative_fixture_missing_by_doc = _release_index_missing_required_snippets_by_doc( + top_level_docs, { - PROJECT_README: missing_release_doc, - TRAINING_PRODUCT_DOC: complete_release_doc, - } + "AI_ICT_实盘交易分析系统开发计划_v1.md", + "ICT交易全书_出版终校版.md", + "training_console_public_surface_v1.md", + }, ) + self.assertTrue(TRAINING_PACKAGE_SMOKE_DOC.is_file()) + self.assertFalse(OLD_TRAINING_DOC.exists()) - self.assertEqual(negative_fixture_missing_by_doc[TRAINING_PRODUCT_DOC], ()) - self.assertEqual( - negative_fixture_missing_by_doc[PROJECT_README], - ((missing_surface, missing_snippet),), - ) - - for surface, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY: - with self.subTest(surface=surface): - self.assertTrue(snippets) - - for snippet in required_release_snippets: - with self.subTest(snippet=snippet): - self.assertIn(snippet, TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS) - - for stale_value in ( - *required_stale_inventory_names, - *required_stale_wording, - ): - with self.subTest(stale_value=stale_value): - self.assertNotIn(stale_value, active_release_inventory_values) - - def test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock(self) -> None: - expected_inventory = ( - "test_training_readmes_expose_m1_m40_release_index_without_raw_test_modules", - "test_training_m41_release_index_guard_inventory_is_current", - "test_training_m41_release_index_inventory_shape_is_stable", - "test_training_m47_release_index_per_doc_helper_inventory_self_lock_is_current", - "test_training_m48_release_index_per_doc_helper_keeps_doc_tuple_shape", - "test_training_m49_release_index_per_doc_helper_handles_empty_docs_without_mutation", - "test_training_m50_release_index_helper_accepts_flattened_inventory_complete_doc", - "test_training_m54_release_index_required_snippet_inventory_category_shape_is_locked", - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock", - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock", - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock", - "test_training_m69_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_negative_fixture_self_lock", - "test_training_m70_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_guard_positive_diagnostic_self_lock", - "test_training_m71_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_source_shape_self_lock", - "test_training_m72_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_rejects_dynamic_sources", - "test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked", - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock", - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock", - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources", - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked", - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources", - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock", - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock", - "test_training_m86_release_index_final_closure_delivery_index_self_lock", - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock", - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock", - "test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock", - "test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock", - "test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock", - "test_training_m92_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock", - "test_training_m53_release_index_doc_category_coverage_is_exact", - "test_training_case_product_doc_reflects_current_m40_maturity", - ) - helper_guard_sequence = ( - "test_training_m47_release_index_per_doc_helper_inventory_self_lock_is_current", - "test_training_m48_release_index_per_doc_helper_keeps_doc_tuple_shape", - "test_training_m49_release_index_per_doc_helper_handles_empty_docs_without_mutation", - "test_training_m50_release_index_helper_accepts_flattened_inventory_complete_doc", - ) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - self.assertEqual(TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, expected_inventory) - self.assertEqual( - len(TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY), - len(set(TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY)), - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[3:7], - helper_guard_sequence, - ) - for method_name in TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - def test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current( - self, - ) -> None: - expected_source_shape_fixture_guards = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - ) - expected_literal_source_shape_fixture_guards = ( - expected_source_shape_fixture_guards[:5] - ) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - self_lock_name = ( - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current" - ) - source_shape_inventory_start = ( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - expected_source_shape_fixture_guards[0] - ) - ) - source_shape_inventory_end = source_shape_inventory_start + len( - expected_source_shape_fixture_guards - ) - discovered_method_names = _test_method_names_from_file( - Path(__file__), - "DocumentationConsistencyTests", - ) - source_shape_start = discovered_method_names.index( - expected_literal_source_shape_fixture_guards[0] - ) - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS, - expected_source_shape_fixture_guards, - ) - self.assertEqual( - len(TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS), - len(set(TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS)), - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - source_shape_inventory_start:source_shape_inventory_end - ], - TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS, - ) - self.assertEqual( - discovered_method_names[ - source_shape_start : source_shape_start - + len(expected_literal_source_shape_fixture_guards) - ], - expected_literal_source_shape_fixture_guards, - ) - self.assertIn(self_lock_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for method_name in TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - @staticmethod - def _release_index_source_shape_fixture_guard_method_names_from_source( - module_source: str, - ) -> tuple[str, ...]: - constant_name = "TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS" - module_tree = ast.parse(module_source) - - constant_assignments = tuple( - node - for node in module_tree.body - if isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == constant_name - ) - - if len(constant_assignments) != 1: - raise AssertionError( - "source shape fixture guard methods must have one assignment" - ) - - constant_source = constant_assignments[0].value - disallowed_source_nodes = ( - ast.Call, - ast.DictComp, - ast.GeneratorExp, - ast.List, - ast.ListComp, - ast.Name, - ast.SetComp, - ast.Starred, - ) - dynamic_source_node = next( - ( - node - for node in ast.walk(constant_source) - if isinstance(node, disallowed_source_nodes) - ), - None, - ) - if dynamic_source_node is not None: - dynamic_node = type(dynamic_source_node).__name__ - raise AssertionError( - "source shape fixture guard methods use dynamic source node " - f"{dynamic_node}" - ) - - if not isinstance(constant_source, ast.Tuple): - raise AssertionError( - "source shape fixture guard methods must be a literal tuple assignment" - ) - - if not constant_source.elts or not all( - isinstance(element, ast.Constant) and isinstance(element.value, str) - for element in constant_source.elts - ): - raise AssertionError( - "source shape fixture guard methods must use literal method names" - ) - - try: - method_names = ast.literal_eval(constant_source) - except (SyntaxError, ValueError) as exc: - raise AssertionError( - "source shape fixture guard methods must use literal method names" - ) from exc - - if not all(isinstance(method_name, str) for method_name in method_names): - raise AssertionError( - "source shape fixture guard methods must use literal method names" - ) - - return method_names - - def test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked( - self, - ) -> None: - expected_source_shape_fixture_guards = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - - self.assertEqual( - self._release_index_source_shape_fixture_guard_method_names_from_source( - module_source - ), - expected_source_shape_fixture_guards, - ) - - def test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources( - self, - ) -> None: - constant_name = "TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS" - first_method = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked" - ) - second_method = ( - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures" - ) - invalid_sources = { - "tuple_call": ( - f"{constant_name} = tuple([{first_method!r}, {second_method!r}])", - "dynamic source node Call", - ), - "sorted_call": ( - f"{constant_name} = sorted(({first_method!r}, {second_method!r}))", - "dynamic source node Call", - ), - "name_alias": ( - f"fixture_guard_methods = ({first_method!r}, {second_method!r})\n" - f"{constant_name} = fixture_guard_methods", - "dynamic source node Name", - ), - "list_literal": ( - f"{constant_name} = [{first_method!r}, {second_method!r}]", - "dynamic source node List", - ), - "list_comprehension": ( - f"{constant_name} = [method for method in ({first_method!r}, {second_method!r})]", - "dynamic source node ListComp", - ), - } - - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_source_shape_fixture_guard_method_names_from_source( - source - ) - - def test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked( - self, - ) -> None: - constant_name = "TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS" - guard_name = ( - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked" - ) - invalid_sources = { - "missing_constant_assignment": ( - "UNRELATED_FIXTURE_GUARD_METHODS = ('literal_method_alpha',)", - "must have one assignment", - ), - "duplicate_constant_assignment": ( - f"{constant_name} = ('literal_method_alpha',)\n" - f"{constant_name} = ('literal_method_beta',)", - "must have one assignment", - ), - "empty_tuple_assignment": ( - f"{constant_name} = ()", - "must use literal method names", - ), - "integer_method_name": ( - f"{constant_name} = ('literal_method_alpha', 123)", - "must use literal method names", - ), - "none_method_name": ( - f"{constant_name} = ('literal_method_alpha', None)", - "must use literal method names", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_source_shape_fixture_guard_method_names_from_source( - source - ) - - def test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources( - self, - ) -> None: - constant_name = "TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS" - guard_name = ( - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources" - ) - expected_literal_order = ( - "literal_method_gamma", - "literal_method_alpha", - "literal_method_beta", - ) - ordered_source = f""" -{constant_name} = ( - {expected_literal_order[0]!r}, - {expected_literal_order[1]!r}, - {expected_literal_order[2]!r}, -) -""" - single_item_source = f"{constant_name} = ('literal_method_single',)" - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - source_method_names = ( - self._release_index_source_shape_fixture_guard_method_names_from_source( - ordered_source - ) - ) - - self.assertEqual(source_method_names, expected_literal_order) - self.assertNotEqual(source_method_names, tuple(sorted(source_method_names))) - self.assertEqual( - self._release_index_source_shape_fixture_guard_method_names_from_source( - single_item_source - ), - ("literal_method_single",), - ) - - def test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock( - self, - ) -> None: - expected_source_shape_fixture_guards = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - ) - guard_name = ( - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock" - ) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - source_shape_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - expected_source_shape_fixture_guards[0] - ) - source_shape_end = source_shape_start + len(expected_source_shape_fixture_guards) - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS, - expected_source_shape_fixture_guards, - ) - self.assertEqual( - len(TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS), - len(set(TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS)), - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - source_shape_start:source_shape_end - ], - TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS, - ) - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for method_name in TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - def test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock( - self, - ) -> None: - expected_source_shape_fixture_guards = ( - "test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked", - "test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures", - "test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked", - "test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures", - "test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order", - "test_training_m60_release_index_source_shape_fixture_guard_inventory_self_lock_is_current", - "test_training_m61_release_index_source_shape_fixture_guard_methods_constant_source_shape_is_locked", - "test_training_m62_release_index_source_shape_fixture_guard_methods_reject_dynamic_constant_sources", - "test_training_m63_release_index_source_shape_fixture_guard_methods_constant_diagnostics_are_locked", - "test_training_m64_release_index_source_shape_fixture_guard_methods_accept_valid_literal_sources", - ) - guard_name = ( - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - source_method_names = ( - self._release_index_source_shape_fixture_guard_method_names_from_source( - module_source - ) - ) - - self.assertEqual( - source_method_names, - TRAINING_M41_RELEASE_INDEX_SOURCE_SHAPE_FIXTURE_GUARD_METHODS, - ) - self.assertEqual(source_method_names, expected_source_shape_fixture_guards) - self.assertEqual(len(source_method_names), len(set(source_method_names))) - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - - def test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock( - self, - ) -> None: - exactness_bridge_guard_names = ( - TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS - ) - ( - inventory_exactness_guard, - source_exactness_guard, - guard_name, - ) = exactness_bridge_guard_names - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - for method_name in exactness_bridge_guard_names: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count(method_name), - 1, - ) - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - inventory_exactness_index = ( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - inventory_exactness_guard - ) - ) - source_exactness_index = ( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - source_exactness_guard - ) - ) - bridge_exactness_index = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - guard_name - ) - self.assertEqual(source_exactness_index, inventory_exactness_index + 1) - self.assertEqual(bridge_exactness_index, source_exactness_index + 1) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - inventory_exactness_index : bridge_exactness_index + 1 - ], - exactness_bridge_guard_names, - ) - - def test_training_m69_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_negative_fixture_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m69_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_negative_fixture_self_lock" - ) - exactness_bridge_guard_names = ( - TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS - ) - bridge_exactness_guard = exactness_bridge_guard_names[-1] - synthetic_duplicate_inventory = ( - *TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, - bridge_exactness_guard, - ) - exact_once_counts = tuple( - synthetic_duplicate_inventory.count(method_name) - for method_name in exactness_bridge_guard_names - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual( - synthetic_duplicate_inventory[:-1], - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, - ) - self.assertNotEqual(exact_once_counts, (1, 1, 1)) - self.assertEqual(exact_once_counts, (1, 1, 2)) - - def test_training_m70_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_guard_positive_diagnostic_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m70_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_duplicate_guard_positive_diagnostic_self_lock" - ) - exactness_bridge_guard_names = ( - TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS - ) - exact_once_counts = tuple( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count(method_name) - for method_name in exactness_bridge_guard_names - ) - bridge_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - exactness_bridge_guard_names[0] - ) - bridge_indices = tuple( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index(method_name) - for method_name in exactness_bridge_guard_names - ) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual(exact_once_counts, (1, 1, 1)) - self.assertEqual( - bridge_indices, - tuple(range(bridge_start, bridge_start + len(exactness_bridge_guard_names))), - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - bridge_start : bridge_start + len(exactness_bridge_guard_names) - ], - exactness_bridge_guard_names, - ) - for method_name in exactness_bridge_guard_names: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - @staticmethod - def _release_index_exactness_bridge_guard_method_names_from_source( - module_source: str, - ) -> tuple[str, ...]: - constant_name = "TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS" - module_tree = ast.parse(module_source) - top_level_writes = tuple( - node - for node in module_tree.body - if ( - isinstance(node, ast.Assign) - and any( - isinstance(target, ast.Name) and target.id == constant_name - for target in node.targets - ) - ) - or ( - isinstance(node, (ast.AnnAssign, ast.AugAssign)) - and isinstance(node.target, ast.Name) - and node.target.id == constant_name - ) - ) - - if len(top_level_writes) != 1: - raise AssertionError("exactness bridge guard methods must have one assignment") - - constant_assignment = top_level_writes[0] - if ( - not isinstance(constant_assignment, ast.Assign) - or len(constant_assignment.targets) != 1 - or not isinstance(constant_assignment.targets[0], ast.Name) - or constant_assignment.targets[0].id != constant_name - ): - raise AssertionError( - "exactness bridge guard methods must use one simple assignment" - ) - - constant_source = constant_assignment.value - disallowed_source_node_types = ( - ast.Call, - ast.DictComp, - ast.GeneratorExp, - ast.List, - ast.ListComp, - ast.Name, - ast.SetComp, - ast.Starred, - ) - dynamic_source_node = next( - ( - node - for node in ast.walk(constant_source) - if isinstance(node, disallowed_source_node_types) - ), - None, - ) - if dynamic_source_node is not None: - dynamic_node = type(dynamic_source_node).__name__ - raise AssertionError( - "exactness bridge guard methods use dynamic source node " - f"{dynamic_node}" - ) - - if not isinstance(constant_source, ast.Tuple) or len(constant_source.elts) != 3: - raise AssertionError( - "exactness bridge guard methods must be a literal triplet tuple assignment" - ) - - if not all( - isinstance(element, ast.Constant) and isinstance(element.value, str) - for element in constant_source.elts - ): - raise AssertionError( - "exactness bridge guard methods must use literal method names" - ) - - try: - method_names = ast.literal_eval(constant_source) - except (SyntaxError, ValueError) as exc: - raise AssertionError( - "exactness bridge guard methods must use literal method names" - ) from exc - - if ( - not isinstance(method_names, tuple) - or len(method_names) != 3 - or not all(isinstance(method_name, str) for method_name in method_names) - ): - raise AssertionError( - "exactness bridge guard methods must use literal method names" - ) - - return method_names - - def test_training_m71_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_source_shape_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m71_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_source_shape_self_lock" - ) - expected_bridge_methods = ( - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock", - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock", - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - source_bridge_methods = ( - self._release_index_exactness_bridge_guard_method_names_from_source( - module_source - ) - ) - - self.assertEqual( - source_bridge_methods, - TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS, - ) - self.assertEqual(source_bridge_methods, expected_bridge_methods) - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - - def test_training_m72_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_rejects_dynamic_sources( - self, - ) -> None: - constant_name = "TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS" - guard_name = ( - "test_training_m72_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_rejects_dynamic_sources" - ) - first_method = ( - "test_training_m65_release_index_source_shape_fixture_guard_methods_constant_inventory_exactness_self_lock" - ) - second_method = ( - "test_training_m66_release_index_source_shape_fixture_guard_methods_constant_source_exactness_self_lock" - ) - third_method = ( - "test_training_m67_release_index_source_shape_fixture_guard_methods_constant_inventory_source_exactness_bridge_self_lock" - ) - invalid_sources = { - "list_literal": ( - f"{constant_name} = [{first_method!r}, {second_method!r}, {third_method!r}]", - "dynamic source node List", - ), - "name_alias": ( - f"bridge_methods = ({first_method!r}, {second_method!r}, {third_method!r})\n" - f"{constant_name} = bridge_methods", - "dynamic source node Name", - ), - "tuple_call": ( - f"{constant_name} = tuple(({first_method!r}, {second_method!r}, {third_method!r}))", - "dynamic source node Call", - ), - "starred_tuple": ( - f"{constant_name} = ({first_method!r}, *({second_method!r},), {third_method!r})", - "dynamic source node Starred", - ), - "list_comprehension": ( - f"{constant_name} = " - f"[method for method in ({first_method!r}, {second_method!r}, {third_method!r})]", - "dynamic source node ListComp", - ), - "generator_expression": ( - f"{constant_name} = " - f"(method for method in ({first_method!r}, {second_method!r}, {third_method!r}))", - "dynamic source node GeneratorExp", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_exactness_bridge_guard_method_names_from_source( - source - ) - - def test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked( - self, - ) -> None: - constant_name = "TRAINING_M41_RELEASE_INDEX_EXACTNESS_BRIDGE_GUARD_METHODS" - guard_name = ( - "test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked" - ) - literal_methods = ( - "literal_bridge_alpha", - "literal_bridge_beta", - "literal_bridge_gamma", - ) - valid_triplet_source = ( - f"({literal_methods[0]!r}, {literal_methods[1]!r}, " - f"{literal_methods[2]!r})" - ) - invalid_sources = { - "missing_assignment": ( - "UNRELATED_BRIDGE_GUARD_METHODS = ('literal_bridge_alpha',)", - "must have one assignment", - ), - "duplicate_top_level_assignment": ( - f"{constant_name} = {valid_triplet_source}\n" - f"{constant_name} = {valid_triplet_source}", - "must have one assignment", - ), - "multiple_assignment_targets": ( - f"{constant_name} = bridge_methods = {valid_triplet_source}", - "must use one simple assignment", - ), - "empty_tuple": ( - f"{constant_name} = ()", - "must be a literal triplet tuple assignment", - ), - "two_item_tuple": ( - f"{constant_name} = ({literal_methods[0]!r}, {literal_methods[1]!r})", - "must be a literal triplet tuple assignment", - ), - "four_item_tuple": ( - f"{constant_name} = (" - f"{literal_methods[0]!r}, {literal_methods[1]!r}, " - f"{literal_methods[2]!r}, 'literal_bridge_delta')", - "must be a literal triplet tuple assignment", - ), - "integer_literal_method_name": ( - f"{constant_name} = ({literal_methods[0]!r}, 123, {literal_methods[2]!r})", - "must use literal method names", - ), - "none_literal_method_name": ( - f"{constant_name} = ({literal_methods[0]!r}, None, {literal_methods[2]!r})", - "must use literal method names", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_exactness_bridge_guard_method_names_from_source( - source - ) - - def test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock" - ) - m73_guard_name = ( - "test_training_m73_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostics_are_locked" - ) - expected_fixture_diagnostics = ( - ("missing_assignment", "must have one assignment"), - ("duplicate_top_level_assignment", "must have one assignment"), - ("multiple_assignment_targets", "must use one simple assignment"), - ( - "empty_tuple", - "must be a literal triplet tuple assignment", - ), - ( - "two_item_tuple", - "must be a literal triplet tuple assignment", - ), - ( - "four_item_tuple", - "must be a literal triplet tuple assignment", - ), - ("integer_literal_method_name", "must use literal method names"), - ("none_literal_method_name", "must use literal method names"), - ) - allowed_diagnostic_snippets = ( - "must have one assignment", - "must use one simple assignment", - "must be a literal triplet tuple assignment", - "must use literal method names", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - m73_methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == m73_guard_name - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual(len(m73_methods), 1) - m73_method = m73_methods[0] - invalid_source_assignments = tuple( - node - for node in ast.walk(m73_method) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == "invalid_sources" - ) - ) - - self.assertEqual(len(invalid_source_assignments), 1) - invalid_sources = invalid_source_assignments[0].value - self.assertIsInstance(invalid_sources, ast.Dict) - fixture_diagnostics = tuple( - (fixture_key.value, fixture_value.elts[1].value) - for fixture_key, fixture_value in zip( - invalid_sources.keys, - invalid_sources.values, - ) - ) - - fixture_names = tuple(name for name, _ in fixture_diagnostics) - self.assertEqual(fixture_diagnostics, expected_fixture_diagnostics) - self.assertEqual(len(fixture_names), len(set(fixture_names))) - for fixture_name, diagnostic_snippet in fixture_diagnostics: - with self.subTest(fixture_name=fixture_name): - self.assertTrue(diagnostic_snippet) - self.assertIn(diagnostic_snippet, allowed_diagnostic_snippets) - - invalid_source_loops = tuple( - node - for node in ast.walk(m73_method) - if ( - isinstance(node, ast.For) - and isinstance(node.iter, ast.Call) - and isinstance(node.iter.func, ast.Attribute) - and node.iter.func.attr == "items" - and isinstance(node.iter.func.value, ast.Name) - and node.iter.func.value.id == "invalid_sources" - ) - ) - self.assertEqual(len(invalid_source_loops), 1) - invalid_source_loop = ( - ast.get_source_segment(module_source, invalid_source_loops[0]) - or "" - ) - for loop_reference in ( - "fixture_name, (source, error_snippet)", - "self.subTest(fixture_name=fixture_name)", - "_release_index_exactness_bridge_guard_method_names_from_source", - ): - with self.subTest(loop_reference=loop_reference): - self.assertIn(loop_reference, invalid_source_loop) - - @staticmethod - def _release_index_m74_diagnostic_fixture_inventory_source_nodes( - m74_method: ast.FunctionDef, - ) -> tuple[ast.Tuple, ast.Tuple]: - local_names = ( - "expected_fixture_diagnostics", - "allowed_diagnostic_snippets", - ) - disallowed_source_node_types = ( - ast.BinOp, - ast.Call, - ast.Dict, - ast.DictComp, - ast.GeneratorExp, - ast.List, - ast.ListComp, - ast.Name, - ast.Set, - ast.SetComp, - ast.Starred, - ) - source_nodes = [] - - for local_name in local_names: - def local_write(node: ast.AST) -> bool: - if isinstance(node, ast.Assign): - targets = node.targets - elif isinstance(node, (ast.AnnAssign, ast.AugAssign)): - targets = (node.target,) - else: - return False - return any( - isinstance(target, ast.Name) and target.id == local_name - for target in targets - ) - - writes = tuple( - node for node in ast.walk(m74_method) if local_write(node) - ) - - if len(writes) != 1: - raise AssertionError( - "diagnostic fixture inventory source shape must have one " - f"{local_name} assignment" - ) - - assignment = writes[0] - if ( - not isinstance(assignment, ast.Assign) - or len(assignment.targets) != 1 - or not isinstance(assignment.targets[0], ast.Name) - or assignment.targets[0].id != local_name - ): - raise AssertionError( - "diagnostic fixture inventory source shape must use one " - f"simple {local_name} assignment" - ) - - source_node = assignment.value - dynamic_source_node = next( - ( - node - for node in ast.walk(source_node) - if isinstance(node, disallowed_source_node_types) - ), - None, - ) - if dynamic_source_node is not None: - dynamic_node = type(dynamic_source_node).__name__ - raise AssertionError( - "diagnostic fixture inventory source shape uses dynamic " - f"source node {dynamic_node}" - ) - - if not isinstance(source_node, ast.Tuple) or not source_node.elts: - raise AssertionError( - "diagnostic fixture inventory source shape must use " - "literal tuple assignments" - ) - - source_nodes.append(source_node) - - return (source_nodes[0], source_nodes[1]) - - def test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock" - ) - m74_guard_name = ( - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock" - ) - expected_fixture_diagnostics = ( - ("missing_assignment", "must have one assignment"), - ("duplicate_top_level_assignment", "must have one assignment"), - ("multiple_assignment_targets", "must use one simple assignment"), - ( - "empty_tuple", - "must be a literal triplet tuple assignment", - ), - ( - "two_item_tuple", - "must be a literal triplet tuple assignment", - ), - ( - "four_item_tuple", - "must be a literal triplet tuple assignment", - ), - ("integer_literal_method_name", "must use literal method names"), - ("none_literal_method_name", "must use literal method names"), - ) - expected_allowed_diagnostic_snippets = ( - "must have one assignment", - "must use one simple assignment", - "must be a literal triplet tuple assignment", - "must use literal method names", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - m74_methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == m74_guard_name - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual(len(m74_methods), 1) - m74_method = m74_methods[0] - fixture_source, snippet_source = ( - self._release_index_m74_diagnostic_fixture_inventory_source_nodes( - m74_method - ) - ) - - self.assertIsInstance(fixture_source, ast.Tuple) - self.assertIsInstance(snippet_source, ast.Tuple) - fixture_diagnostics = ast.literal_eval(fixture_source) - diagnostic_snippets = ast.literal_eval(snippet_source) - self.assertEqual(fixture_diagnostics, expected_fixture_diagnostics) - self.assertEqual(diagnostic_snippets, expected_allowed_diagnostic_snippets) - self.assertNotEqual(fixture_diagnostics, tuple(sorted(fixture_diagnostics))) - self.assertNotEqual(diagnostic_snippets, tuple(sorted(diagnostic_snippets))) - - self.assertEqual(len(fixture_source.elts), 8) - self.assertTrue( - all( - isinstance(fixture_tuple, ast.Tuple) and len(fixture_tuple.elts) == 2 - for fixture_tuple in fixture_source.elts - ) - ) - fixture_literal_nodes = tuple( - fixture_literal - for fixture_tuple in fixture_source.elts - for fixture_literal in fixture_tuple.elts - ) - self.assertTrue( - all( - isinstance(fixture_literal, ast.Constant) - and isinstance(fixture_literal.value, str) - for fixture_literal in fixture_literal_nodes - ) - ) - self.assertEqual(len(snippet_source.elts), 4) - self.assertTrue( - all( - isinstance(snippet_literal, ast.Constant) - and isinstance(snippet_literal.value, str) - and bool(snippet_literal.value) - for snippet_literal in snippet_source.elts - ) - ) - - def test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources( - self, - ) -> None: - guard_name = ( - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources" - ) - fixture_pair = "('missing_assignment', 'must have one assignment')" - snippet = "'must have one assignment'" - valid_fixture_source = f"({fixture_pair},)" - valid_snippet_source = f"({snippet},)" - - def synthetic_m74_method( - fixture_source: str, - snippet_source: str, - prefix: str = "", - ) -> ast.FunctionDef: - module_tree = ast.parse( - "def test_training_m74_synthetic_guard(self):\n" - f"{prefix}" - f" expected_fixture_diagnostics = {fixture_source}\n" - f" allowed_diagnostic_snippets = {snippet_source}\n" - ) - self.assertEqual(len(module_tree.body), 1) - method = module_tree.body[0] - self.assertIsInstance(method, ast.FunctionDef) - return method - - invalid_sources = { - "fixture_list_literal": ( - f"[{fixture_pair}]", - valid_snippet_source, - "", - "List", - ), - "fixture_name_alias": ( - "fixture_diagnostics", - valid_snippet_source, - f" fixture_diagnostics = ({fixture_pair},)\n", - "Name", - ), - "fixture_tuple_call": ( - f"tuple(({fixture_pair},))", - valid_snippet_source, - "", - "Call", - ), - "fixture_dict_literal": ( - "{'missing_assignment': 'must have one assignment'}", - valid_snippet_source, - "", - "Dict", - ), - "fixture_set_literal": ( - f"{{{fixture_pair}}}", - valid_snippet_source, - "", - "Set", - ), - "fixture_list_comprehension": ( - f"[diagnostic for diagnostic in ({fixture_pair},)]", - valid_snippet_source, - "", - "ListComp", - ), - "snippet_generator_expression": ( - valid_fixture_source, - f"(snippet for snippet in ({snippet},))", - "", - "GeneratorExp", - ), - "snippet_starred_tuple": ( - valid_fixture_source, - f"(*({snippet},),)", - "", - "Starred", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, ( - fixture_source, - snippet_source, - prefix, - node_type, - ) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex( - AssertionError, - re.escape(f"dynamic source node {node_type}"), - ): - self._release_index_m74_diagnostic_fixture_inventory_source_nodes( - synthetic_m74_method(fixture_source, snippet_source, prefix) - ) - - def test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked( - self, - ) -> None: - guard_name = ( - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked" - ) - fixture_pair = "('missing_assignment', 'must have one assignment')" - snippet = "'must have one assignment'" - valid_fixture_source = f"({fixture_pair},)" - valid_snippet_source = f"({snippet},)" - expected_assignment = ( - f" expected_fixture_diagnostics = {valid_fixture_source}\n" - ) - allowed_assignment = ( - f" allowed_diagnostic_snippets = {valid_snippet_source}\n" - ) - - def synthetic_m74_method(source_body: str) -> ast.FunctionDef: - module_tree = ast.parse( - "def test_training_m74_synthetic_guard(self):\n" - f"{source_body}" - ) - self.assertEqual(len(module_tree.body), 1) - method = module_tree.body[0] - self.assertIsInstance(method, ast.FunctionDef) - return method - - invalid_sources = { - "missing_expected_assignment": ( - allowed_assignment, - "must have one expected_fixture_diagnostics assignment", - ), - "duplicate_expected_assignment": ( - f"{expected_assignment}{expected_assignment}{allowed_assignment}", - "must have one expected_fixture_diagnostics assignment", - ), - "annotated_expected_assignment": ( - f" expected_fixture_diagnostics: tuple = {valid_fixture_source}\n" - f"{allowed_assignment}", - "must use one simple expected_fixture_diagnostics assignment", - ), - "augmented_expected_assignment": ( - f" expected_fixture_diagnostics += {valid_fixture_source}\n" - f"{allowed_assignment}", - "must use one simple expected_fixture_diagnostics assignment", - ), - "missing_allowed_assignment": ( - expected_assignment, - "must have one allowed_diagnostic_snippets assignment", - ), - "duplicate_allowed_assignment": ( - f"{expected_assignment}{allowed_assignment}{allowed_assignment}", - "must have one allowed_diagnostic_snippets assignment", - ), - "annotated_allowed_assignment": ( - f"{expected_assignment}" - f" allowed_diagnostic_snippets: tuple = {valid_snippet_source}\n", - "must use one simple allowed_diagnostic_snippets assignment", - ), - "augmented_allowed_assignment": ( - f"{expected_assignment}" - f" allowed_diagnostic_snippets += {valid_snippet_source}\n", - "must use one simple allowed_diagnostic_snippets assignment", - ), - "fixture_scalar_string": ( - f" expected_fixture_diagnostics = {snippet}\n" - f"{allowed_assignment}", - "literal tuple assignments", - ), - "snippet_scalar_string": ( - f"{expected_assignment}" - f" allowed_diagnostic_snippets = {snippet}\n", - "literal tuple assignments", - ), - "fixture_empty_tuple": ( - f" expected_fixture_diagnostics = ()\n{allowed_assignment}", - "literal tuple assignments", - ), - "snippet_empty_tuple": ( - f"{expected_assignment} allowed_diagnostic_snippets = ()\n", - "literal tuple assignments", - ), - "fixture_empty_list": ( - f" expected_fixture_diagnostics = []\n{allowed_assignment}", - "dynamic source node List", - ), - "snippet_empty_list": ( - f"{expected_assignment} allowed_diagnostic_snippets = []\n", - "dynamic source node List", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source_body, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex( - AssertionError, - re.escape(error_snippet), - ): - self._release_index_m74_diagnostic_fixture_inventory_source_nodes( - synthetic_m74_method(source_body) - ) - - def test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources( - self, - ) -> None: - guard_name = ( - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources" - ) - expected_fixture_diagnostics = ( - ("zeta_fixture_gap", "zeta diagnostic stays literal"), - ("alpha_fixture_gap", "alpha diagnostic stays literal"), - ("mango_fixture_gap", "mango diagnostic stays literal"), - ) - expected_allowed_diagnostic_snippets = ( - "zeta diagnostic stays literal", - "alpha diagnostic stays literal", - "mango diagnostic stays literal", - ) - disallowed_source_node_types = ( - ast.BinOp, - ast.Call, - ast.Dict, - ast.DictComp, - ast.GeneratorExp, - ast.List, - ast.ListComp, - ast.Name, - ast.Set, - ast.SetComp, - ast.Starred, - ) - - def synthetic_m74_method() -> ast.FunctionDef: - module_tree = ast.parse( - "def test_training_m74_synthetic_guard(self):\n" - " expected_fixture_diagnostics = (\n" - " ('zeta_fixture_gap', 'zeta diagnostic stays literal'),\n" - " ('alpha_fixture_gap', 'alpha diagnostic stays literal'),\n" - " ('mango_fixture_gap', 'mango diagnostic stays literal'),\n" - " )\n" - " allowed_diagnostic_snippets = (\n" - " 'zeta diagnostic stays literal',\n" - " 'alpha diagnostic stays literal',\n" - " 'mango diagnostic stays literal',\n" - " )\n" - ) - self.assertEqual(len(module_tree.body), 1) - method = module_tree.body[0] - self.assertIsInstance(method, ast.FunctionDef) - return method - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - fixture_source, snippet_source = ( - self._release_index_m74_diagnostic_fixture_inventory_source_nodes( - synthetic_m74_method() - ) - ) - - self.assertIsInstance(fixture_source, ast.Tuple) - self.assertIsInstance(snippet_source, ast.Tuple) - fixture_diagnostics = ast.literal_eval(fixture_source) - diagnostic_snippets = ast.literal_eval(snippet_source) - self.assertEqual(fixture_diagnostics, expected_fixture_diagnostics) - self.assertEqual(diagnostic_snippets, expected_allowed_diagnostic_snippets) - self.assertNotEqual( - expected_fixture_diagnostics, - tuple(sorted(expected_fixture_diagnostics)), - ) - self.assertNotEqual( - expected_allowed_diagnostic_snippets, - tuple(sorted(expected_allowed_diagnostic_snippets)), - ) - - self.assertTrue( - all( - isinstance(fixture_tuple, ast.Tuple) and len(fixture_tuple.elts) == 2 - for fixture_tuple in fixture_source.elts - ) - ) - fixture_literal_nodes = tuple( - fixture_literal - for fixture_tuple in fixture_source.elts - for fixture_literal in fixture_tuple.elts - ) - self.assertTrue( - all( - isinstance(fixture_literal, ast.Constant) - and isinstance(fixture_literal.value, str) - for fixture_literal in fixture_literal_nodes - ) - ) - self.assertTrue( - all( - isinstance(snippet_literal, ast.Constant) - and isinstance(snippet_literal.value, str) - for snippet_literal in snippet_source.elts - ) - ) - self.assertFalse( - any( - isinstance(node, disallowed_source_node_types) - for node in ( - *ast.walk(fixture_source), - *ast.walk(snippet_source), - ) - ) - ) - - def test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock" - ) - diagnostic_fixture_inventory_guard_cluster = ( - TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - def local_assignment_value(method_name: str, local_name: str) -> ast.AST: - methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == method_name - ) - self.assertEqual(len(methods), 1) - assignments = tuple( - node.value - for node in ast.walk(methods[0]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_value( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - expected_inventory = ast.literal_eval( - local_assignment_value( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual(len(diagnostic_fixture_inventory_guard_cluster), 5) - for method_name in diagnostic_fixture_inventory_guard_cluster: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - cluster_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - diagnostic_fixture_inventory_guard_cluster[0] - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - cluster_start : cluster_start - + len(diagnostic_fixture_inventory_guard_cluster) - ], - diagnostic_fixture_inventory_guard_cluster, - ) - self.assertEqual( - tuple( - method_name - for method_name in role_mapping.values() - if method_name in diagnostic_fixture_inventory_guard_cluster - ), - diagnostic_fixture_inventory_guard_cluster, - ) - self.assertEqual( - tuple( - method_name - for method_name in expected_inventory - if method_name in diagnostic_fixture_inventory_guard_cluster - ), - diagnostic_fixture_inventory_guard_cluster, - ) - - def test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock" - ) - diagnostic_fixture_inventory_guard_cluster = ( - TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER - ) - base_inventory = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY - cluster_start = base_inventory.index(diagnostic_fixture_inventory_guard_cluster[0]) - separator_method = base_inventory[0] - - def validate_diagnostic_fixture_inventory_guard_cluster( - inventory: tuple[str, ...], - ) -> None: - missing_methods = tuple( - method_name - for method_name in diagnostic_fixture_inventory_guard_cluster - if method_name not in inventory - ) - if missing_methods: - raise AssertionError( - "missing diagnostic fixture inventory guard cluster method " - f"{missing_methods[0]}" - ) - - duplicate_methods = tuple( - method_name - for method_name in diagnostic_fixture_inventory_guard_cluster - if inventory.count(method_name) != 1 - ) - if duplicate_methods: - raise AssertionError( - "duplicate diagnostic fixture inventory guard cluster method " - f"{duplicate_methods[0]}" - ) - - cluster_positions = tuple( - inventory.index(method_name) - for method_name in diagnostic_fixture_inventory_guard_cluster - ) - ordered_positions = tuple(sorted(cluster_positions)) - expected_positions = tuple( - range(ordered_positions[0], ordered_positions[0] + len(ordered_positions)) - ) - if ordered_positions != expected_positions: - raise AssertionError( - "contiguous diagnostic fixture inventory guard cluster positions " - f"{ordered_positions}" - ) - - ordered_cluster = inventory[ - ordered_positions[0] : ordered_positions[0] - + len(diagnostic_fixture_inventory_guard_cluster) - ] - if ordered_cluster != diagnostic_fixture_inventory_guard_cluster: - raise AssertionError( - "order diagnostic fixture inventory guard cluster mismatch " - f"{ordered_cluster}" - ) - - negative_inventories = { - "missing": ( - tuple( - method_name - for method_name in base_inventory - if method_name != diagnostic_fixture_inventory_guard_cluster[2] - ), - "missing", - ), - "duplicate": ( - (*base_inventory, diagnostic_fixture_inventory_guard_cluster[1]), - "duplicate", - ), - "contiguous": ( - ( - *base_inventory[: cluster_start + 2], - separator_method, - *base_inventory[cluster_start + 2 :], - ), - "contiguous", - ), - "order": ( - ( - *base_inventory[:cluster_start], - diagnostic_fixture_inventory_guard_cluster[1], - diagnostic_fixture_inventory_guard_cluster[0], - *diagnostic_fixture_inventory_guard_cluster[2:], - *base_inventory[ - cluster_start - + len(diagnostic_fixture_inventory_guard_cluster) : - ], - ), - "order", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - validate_diagnostic_fixture_inventory_guard_cluster(base_inventory) - for fixture_name, ( - synthetic_inventory, - diagnostic_snippet, - ) in negative_inventories.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex( - AssertionError, - re.escape(diagnostic_snippet), - ): - validate_diagnostic_fixture_inventory_guard_cluster( - synthetic_inventory - ) - - @staticmethod - def _release_index_diagnostic_fixture_inventory_guard_cluster_from_source( - module_source: str, - ) -> tuple[str, ...]: - constant_name = ( - "TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER" - ) - disallowed_source_node_types = ( - ast.Call, - ast.Dict, - ast.GeneratorExp, - ast.List, - ast.ListComp, - ast.Name, - ast.Set, - ast.Starred, - ) - module_tree = ast.parse(module_source) - - def assigns_constant(node: ast.AST) -> bool: - if isinstance(node, ast.Assign): - targets = node.targets - elif isinstance(node, (ast.AnnAssign, ast.AugAssign)): - targets = (node.target,) - else: - return False - return any( - isinstance(target, ast.Name) and target.id == constant_name - for target in targets - ) - - constant_assignments = tuple( - node for node in module_tree.body if assigns_constant(node) - ) - - if len(constant_assignments) != 1: - raise AssertionError( - "diagnostic fixture inventory guard cluster must have one assignment" - ) - - constant_assignment = constant_assignments[0] - if ( - not isinstance(constant_assignment, ast.Assign) - or len(constant_assignment.targets) != 1 - or not isinstance(constant_assignment.targets[0], ast.Name) - ): - raise AssertionError( - "diagnostic fixture inventory guard cluster must be a simple assignment" - ) - - constant_source = constant_assignment.value - dynamic_source_node = next( - ( - node - for node in ast.walk(constant_source) - if isinstance(node, disallowed_source_node_types) - ), - None, - ) - if dynamic_source_node is not None: - dynamic_node = type(dynamic_source_node).__name__ - raise AssertionError( - "diagnostic fixture inventory guard cluster uses dynamic source node " - f"{dynamic_node}" - ) - - if not isinstance(constant_source, ast.Tuple): - raise AssertionError( - "diagnostic fixture inventory guard cluster must be a literal tuple assignment" - ) - - if not constant_source.elts or not all( - isinstance(method_node, ast.Constant) - and isinstance(method_node.value, str) - for method_node in constant_source.elts - ): - raise AssertionError( - "diagnostic fixture inventory guard cluster must use literal method names" - ) - - try: - method_names = ast.literal_eval(constant_source) - except (SyntaxError, ValueError) as exc: - raise AssertionError( - "diagnostic fixture inventory guard cluster must use literal method names" - ) from exc - - if not all(isinstance(method_name, str) for method_name in method_names): - raise AssertionError( - "diagnostic fixture inventory guard cluster must use literal method names" - ) - - return method_names - - def test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock" - ) - expected_cluster = ( - "test_training_m74_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_self_lock", - "test_training_m75_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_self_lock", - "test_training_m76_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_rejects_dynamic_sources", - "test_training_m77_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_edge_diagnostics_are_locked", - "test_training_m78_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_source_shape_accepts_ordered_literal_sources", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - constant_cluster = ( - self._release_index_diagnostic_fixture_inventory_guard_cluster_from_source( - module_source - ) - ) - - self.assertEqual(constant_cluster, expected_cluster) - self.assertEqual(len(constant_cluster), 5) - self.assertEqual(len(constant_cluster), len(set(constant_cluster))) - for method_name in constant_cluster: - with self.subTest(method_name=method_name): - self.assertIn( - method_name, - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, - ) - - def test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources( - self, - ) -> None: - constant_name = ( - "TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER" - ) - guard_name = ( - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources" - ) - first_method = "literal_method_alpha" - second_method = "literal_method_beta" - invalid_sources = { - "list_literal": ( - f"{constant_name} = [{first_method!r}, {second_method!r}]", - "dynamic source node List", - ), - "name_reference": ( - f"fixture_guard_methods = ({first_method!r}, {second_method!r})\n" - f"{constant_name} = fixture_guard_methods", - "dynamic source node Name", - ), - "tuple_call": ( - f"{constant_name} = tuple(({first_method!r}, {second_method!r}))", - "dynamic source node Call", - ), - "sorted_call": ( - f"{constant_name} = sorted(({first_method!r}, {second_method!r}))", - "dynamic source node Call", - ), - "set_literal": ( - f"{constant_name} = {{{first_method!r}, {second_method!r}}}", - "dynamic source node Set", - ), - "dict_literal": ( - f"{constant_name} = {{{first_method!r}: {second_method!r}}}", - "dynamic source node Dict", - ), - "list_comprehension": ( - f"{constant_name} = [method for method in ({first_method!r}, {second_method!r})]", - "dynamic source node ListComp", - ), - "generator_expression": ( - f"{constant_name} = (method for method in ({first_method!r}, {second_method!r}))", - "dynamic source node GeneratorExp", - ), - "starred_tuple": ( - f"fixture_guard_methods = ({second_method!r},)\n" - f"{constant_name} = ({first_method!r}, *fixture_guard_methods)", - "dynamic source node Starred", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_diagnostic_fixture_inventory_guard_cluster_from_source( - source - ) - - def test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked( - self, - ) -> None: - constant_name = ( - "TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER" - ) - guard_name = ( - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked" - ) - first_method = "literal_method_alpha" - second_method = "literal_method_beta" - invalid_sources = { - "missing_target_assignment": ( - "UNRELATED_FIXTURE_GUARD_CLUSTER = ()", - "must have one assignment", - ), - "duplicate_top_level_assignments": ( - f"{constant_name} = ({first_method!r},)\n" - f"{constant_name} = ({second_method!r},)", - "must have one assignment", - ), - "annotated_assignment": ( - f"{constant_name}: tuple[str, ...] = ({first_method!r},)", - "must be a simple assignment", - ), - "augmented_assignment": ( - f"{constant_name} += ({first_method!r},)", - "must be a simple assignment", - ), - "empty_tuple_assignment": ( - f"{constant_name} = ()", - "must use literal method names", - ), - "integer_literal_method_name": ( - f"{constant_name} = ({first_method!r}, 123)", - "must use literal method names", - ), - "none_literal_method_name": ( - f"{constant_name} = ({first_method!r}, None)", - "must use literal method names", - ), - "scalar_string_assignment": ( - f"{constant_name} = {first_method!r}", - "must be a literal tuple assignment", - ), - } - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_diagnostic_fixture_inventory_guard_cluster_from_source( - source - ) - - def test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock" - ) - expected_governance_methods = ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", - ) - governance_methods = ( - TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER_GOVERNANCE_METHODS - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - def local_assignment_value(method_name: str, local_name: str) -> ast.AST: - methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == method_name - ) - self.assertEqual(len(methods), 1) - assignments = tuple( - node.value - for node in ast.walk(methods[0]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_value( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_value( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertIn(guard_name, role_methods) - self.assertIn(guard_name, expected_inventory) - self.assertEqual(governance_methods, expected_governance_methods) - self.assertEqual(len(governance_methods), len(set(governance_methods))) - for method_name in governance_methods: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - self.assertEqual(role_methods.count(method_name), 1) - self.assertEqual(expected_inventory.count(method_name), 1) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - governance_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - governance_methods[0] - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - governance_start : governance_start + len(governance_methods) - ], - governance_methods, - ) - self.assertEqual( - tuple( - method_name - for method_name in role_methods - if method_name in governance_methods - ), - governance_methods, - ) - self.assertEqual( - tuple( - method_name - for method_name in expected_inventory - if method_name in governance_methods - ), - governance_methods, - ) - - def test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock" - ) - recent_closure_methods = ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock", - ) - grouped_inventory = TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - inventory_by_category = dict(grouped_inventory) - flattened_required_snippets = tuple( - snippet - for _, snippets in grouped_inventory - for snippet in snippets - ) - text_by_doc = { - path: path.read_text(encoding="utf-8") - for path in TRAINING_M41_RELEASE_INDEX_DOCS - } - missing_by_doc = _release_index_missing_required_snippets_by_doc(text_by_doc) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - - def local_assignment_value(method_name: str, local_name: str) -> ast.AST: - methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == method_name - ) - self.assertEqual(len(methods), 1) - assignments = tuple( - node.value - for node in ast.walk(methods[0]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_value( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_value( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - - self.assertEqual(tuple(text_by_doc), TRAINING_M41_RELEASE_INDEX_DOCS) - self.assertEqual(tuple(missing_by_doc), TRAINING_M41_RELEASE_INDEX_DOCS) - for path in TRAINING_M41_RELEASE_INDEX_DOCS: - with self.subTest(path=path.name): - self.assertEqual(missing_by_doc[path], ()) - - self.assertEqual( - inventory_by_category["acceptance_offline_validation"], - TRAINING_ACCEPTANCE_VALIDATION_DOC_SNIPPETS, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS, - flattened_required_snippets, - ) - for category, snippets in grouped_inventory: - with self.subTest(category=category): - self.assertTrue(snippets) - self.assertEqual( - tuple(category for category, _ in grouped_inventory), - tuple(inventory_by_category), - ) - - recent_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - recent_closure_methods[0] - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - recent_start : recent_start + len(recent_closure_methods) - ], - recent_closure_methods, - ) - for method_name in recent_closure_methods: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertIn(guard_name, role_methods) - self.assertIn(guard_name, expected_inventory) - - def test_training_m86_release_index_final_closure_delivery_index_self_lock( - self, - ) -> None: - guard_name = "test_training_m86_release_index_final_closure_delivery_index_self_lock" - closure_methods = ( - "test_training_m79_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_self_lock", - "test_training_m80_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_negative_fixture_self_lock", - "test_training_m81_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_self_lock", - "test_training_m82_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_rejects_dynamic_sources", - "test_training_m83_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_constant_source_shape_edge_diagnostics_are_locked", - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock", - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock", - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - - def local_assignment_value(method_name: str, local_name: str) -> ast.AST: - methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == method_name - ) - self.assertEqual(len(methods), 1) - assignments = tuple( - node.value - for node in ast.walk(methods[0]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_value( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_value( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - m84_governance_methods = ast.literal_eval( - local_assignment_value( - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock", - "expected_governance_methods", - ) - ) - delivery_methods = (*closure_methods, guard_name) - closure_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - closure_methods[0] - ) - m79_m83_methods = closure_methods[:5] - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - closure_start : closure_start + len(closure_methods) - ], - closure_methods, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - closure_start : closure_start + len(delivery_methods) - ], - delivery_methods, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_DIAGNOSTIC_FIXTURE_INVENTORY_GUARD_CLUSTER_GOVERNANCE_METHODS, - m79_m83_methods, - ) - self.assertEqual(m84_governance_methods, m79_m83_methods) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - "test_training_m85_release_index_public_docs_acceptance_inventory_alignment_self_lock" - ), - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - "test_training_m84_release_index_source_shape_fixture_guard_methods_constant_exactness_bridge_triplet_diagnostic_fixture_inventory_guard_cluster_inventory_self_lock" - ) - + 1, - ) - - for method_name in closure_methods: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - self.assertEqual(role_methods.count(method_name), 1) - self.assertEqual(expected_inventory.count(method_name), 1) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count(guard_name), - 1, - ) - self.assertEqual(role_methods.count(guard_name), 1) - self.assertEqual(expected_inventory.count(guard_name), 1) - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_MARKER, "M1-M40") - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_SPAN, "through M40") - self.assertEqual( - CURRENT_TRAINING_DOC_MATURITY_PHRASE, - "current product maturity through M40", - ) - for path in TRAINING_M41_RELEASE_INDEX_DOCS: + def test_readme_and_runbook_use_current_training_entry_and_not_old_doc(self) -> None: + for path in (README, RUNBOOK): text = path.read_text(encoding="utf-8") with self.subTest(path=path.name): - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_MARKER, text) - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_SPAN, text) - - def test_training_m87_release_index_final_closure_delivery_index_guard_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock" - ) - final_closure_guard_name = ( - "test_training_m86_release_index_final_closure_delivery_index_self_lock" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - functions_by_name = { - node.name: ast.get_source_segment(module_source, node) or "" - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_value(method_name: str, local_name: str) -> ast.AST: - methods = tuple( - node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) and node.name == method_name - ) - self.assertEqual(len(methods), 1) - assignments = tuple( - node.value - for node in ast.walk(methods[0]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_value( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_value( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - closure_methods = ast.literal_eval( - local_assignment_value(final_closure_guard_name, "closure_methods") - ) - delivery_methods = (*closure_methods, final_closure_guard_name) - delivery_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - delivery_methods[0] - ) - - self.assertIn(final_closure_guard_name, functions_by_name) - for source_reference in ( - "delivery_methods = (*closure_methods, guard_name)", - "closure_start : closure_start + len(delivery_methods)", - "CURRENT_TRAINING_DOC_MATURITY_MARKER, \"M1-M40\"", - "CURRENT_TRAINING_DOC_MATURITY_SPAN, \"through M40\"", - ): - with self.subTest(source_reference=source_reference): - self.assertIn( - source_reference, - functions_by_name[final_closure_guard_name], - ) - - self.assertEqual(delivery_methods[-1], final_closure_guard_name) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - delivery_start : delivery_start + len(delivery_methods) - ], - delivery_methods, - ) - for method_name in delivery_methods: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - self.assertEqual(role_methods.count(method_name), 1) - self.assertEqual(expected_inventory.count(method_name), 1) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count(guard_name), - 1, - ) - self.assertEqual(role_methods.count(guard_name), 1) - self.assertEqual(expected_inventory.count(guard_name), 1) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index(guard_name), - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - final_closure_guard_name - ) - + 1, - ) - self.assertEqual( - role_mapping["release_index_final_closure_delivery_guard_self_lock"], - guard_name, - ) - - def test_training_m88_release_index_final_closure_delivery_guard_source_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock" - ) - guarded_method_name = ( - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock" - ) - final_closure_guard_name = ( - "test_training_m86_release_index_final_closure_delivery_index_self_lock" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - functions_by_name = { - node.name: node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_node(method_name: str, local_name: str) -> ast.AST: - self.assertIn(method_name, functions_by_name) - assignments = tuple( - node.value - for node in ast.walk(functions_by_name[method_name]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_node( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_node( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - closure_methods = ast.literal_eval( - local_assignment_node(final_closure_guard_name, "closure_methods") - ) - delivery_methods = ( - *closure_methods, - final_closure_guard_name, - guarded_method_name, - guard_name, - ) - delivery_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - delivery_methods[0] - ) - guarded_delivery_node = local_assignment_node( - guarded_method_name, - "delivery_methods", - ) - - self.assertEqual( - ast.literal_eval(local_assignment_node(guarded_method_name, "guard_name")), - guarded_method_name, - ) - self.assertEqual( - ast.literal_eval( - local_assignment_node(guarded_method_name, "final_closure_guard_name") - ), - final_closure_guard_name, - ) - self.assertIsInstance(guarded_delivery_node, ast.Tuple) - self.assertEqual(len(guarded_delivery_node.elts), 2) - self.assertIsInstance(guarded_delivery_node.elts[0], ast.Starred) - self.assertIsInstance(guarded_delivery_node.elts[0].value, ast.Name) - self.assertEqual(guarded_delivery_node.elts[0].value.id, "closure_methods") - self.assertIsInstance(guarded_delivery_node.elts[1], ast.Name) - self.assertEqual( - guarded_delivery_node.elts[1].id, - "final_closure_guard_name", - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - delivery_start : delivery_start + len(delivery_methods) - ], - delivery_methods, - ) - for method_name in delivery_methods: - with self.subTest(method_name=method_name): - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - self.assertEqual(role_methods.count(method_name), 1) - self.assertEqual(expected_inventory.count(method_name), 1) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - - self.assertEqual( - role_mapping.get("release_index_final_closure_delivery_guard_source_self_lock"), - guard_name, - ) - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_MARKER, "M1-M40") - self.assertEqual(CURRENT_TRAINING_DOC_MATURITY_SPAN, "through M40") - - def test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock" - ) - final_closure_guard_name = ( - "test_training_m86_release_index_final_closure_delivery_index_self_lock" - ) - delivery_guard_name = ( - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock" - ) - source_guard_name = ( - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - functions_by_name = { - node.name: node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_node(method_name: str, local_name: str) -> ast.AST: - self.assertIn(method_name, functions_by_name) - assignments = tuple( - node.value - for node in ast.walk(functions_by_name[method_name]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_node( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_node( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - closure_methods = ast.literal_eval( - local_assignment_node(final_closure_guard_name, "closure_methods") - ) - source_delivery_node = local_assignment_node( - source_guard_name, - "delivery_methods", - ) - delivery_chain = ( - *closure_methods, - final_closure_guard_name, - delivery_guard_name, - source_guard_name, - guard_name, - ) - chain_methods = delivery_chain[-4:] - chain_start = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - delivery_chain[0] - ) - - self.assertEqual( - ast.literal_eval( - local_assignment_node(source_guard_name, "final_closure_guard_name") - ), - final_closure_guard_name, - ) - self.assertEqual( - ast.literal_eval( - local_assignment_node(source_guard_name, "guarded_method_name") - ), - delivery_guard_name, - ) - self.assertIsInstance(source_delivery_node, ast.Tuple) - self.assertEqual(len(source_delivery_node.elts), 4) - self.assertIsInstance(source_delivery_node.elts[0], ast.Starred) - self.assertIsInstance(source_delivery_node.elts[0].value, ast.Name) - self.assertEqual(source_delivery_node.elts[0].value.id, "closure_methods") - self.assertEqual( - tuple( - node.id - for node in source_delivery_node.elts[1:] - if isinstance(node, ast.Name) - ), - ("final_closure_guard_name", "guarded_method_name", "guard_name"), - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - chain_start : chain_start + len(delivery_chain) - ], - delivery_chain, - ) - self.assertEqual( - tuple( - method_name - for method_name in role_methods - if method_name in chain_methods - ), - chain_methods, - ) - self.assertEqual( - tuple( - method_name - for method_name in expected_inventory - if method_name in chain_methods - ), - chain_methods, - ) - self.assertEqual( - role_mapping.get( - "release_index_final_closure_delivery_guard_source_chain_self_lock" - ), - guard_name, - ) - for method_name in chain_methods: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - - def test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock" - ) - guarded_method_name = ( - "test_training_m89_release_index_final_closure_delivery_guard_source_chain_self_lock" - ) - expected_guard_chain_names = ( - "test_training_m86_release_index_final_closure_delivery_index_self_lock", - "test_training_m87_release_index_final_closure_delivery_index_guard_self_lock", - "test_training_m88_release_index_final_closure_delivery_guard_source_self_lock", - guarded_method_name, - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - functions_by_name = { - node.name: node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_node(method_name: str, local_name: str) -> ast.AST: - self.assertIn(method_name, functions_by_name) - assignments = tuple( - node.value - for node in ast.walk(functions_by_name[method_name]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_node( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_node( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - guarded_chain_names = tuple( - ast.literal_eval(local_assignment_node(guarded_method_name, local_name)) - for local_name in ( - "final_closure_guard_name", - "delivery_guard_name", - "source_guard_name", - "guard_name", - ) - ) - delivery_chain_node = local_assignment_node( - guarded_method_name, - "delivery_chain", - ) - chain_methods_node = local_assignment_node( - guarded_method_name, - "chain_methods", - ) - - self.assertEqual(guarded_chain_names, expected_guard_chain_names) - self.assertIsInstance(delivery_chain_node, ast.Tuple) - self.assertEqual(len(delivery_chain_node.elts), 5) - self.assertIsInstance(delivery_chain_node.elts[0], ast.Starred) - self.assertIsInstance(delivery_chain_node.elts[0].value, ast.Name) - self.assertEqual(delivery_chain_node.elts[0].value.id, "closure_methods") - self.assertEqual( - tuple( - node.id - for node in delivery_chain_node.elts[1:] - if isinstance(node, ast.Name) - ), - ( - "final_closure_guard_name", - "delivery_guard_name", - "source_guard_name", - "guard_name", - ), - ) - self.assertIsInstance(chain_methods_node, ast.Subscript) - self.assertIsInstance(chain_methods_node.value, ast.Name) - self.assertEqual(chain_methods_node.value.id, "delivery_chain") - self.assertIsInstance(chain_methods_node.slice, ast.Slice) - self.assertIsInstance(chain_methods_node.slice.lower, ast.UnaryOp) - self.assertIsInstance(chain_methods_node.slice.lower.op, ast.USub) - self.assertIsInstance(chain_methods_node.slice.lower.operand, ast.Constant) - self.assertEqual(chain_methods_node.slice.lower.operand.value, 4) - self.assertIsNone(chain_methods_node.slice.upper) - self.assertIsNone(chain_methods_node.slice.step) - - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index(guard_name), - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - guarded_method_name - ) - + 1, - ) - self.assertEqual( - role_mapping.get( - "release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock" - ), - guard_name, - ) - self.assertEqual(role_methods.count(guard_name), 1) - self.assertEqual(expected_inventory.count(guard_name), 1) - - def test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock" - ) - source_shape_guard_name = ( - "test_training_m90_release_index_final_closure_delivery_guard_source_chain_source_shape_self_lock" - ) - expected_local_names = ( - "final_closure_guard_name", - "delivery_guard_name", - "source_guard_name", - "guard_name", - ) - expected_lookup_source = ( - "ast.literal_eval(local_assignment_node(guarded_method_name, local_name))" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - functions_by_name = { - node.name: node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_node(method_name: str, local_name: str) -> ast.AST: - self.assertIn(method_name, functions_by_name) - assignments = tuple( - node.value - for node in ast.walk(functions_by_name[method_name]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - guarded_chain_names_node = local_assignment_node( - source_shape_guard_name, - "guarded_chain_names", - ) - - self.assertIsInstance(guarded_chain_names_node, ast.Call) - self.assertIsInstance(guarded_chain_names_node.func, ast.Name) - self.assertEqual(guarded_chain_names_node.func.id, "tuple") - self.assertEqual(len(guarded_chain_names_node.args), 1) - guarded_chain_generator = guarded_chain_names_node.args[0] - self.assertIsInstance(guarded_chain_generator, ast.GeneratorExp) - self.assertEqual(len(guarded_chain_generator.generators), 1) - generator_clause = guarded_chain_generator.generators[0] - self.assertIsInstance(generator_clause.target, ast.Name) - self.assertEqual(generator_clause.target.id, "local_name") - self.assertIsInstance(generator_clause.iter, ast.Tuple) - self.assertEqual( - ast.literal_eval(generator_clause.iter), - expected_local_names, - ) - for element in generator_clause.iter.elts: - with self.subTest(local_name_node=ast.dump(element)): - self.assertIsInstance(element, ast.Constant) - self.assertIsInstance(element.value, str) - self.assertEqual( - ast.get_source_segment(module_source, guarded_chain_generator.elt), - expected_lookup_source, - ) - - self.assertIn(guard_name, TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count(guard_name), - 1, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - source_shape_guard_name - ), - 1, - ) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index(guard_name), - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - source_shape_guard_name - ) - + 1, - ) - - def test_training_m92_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock( - self, - ) -> None: - guard_name = ( - "test_training_m92_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock" - ) - local_names_guard_name = ( - "test_training_m91_release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_self_lock" - ) - role_key = ( - "release_index_final_closure_delivery_guard_source_chain_source_shape_local_names_registration_self_lock" - ) - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - test_case_name = f"{type(self).__module__}.{type(self).__qualname__}" - functions_by_name = { - node.name: node - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - def local_assignment_node(method_name: str, local_name: str) -> ast.AST: - self.assertIn(method_name, functions_by_name) - assignments = tuple( - node.value - for node in ast.walk(functions_by_name[method_name]) - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == local_name - ) - ) - - self.assertEqual(len(assignments), 1) - return assignments[0] - - role_mapping = ast.literal_eval( - local_assignment_node( - "test_training_m41_release_index_guard_inventory_is_current", - "required_guard_tests_by_role", - ) - ) - role_methods = tuple(role_mapping.values()) - expected_inventory = ast.literal_eval( - local_assignment_node( - "test_training_m52_release_index_guard_inventory_order_and_uniqueness_self_lock", - "expected_inventory", - ) - ) - registration_pair = (local_names_guard_name, guard_name) - local_names_guard_index = TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.index( - local_names_guard_name - ) - - self.assertEqual(role_mapping.get(role_key), guard_name) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY[ - local_names_guard_index : local_names_guard_index - + len(registration_pair) - ], - registration_pair, - ) - self.assertEqual( - tuple(method_name for method_name in role_methods if method_name in registration_pair), - registration_pair, - ) - self.assertEqual( - tuple( - method_name - for method_name in expected_inventory - if method_name in registration_pair - ), - registration_pair, - ) - for method_name in registration_pair: - with self.subTest(method_name=method_name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"{test_case_name}.{method_name}") - - self.assertEqual(loader.errors, []) - self.assertEqual(suite.countTestCases(), 1) - self.assertEqual( - TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY.count( - method_name - ), - 1, - ) - - def test_training_m47_release_index_per_doc_helper_inventory_self_lock_is_current(self) -> None: - helper_name = "_release_index_missing_required_snippets_by_doc" - docs_guard_name = "test_training_readmes_expose_m1_m40_release_index_without_raw_test_modules" - inventory_guard_name = "test_training_m41_release_index_guard_inventory_is_current" - module_source = Path(__file__).read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - functions_by_name = { - node.name: ast.get_source_segment(module_source, node) or "" - for node in ast.walk(module_tree) - if isinstance(node, ast.FunctionDef) - } - - self.assertEqual( - [ - node.name - for node in module_tree.body - if isinstance(node, ast.FunctionDef) and node.name == helper_name - ], - [helper_name], - ) - for guard_name in (docs_guard_name, inventory_guard_name): - with self.subTest(guard_name=guard_name): - self.assertIn(guard_name, functions_by_name) - self.assertIn(helper_name, functions_by_name[guard_name]) - - inventory_guard_source = functions_by_name[inventory_guard_name] - for fixture_reference in ( - "PROJECT_README: missing_release_doc", - "TRAINING_PRODUCT_DOC: complete_release_doc", - "negative_fixture_missing_by_doc[TRAINING_PRODUCT_DOC], ()", - "negative_fixture_missing_by_doc[PROJECT_README]", - ): - with self.subTest(fixture_reference=fixture_reference): - self.assertIn(fixture_reference, inventory_guard_source) - - def test_training_m48_release_index_per_doc_helper_keeps_doc_tuple_shape(self) -> None: - complete_release_doc = "\n".join(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS) - all_required_snippets = tuple( - (surface, snippet) - for surface, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - for snippet in snippets - ) - expected_missing = tuple( - required_snippet - for required_snippet in all_required_snippets - if required_snippet[1] - in (CURRENT_TRAINING_DOC_MATURITY_MARKER, "API contract coverage") - ) - missing_release_doc = complete_release_doc - for _, snippet in expected_missing: - missing_release_doc = missing_release_doc.replace(snippet, "", 1) - docs_by_name = { - "missing-release-index.md": missing_release_doc, - "complete-release-index.md": complete_release_doc, - } - original_docs_by_name = dict(docs_by_name) - - missing_by_doc = _release_index_missing_required_snippets_by_doc(docs_by_name) - - self.assertEqual(len(expected_missing), 2) - self.assertEqual(tuple(missing_by_doc), tuple(docs_by_name)) - self.assertEqual(missing_by_doc["missing-release-index.md"], expected_missing) - self.assertIsInstance(missing_by_doc["missing-release-index.md"], tuple) - self.assertEqual(missing_by_doc["complete-release-index.md"], ()) - self.assertIsInstance(missing_by_doc["complete-release-index.md"], tuple) - self.assertEqual(docs_by_name, original_docs_by_name) - - def test_training_m49_release_index_per_doc_helper_handles_empty_docs_without_mutation(self) -> None: - complete_release_doc = "\n".join(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS) - docs_by_name = {"complete-release-index.md": complete_release_doc} - original_docs_by_name = dict(docs_by_name) - - empty_missing_by_doc = _release_index_missing_required_snippets_by_doc({}) - complete_missing_by_doc = _release_index_missing_required_snippets_by_doc(docs_by_name) - - self.assertEqual(empty_missing_by_doc, {}) - self.assertEqual(docs_by_name, original_docs_by_name) - self.assertEqual(complete_missing_by_doc["complete-release-index.md"], ()) - - def test_training_m50_release_index_helper_accepts_flattened_inventory_complete_doc(self) -> None: - flattened_required_snippets = tuple( - snippet - for _, snippets in TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - for snippet in snippets - ) - complete_release_doc = "\n".join(flattened_required_snippets) - docs_by_name = {"synthetic-complete-release.md": complete_release_doc} - - missing_by_doc = _release_index_missing_required_snippets_by_doc(docs_by_name) - - self.assertTrue(flattened_required_snippets) - self.assertEqual(len(flattened_required_snippets), len(set(flattened_required_snippets))) - self.assertEqual(missing_by_doc, {"synthetic-complete-release.md": ()}) - - def test_training_m54_release_index_required_snippet_inventory_category_shape_is_locked(self) -> None: - inventory = TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - inventory_by_category = dict(inventory) - expected_categories = ( - "current_m1_m40_marker", - "acceptance_offline_validation", - "public_api_privacy_contracts", - "package_smoke_offline_registry", - "dashboard_public_docs_privacy", - "single_set_docker_deployment", - "docker_smoke_guard_coverage", - ) - flattened_snippets = tuple( - snippet - for category in expected_categories - for snippet in inventory_by_category[category] - ) - - self.assertEqual(tuple(category for category, _ in inventory), expected_categories) - self.assertEqual(len(expected_categories), len(set(expected_categories))) - for category in expected_categories: - with self.subTest(category=category): - snippets = inventory_by_category[category] - self.assertTrue(snippets) - for snippet in snippets: - self.assertIsInstance(snippet, str) - self.assertNotEqual(snippet, "") - - self.assertEqual(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS, flattened_snippets) - self.assertEqual(len(flattened_snippets), len(set(flattened_snippets))) - - @staticmethod - def _release_index_required_snippet_inventory_source_categories( - module_source: str, - ) -> tuple[str, ...]: - inventory_name = "TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY" - - def assignment_targets(node: ast.AST): - if isinstance(node, ast.Assign): - return tuple(node.targets) - if isinstance(node, (ast.AnnAssign, ast.AugAssign)): - return (node.target,) - return () - - module_tree = ast.parse(module_source) - inventory_assignments = tuple( - node - for node in module_tree.body - if any( - isinstance(target, ast.Name) and target.id == inventory_name - for target in assignment_targets(node) - ) - ) - - if len(inventory_assignments) != 1: - raise AssertionError( - "release index required snippet inventory must have one assignment" - ) - - inventory_source = getattr(inventory_assignments[0], "value", None) - if not isinstance(inventory_source, ast.Tuple): - raise AssertionError( - "release index required snippet inventory must be a literal tuple assignment" - ) - - disallowed_source_nodes = ( - ast.Call, - ast.Dict, - ast.DictComp, - ast.GeneratorExp, - ast.ListComp, - ast.SetComp, - ast.Starred, - ) - dynamic_source_node = next( - ( - node - for node in ast.walk(inventory_source) - if isinstance(node, disallowed_source_nodes) - ), - None, - ) - if dynamic_source_node is not None: - dynamic_node = type(dynamic_source_node).__name__ - raise AssertionError( - "release index required snippet inventory uses dynamic source node " - f"{dynamic_node}" - ) - - try: - inventory = ast.literal_eval(inventory_source) - except (SyntaxError, ValueError) as exc: - raise AssertionError( - "release index required snippet inventory must use literal snippets" - ) from exc - - if not all( - isinstance(group, tuple) - and len(group) == 2 - and isinstance(group[0], str) - and isinstance(group[1], (tuple, list)) - and group[1] - and all(isinstance(snippet, str) and snippet for snippet in group[1]) - for group in inventory - ): - raise AssertionError( - "release index required snippet inventory groups must be " - "literal category and snippet pairs" - ) - - return tuple(category for category, _ in inventory) - - def test_training_m55_release_index_required_snippet_inventory_source_shape_is_locked(self) -> None: - module_source = Path(__file__).read_text(encoding="utf-8") - expected_categories = ( - "current_m1_m40_marker", - "acceptance_offline_validation", - "public_api_privacy_contracts", - "package_smoke_offline_registry", - "dashboard_public_docs_privacy", - "single_set_docker_deployment", - "docker_smoke_guard_coverage", - ) - source_group_order = ( - self._release_index_required_snippet_inventory_source_categories( - module_source - ) - ) - - self.assertEqual(source_group_order, expected_categories) - - def test_training_m56_release_index_required_snippet_inventory_source_shape_rejects_dynamic_fixtures( - self, - ) -> None: - inventory_name = "TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY" - invalid_sources = { - "dict_assignment": ( - f"{inventory_name} = {{'literal_category': ('literal snippet',)}}", - "literal tuple assignment", - ), - "call_snippets": ( - f"{inventory_name} = (('literal_category', make_snippets()),)", - "dynamic source node Call", - ), - "name_snippets": ( - f"{inventory_name} = (('literal_category', snippets),)", - "must use literal snippets", - ), - "starred_snippets": ( - f"{inventory_name} = (('literal_category', (*('literal snippet',),)),)", - "dynamic source node Starred", - ), - "comprehension_snippets": ( - f"{inventory_name} = " - "(('literal_category', [item for item in ('literal snippet',)]),)", - "dynamic source node ListComp", - ), - } - - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_required_snippet_inventory_source_categories( - source - ) - - def test_training_m57_release_index_required_snippet_inventory_source_shape_diagnostics_are_locked( - self, - ) -> None: - inventory_name = "TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY" - group_shape_error = "literal category and snippet pairs" - invalid_sources = { - "zero_inventory_assignment": ( - "UNRELATED_RELEASE_INDEX_INVENTORY = ()", - "must have one assignment", - ), - "duplicate_inventory_assignment": ( - f"{inventory_name} = (('literal_category', ('literal snippet',)),)\n" - f"{inventory_name} = (('second_literal_category', ('second literal snippet',)),)", - "must have one assignment", - ), - "malformed_group_tuple": ( - f"{inventory_name} = (('literal_category', ('literal snippet',), 'extra'),)", - group_shape_error, - ), - "empty_snippet_group": ( - f"{inventory_name} = (('literal_category', ()),)", - group_shape_error, - ), - "non_string_category": ( - f"{inventory_name} = ((7, ('literal snippet',)),)", - group_shape_error, - ), - "non_string_snippet": ( - f"{inventory_name} = (('literal_category', (7,)),)", - group_shape_error, - ), - "empty_snippet": ( - f"{inventory_name} = (('literal_category', ('',)),)", - group_shape_error, - ), - } - - for fixture_name, (source, error_snippet) in invalid_sources.items(): - with self.subTest(fixture_name=fixture_name): - with self.assertRaisesRegex(AssertionError, re.escape(error_snippet)): - self._release_index_required_snippet_inventory_source_categories( - source - ) - - def test_training_m58_release_index_required_snippet_inventory_source_shape_accepts_literal_fixtures( - self, - ) -> None: - source = """ -TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY = ( - ( - "gamma_literal_category", - ( - "gamma release note", - "gamma release detail", - ), - ), - ( - "alpha_literal_category", - [ - "alpha release note", - "alpha release detail", - ], - ), - ( - "beta_literal_category", - ( - "beta release note", - ), - ), -) -""" - expected_categories = ( - "gamma_literal_category", - "alpha_literal_category", - "beta_literal_category", - ) - - source_group_order = ( - self._release_index_required_snippet_inventory_source_categories(source) - ) - - self.assertEqual(source_group_order, expected_categories) - self.assertNotEqual(source_group_order, tuple(sorted(source_group_order))) - - def test_training_m59_release_index_required_snippet_inventory_source_shape_preserves_literal_order( - self, - ) -> None: - source = """ -TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY = ( - ( - "literal_order_third_category", - ( - "third literal order signal", - ), - ), - ( - "literal_order_first_category", - ( - "first literal order signal", - ), - ), - ( - "literal_order_second_category", - ( - "second literal order signal", - ), - ), -) -""" - expected_categories = ( - "literal_order_third_category", - "literal_order_first_category", - "literal_order_second_category", - ) - - source_group_order = ( - self._release_index_required_snippet_inventory_source_categories(source) - ) - - self.assertNotEqual(expected_categories, tuple(sorted(expected_categories))) - self.assertEqual(source_group_order, expected_categories) - - def test_training_m53_release_index_doc_category_coverage_is_exact(self) -> None: - inventory = TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - inventory_by_category = dict(inventory) - expected_categories = tuple(category for category, _ in inventory) - expected_complete_results = { - category: (inventory_by_category[category], ()) - for category in expected_categories - } - - def category_results(text: str): - missing_pairs = _release_index_missing_required_snippets_by_doc( - {"release-index-doc.md": text} - )["release-index-doc.md"] - missing_by_category = { - category: tuple( - snippet - for missing_category, snippet in missing_pairs - if missing_category == category - ) - for category in expected_categories - } - return { - category: ( - tuple( - snippet - for snippet in inventory_by_category[category] - if snippet not in missing_by_category[category] - ), - missing_by_category[category], - ) - for category in expected_categories - } - - for path in TRAINING_M41_RELEASE_INDEX_DOCS: - with self.subTest(path=path.name): - self.assertEqual( - category_results(path.read_text(encoding="utf-8")), - expected_complete_results, - ) - - synthetic_missing_category = expected_categories[-1] - synthetic_missing_doc = "\n".join( - snippet - for category in expected_categories - if category != synthetic_missing_category - for snippet in inventory_by_category[category] - ) - expected_missing_results = dict(expected_complete_results) - expected_missing_results[synthetic_missing_category] = ( - (), - inventory_by_category[synthetic_missing_category], - ) - - self.assertEqual( - category_results(synthetic_missing_doc), - expected_missing_results, - ) - - def test_training_m41_release_index_inventory_shape_is_stable(self) -> None: - module_tree = ast.parse(Path(__file__).read_text(encoding="utf-8")) - inventory_assignments = [ - node - for node in module_tree.body - if isinstance(node, ast.Assign) - for target in node.targets - if ( - isinstance(target, ast.Name) - and target.id == "TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY" - ) - ] - expected_group_order = ( - "current_m1_m40_marker", - "acceptance_offline_validation", - "public_api_privacy_contracts", - "package_smoke_offline_registry", - "dashboard_public_docs_privacy", - "single_set_docker_deployment", - "docker_smoke_guard_coverage", - ) - grouped_inventory = TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY - grouped_snippets = tuple( - snippet - for _, snippets in grouped_inventory - for snippet in snippets - ) - active_release_inventory = ( - *TRAINING_M41_RELEASE_INDEX_GUARD_TEST_INVENTORY, - *(category for category, _ in grouped_inventory), - *grouped_snippets, - CURRENT_TRAINING_DOC_MATURITY_MARKER, - CURRENT_TRAINING_DOC_MATURITY_SPAN, - ) - - self.assertEqual(len(inventory_assignments), 1) - inventory_source = inventory_assignments[0].value - self.assertIsInstance(inventory_source, ast.Tuple) - source_group_order = tuple( - ast.literal_eval(group_node.elts[0]) - for group_node in inventory_source.elts - if isinstance(group_node, ast.Tuple) and len(group_node.elts) == 2 - ) - - self.assertEqual(source_group_order, expected_group_order) - self.assertEqual(len(source_group_order), len(set(source_group_order))) - self.assertEqual(tuple(category for category, _ in grouped_inventory), expected_group_order) - for group_name, snippets in grouped_inventory: - with self.subTest(group_name=group_name): - self.assertTrue(snippets) - - self.assertEqual(TRAINING_M41_RELEASE_INDEX_DOC_SNIPPETS, grouped_snippets) - self.assertEqual(len(grouped_snippets), len(set(grouped_snippets))) - self.assertIn("M1-M40", active_release_inventory) - self.assertIn("through M40", active_release_inventory) - for stale_value in ( - *TRAINING_M41_STALE_RELEASE_INVENTORY_NAMES, - "M1-M39", - "through M39", - ): - with self.subTest(stale_value=stale_value): - self.assertNotIn(stale_value, active_release_inventory) - - def test_local_docker_deployment_documentation_guard_inventory_is_current(self) -> None: - module_path = Path(__file__) - module_source = module_path.read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - class_nodes = [ - node - for node in module_tree.body - if isinstance(node, ast.ClassDef) and node.name == "DocumentationConsistencyTests" - ] - top_level_names = { - target.id - for node in module_tree.body - if isinstance(node, ast.Assign) - for target in node.targets - if isinstance(target, ast.Name) - } - required_guard_tests = { - "test_readmes_describe_single_set_local_docker_deployment_policy", - "test_readmes_lock_single_set_docker_smoke_command_shape", - } - inventory_names = tuple(LOCAL_DOCKER_DOCUMENTATION_GUARD_TEST_INVENTORY) - - self.assertEqual(len(class_nodes), 1) - self.assertEqual(len(inventory_names), len(set(inventory_names))) - self.assertLessEqual(required_guard_tests, set(inventory_names)) - - test_names = { - item.name - for item in class_nodes[0].body - if isinstance(item, ast.FunctionDef) and item.name.startswith("test_") - } - for name in inventory_names: - with self.subTest(name=name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName( - f"{type(self).__module__}.DocumentationConsistencyTests.{name}" - ) - - self.assertIn(name, test_names) - self.assertEqual(loader.errors, []) - self.assertGreater(suite.countTestCases(), 0) - - for constant_name in LOCAL_DOCKER_DOCUMENTATION_GUARD_SOURCE_CONSTANTS: - with self.subTest(constant_name=constant_name): - self.assertIn(constant_name, top_level_names) - self.assertIn(f"{constant_name} =", module_source) - - def test_trading_delivery_documentation_guard_inventory_is_current(self) -> None: - module_path = Path(__file__) - module_source = module_path.read_text(encoding="utf-8") - module_tree = ast.parse(module_source) - class_nodes = [ - node - for node in module_tree.body - if isinstance(node, ast.ClassDef) and node.name == "DocumentationConsistencyTests" - ] - top_level_names = { - target.id - for node in module_tree.body - if isinstance(node, ast.Assign) - for target in node.targets - if isinstance(target, ast.Name) - } - required_guard_tests = { - "test_docs_describe_trading_delivery_checklist", - "test_docs_lock_trading_delivery_command_shape", - } - inventory_names = tuple(TRADING_DELIVERY_DOCUMENTATION_GUARD_TEST_INVENTORY) - - self.assertEqual(len(class_nodes), 1) - self.assertEqual(len(inventory_names), len(set(inventory_names))) - self.assertLessEqual(required_guard_tests, set(inventory_names)) - - test_names = { - item.name - for item in class_nodes[0].body - if isinstance(item, ast.FunctionDef) and item.name.startswith("test_") - } - for name in inventory_names: - with self.subTest(name=name): - loader = unittest.TestLoader() - suite = loader.loadTestsFromName( - f"{type(self).__module__}.DocumentationConsistencyTests.{name}" - ) - - self.assertIn(name, test_names) - self.assertEqual(loader.errors, []) - self.assertGreater(suite.countTestCases(), 0) - - for constant_name in TRADING_DELIVERY_DOCUMENTATION_GUARD_SOURCE_CONSTANTS: - with self.subTest(constant_name=constant_name): - self.assertIn(constant_name, top_level_names) - self.assertIn(f"{constant_name} =", module_source) - - def test_training_case_acceptance_manifest_modules_are_discoverable(self) -> None: - manifest_modules = tuple(TRAINING_CASE_ACCEPTANCE_TEST_MANIFEST) - missing_modules = [ - module - for module in TRAINING_CASE_REQUIRED_ACCEPTANCE_TEST_MODULES - if module not in manifest_modules - ] - - self.assertEqual(missing_modules, []) - self.assertEqual(len(manifest_modules), len(set(manifest_modules))) - - for module in manifest_modules: - with self.subTest(module=module): - path = ROOT / "tests" / "unit" / f"{module}.py" - loader = unittest.TestLoader() - suite = loader.loadTestsFromName(f"tests.unit.{module}") - - self.assertTrue(path.is_file(), path) - self.assertEqual(loader.errors, []) - self.assertGreater(suite.countTestCases(), 0) - - def test_training_readmes_name_dashboard_privacy_and_offline_registry_coverage(self) -> None: - expected_doc_snippets = ( - "dashboard and public docs privacy smoke coverage", - "dashboard_js_syntax", - "training_case_package", - ) - for path in (PROJECT_README, TRAINING_PRODUCT_DOC): - text = path.read_text(encoding="utf-8") - with self.subTest(path=path.name): - for snippet in expected_doc_snippets: - self.assertIn(snippet, text) - - registry_text = OFFLINE_CHECKS_SCRIPT.read_text(encoding="utf-8") - for check_name in ("dashboard_js_syntax", "training_case_package"): - with self.subTest(check_name=check_name): - self.assertEqual(registry_text.count(f'"{check_name}"'), 1) - - def test_training_readmes_describe_acceptance_manifest_offline_validation_posture(self) -> None: - for path in (PROJECT_README, TRAINING_PRODUCT_DOC): - text = path.read_text(encoding="utf-8") - with self.subTest(path=path.name): - for snippet in TRAINING_ACCEPTANCE_VALIDATION_DOC_SNIPPETS: - self.assertIn(snippet, text) - - def test_training_readmes_expose_operator_acceptance_index(self) -> None: - for path in TRAINING_ACCEPTANCE_INDEX_DOCS: - text = path.read_text(encoding="utf-8") - with self.subTest(path=path.name): - for snippet in TRAINING_ACCEPTANCE_INDEX_REQUIRED_SNIPPETS: - self.assertIn(snippet, text) - self.assertLess( - text.index("Accepted milestone range:"), - text.index("Offline validation commands:"), - ) - self.assertLess( - text.index("Offline validation commands:"), - text.index("Accepted user-facing surfaces:"), - ) - - for path in TRAINING_PUBLIC_PRIVACY_DOCS: - text = path.read_text(encoding="utf-8") - with self.subTest(path=path.name, surface="manifest_modules"): - for module in TRAINING_CASE_ACCEPTANCE_TEST_MANIFEST: - self.assertNotIn(module, text) - - def test_readmes_describe_single_set_local_docker_deployment_policy(self) -> None: - for path in LOCAL_DOCKER_POLICY_DOCS: - text = path.read_text(encoding="utf-8") - lower_text = text.lower() - - with self.subTest(path=path.name): - for snippet in LOCAL_DOCKER_POLICY_REQUIRED_SNIPPETS: - self.assertIn(snippet, text) - self.assertLess( - lower_text.index("down --remove-orphans"), - lower_text.index("up -d --build"), - ) - for forbidden in LOCAL_DOCKER_POLICY_FORBIDDEN_ENCOURAGEMENT: - self.assertNotIn(forbidden, lower_text) - - def test_readmes_lock_single_set_docker_smoke_command_shape(self) -> None: - command_lines_by_path = { - path: _local_docker_command_lines(_local_docker_deployment_section(path)) - for path in LOCAL_DOCKER_POLICY_DOCS - } - expected_commands = LOCAL_DOCKER_REBUILD_COMMANDS + LOCAL_DOCKER_SMOKE_COMMANDS - - self.assertEqual( - command_lines_by_path[PROJECT_README], - command_lines_by_path[TRAINING_PRODUCT_DOC], - ) - for path, command_lines in command_lines_by_path.items(): - with self.subTest(path=path.name): - for command in expected_commands: - self.assertIn(command, command_lines) - self.assertLess( - command_lines.index(LOCAL_DOCKER_REBUILD_COMMANDS[0]), - command_lines.index(LOCAL_DOCKER_REBUILD_COMMANDS[1]), - ) - for command in command_lines: - if command.startswith("docker compose"): - self.assertTrue(command.startswith(LOCAL_DOCKER_COMPOSE_PREFIX), command) - self.assertNotIn("docker compose -p ai-ict down --remove-orphans", command_lines) - self.assertNotIn("docker compose -p ai-ict up -d --build", command_lines) - - def test_docs_describe_trading_delivery_checklist(self) -> None: - for path in TRADING_DELIVERY_DOCS: - section = _trading_delivery_section(path) - - with self.subTest(path=path.name): - for snippet in TRADING_DELIVERY_REQUIRED_SNIPPETS: - self.assertIn(snippet, section) - self.assertLess( - section.index("must-run before every market validation or deploy"), - section.index("Rollback trigger"), - ) - self.assertIn("backtest_quality_gate.passed", section) - - def test_docs_lock_trading_delivery_command_shape(self) -> None: - command_lines_by_path = { - path: _trading_delivery_command_lines(_trading_delivery_section(path)) - for path in TRADING_DELIVERY_DOCS - } - - self.assertEqual( - command_lines_by_path[PROJECT_README], - command_lines_by_path[PROJECT_RUNBOOK], - ) - for path, command_lines in command_lines_by_path.items(): - with self.subTest(path=path.name): - self.assertEqual(command_lines, TRADING_DELIVERY_COMMANDS) - - def test_training_case_product_doc_names_public_response_privacy_contracts(self) -> None: - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") + self.assertIn("/training", text) + self.assertNotIn("training_case_engine_m1_m6_product_readme.md", text) + self.assertIn("Training Console at `/training`", README.read_text(encoding="utf-8")) + def test_training_public_doc_matches_current_console_and_routes(self) -> None: + text = TRAINING_PUBLIC_DOC.read_text(encoding="utf-8") for snippet in ( - "case list/detail", - "package export/import", - "progress", - "submit", - "create-attempt", - "annotation lifecycle", - "privacy contract tests", - "scrub private solution material", - "internal solution details", + "The public browser entry is `/training`.", + "The trading workbench stays at `/`.", + "| GET | `/training/cases` |", + "| GET | `/training/progress` |", + "| POST | `/training/attempts` |", + "Training public error catalog:", ): - with self.subTest(snippet=snippet): - self.assertIn(snippet, text) - - self.assertNotIn("private " + "answer", text.lower()) - self.assertNotIn("answer " + "detail", text.lower()) - - def test_training_case_product_doc_api_routes_match_server_route_request(self) -> None: - documented_routes = _training_api_routes_from_product_doc() - server_routes = _supported_training_routes_from_route_request() - - self.assertEqual( - len(documented_routes), - len(set(documented_routes)), - "Training API docs contain duplicate route entries", - ) - self.assertEqual( - sorted(documented_routes), - sorted(server_routes), - ) - - def test_training_case_public_route_registry_is_documented_and_supported(self) -> None: - documented_routes = set(_training_api_routes_from_product_doc()) - server_routes = set(_supported_training_routes_from_route_request()) - - missing_from_docs = [ - route for route in TRAINING_PUBLIC_API_ROUTE_REGISTRY if route not in documented_routes - ] - missing_from_server = [ - route for route in TRAINING_PUBLIC_API_ROUTE_REGISTRY if route not in server_routes - ] - - self.assertEqual(missing_from_docs, []) - self.assertEqual(missing_from_server, []) - - def test_training_case_product_doc_api_surface_wording_matches_public_routes(self) -> None: - capabilities = { - (method, path): capability - for method, path, capability in _training_api_surface_rows_from_product_doc() - } - server_routes = set(_supported_training_routes_from_route_request()) - - self.assertEqual(sorted(capabilities), sorted(TRAINING_PUBLIC_API_ROUTE_REGISTRY)) - for route, snippets in TRAINING_ROUTE_SURFACE_WORDING.items(): - with self.subTest(route=route): - self.assertIn(route, server_routes) - for snippet in snippets: - self.assertIn(snippet, capabilities[route]) - - def test_training_dashboard_routes_are_documented_public_api_routes(self) -> None: - dashboard_routes = _training_dashboard_routes_from_app() - dashboard_paths = _training_dashboard_fetch_paths_from_app() - documented_routes = set(_training_api_routes_from_product_doc()) - server_routes = set(_supported_training_routes_from_route_request()) - - self.assertEqual(dashboard_routes, TRAINING_DASHBOARD_ROUTE_REGISTRY) - self.assertEqual(dashboard_paths, tuple(path for _method, path in TRAINING_DASHBOARD_ROUTE_REGISTRY)) - for route in dashboard_routes: - with self.subTest(route=route): - self.assertIn(route, TRAINING_PUBLIC_API_ROUTE_REGISTRY) - self.assertIn(route, documented_routes) - self.assertIn(route, server_routes) - - def test_training_dashboard_and_public_docs_keep_privacy_wording_exclusionary(self) -> None: - dashboard_section = _training_product_doc_section("Dashboard Entry").lower() - dashboard_source = "\n".join( - path.read_text(encoding="utf-8").lower() - for path in (TRAINING_DASHBOARD_HTML, TRAINING_DASHBOARD_APP) - ) - - for snippet in TRAINING_DASHBOARD_PUBLIC_WORDING: - with self.subTest(snippet=snippet): - self.assertIn(snippet, dashboard_section) - - for term in TRAINING_PUBLIC_PRIVACY_FORBIDDEN_TERMS: - with self.subTest(surface="dashboard", term=term): - self.assertNotIn(term, dashboard_source) - - for phrase in TRAINING_PUBLIC_PRIVACY_MATERIAL_PHRASES: - with self.subTest(surface="dashboard", phrase=phrase): - self.assertNotIn(phrase, dashboard_source) - - for path in TRAINING_PUBLIC_PRIVACY_DOCS: - text = path.read_text(encoding="utf-8") - lower_text = text.lower() - privacy_lines = _training_public_privacy_lines(text) - - with self.subTest(path=path.name): - self.assertTrue(privacy_lines) - for term in TRAINING_PUBLIC_PRIVACY_FORBIDDEN_TERMS: - self.assertNotIn(term, lower_text) - for line in privacy_lines: - self.assertTrue( - any(word in line for word in TRAINING_PUBLIC_PRIVACY_EXCLUSION_WORDS), - line, - ) - - def test_training_case_product_doc_names_public_error_semantics(self) -> None: - section = _training_validation_section() + self.assertIn(snippet, text) + def test_training_package_smoke_doc_matches_current_fixture_and_cli(self) -> None: + text = TRAINING_PACKAGE_SMOKE_DOC.read_text(encoding="utf-8") for snippet in ( - "`400` with `invalid_json_body`", - "`400` with `invalid_path_param:`", - "`400` with `invalid_limit_query_param:limit`", - "`404` with `not_found`", - "`500` with `internal_server_error`", - "do not echo raw internals", + "tests/fixtures/training_case_package_m6.json", + ".venv/bin/python scripts/run_training_case_package.py", + "--input", + "--output", + "public package", + "public export", + "public output", ): - with self.subTest(snippet=snippet): - self.assertIn(snippet, section) + self.assertIn(snippet, text) - self.assertNotIn("answer" + "_key", section) + def test_trading_root_and_training_console_are_split_cleanly(self) -> None: + trading_html = TRADING_INDEX_HTML.read_text(encoding="utf-8") + trading_js = TRADING_APP_JS.read_text(encoding="utf-8") + training_html = TRAINING_HTML.read_text(encoding="utf-8") + training_js = TRAINING_JS.read_text(encoding="utf-8") - def test_training_readmes_publish_public_error_catalog(self) -> None: - for path in TRAINING_PUBLIC_ERROR_DOCS: + self.assertNotIn('id="trainingSection"', trading_html) + self.assertIn('id="trainingSection"', training_html) + self.assertNotIn("TRAINING_PUBLIC_ROUTES", trading_js) + self.assertIn("TRAINING_PUBLIC_ROUTES", training_js) + + def test_offline_checks_include_training_console_syntax(self) -> None: + commands = dict(run_offline_checks.build_check_commands(env={})) + self.assertEqual(commands["dashboard_js_syntax"], ["node", "--check", "src/web/app.js"]) + self.assertEqual(commands["training_console_js_syntax"], ["node", "--check", "src/web/training.js"]) + + def test_local_demo_readiness_uses_current_docs(self) -> None: + self.assertEqual( + check_local_demo_readiness.LOCAL_DEMO_DOCS, + (README, RUNBOOK), + ) + + def test_trading_delivery_readiness_uses_canonical_smoke_route(self) -> None: + commands = dict(check_trading_delivery_readiness.TRADING_DELIVERY_COMMAND_GROUPS)["deploy_and_health"] + self.assertIn('curl -fsS "http://127.0.0.1:8000/trading/candidates?instrument_id=1&limit=1"', commands) + + def test_readme_lists_current_json_output_variables(self) -> None: + text = README.read_text(encoding="utf-8") + for variable in ( + "LIVE_ADAPTER_OUTPUT_JSON", + "TRADING_JOURNAL_OUTPUT_JSON", + "TRADE_PLAN_CANDIDATES_OUTPUT_JSON", + ): + self.assertIn(variable, text) + + def test_scripts_do_not_reference_deleted_training_product_doc(self) -> None: + for path in ( + ROOT / "scripts" / "check_local_demo_readiness.py", + ROOT / "scripts" / "run_offline_checks.py", + ROOT / "scripts" / "check_trading_delivery_readiness.py", + ): text = path.read_text(encoding="utf-8") with self.subTest(path=path.name): - for snippet in TRAINING_PUBLIC_ERROR_CATALOG_SNIPPETS: - self.assertIn(snippet, text) + self.assertNotIn("training_case_engine_m1_m6_product_readme.md", text) - def test_training_readmes_publish_public_error_catalog_examples(self) -> None: - for path in TRAINING_PUBLIC_ERROR_DOCS: - section = _training_public_error_section(path) - with self.subTest(path=path.name): - self.assertIn("Public API response examples", section) - self.assertIn("Package CLI public error examples", section) - for label, summary, payload in TRAINING_PUBLIC_ERROR_API_EXAMPLES: - self.assertIn(f"- {label}: {summary}", section) - self.assertIn(_public_error_json_block(payload), section) - for label, payload in TRAINING_PUBLIC_ERROR_CLI_EXAMPLES: - self.assertIn(f"- {label}:", section) - self.assertIn(_public_error_json_block(payload), section) - - def test_training_public_error_catalog_examples_are_one_line_public_json(self) -> None: - api_payloads = tuple(payload for _, _, payload in TRAINING_PUBLIC_ERROR_API_EXAMPLES) - cli_payloads = tuple(payload for _, payload in TRAINING_PUBLIC_ERROR_CLI_EXAMPLES) - - for path in TRAINING_PUBLIC_ERROR_DOCS: - section = _training_public_error_section(path) - with self.subTest(path=path.name): - for payload in api_payloads: - block = _public_error_json_block(payload) - lines = block.splitlines() - parsed = json.loads(lines[1]) - - self.assertIn(block, section) - self.assertEqual(lines, ["```json", lines[1], "```"]) - self.assertEqual(parsed, payload) - self.assertEqual(set(parsed), {"error"}) - - for payload in cli_payloads: - block = _public_error_json_block(payload) - lines = block.splitlines() - parsed = json.loads(lines[1]) - - self.assertIn(block, section) - self.assertEqual(lines, ["```json", lines[1], "```"]) - self.assertEqual(parsed, payload) - self.assertEqual(list(parsed), ["passed", "input", "output", "error"]) - self.assertIs(parsed["passed"], False) - - def test_training_public_error_code_inventory_self_lock(self) -> None: - expected_inventory = ( - ( - "api_examples", - ( - "invalid_json_body", - "missing_required_json_field:case_id", - "invalid_json_field:case_id", - "invalid_path_param:case_id", - "invalid_limit_query_param:limit", - "not_found", - "internal_server_error", - ), - ), - ( - "package_examples", - ( - "input_file_not_found", - "input_path_is_directory", - "output_path_is_directory", - ), - ), - ( - "package_catalog_additional", - ( - "output_path_unwritable", - ), - ), - ) - module_tree = ast.parse(Path(__file__).read_text(encoding="utf-8")) - inventory_assignments = [ - node - for node in module_tree.body - if isinstance(node, ast.Assign) - for target in node.targets - if ( - isinstance(target, ast.Name) - and target.id == "TRAINING_PUBLIC_ERROR_CODE_INVENTORY" - ) - ] - inventory = TRAINING_PUBLIC_ERROR_CODE_INVENTORY - inventory_by_group = dict(inventory) - flattened_codes = tuple(code for _, codes in inventory for code in codes) - api_example_codes = tuple( - payload["error"] for _, _, payload in TRAINING_PUBLIC_ERROR_API_EXAMPLES - ) - cli_example_codes = tuple( - payload["error"] for _, payload in TRAINING_PUBLIC_ERROR_CLI_EXAMPLES - ) - - self.assertEqual(len(inventory_assignments), 1) - inventory_source = inventory_assignments[0].value - self.assertIsInstance(inventory_source, ast.Tuple) - self.assertEqual(ast.literal_eval(inventory_source), expected_inventory) - self.assertEqual(inventory, expected_inventory) - self.assertEqual( - tuple(group_name for group_name, _ in inventory), - ("api_examples", "package_examples", "package_catalog_additional"), - ) - self.assertEqual(len(inventory), len(inventory_by_group)) - self.assertEqual(len(flattened_codes), len(set(flattened_codes))) - self.assertEqual(inventory_by_group["api_examples"], api_example_codes) - self.assertEqual(inventory_by_group["package_examples"], cli_example_codes) - - for path in TRAINING_PUBLIC_ERROR_DOCS: - section = _training_public_error_section(path) - with self.subTest(path=path.name): - self.assertLess( - section.index("Public API response examples"), - section.index("Package CLI public error examples"), - ) - for code in flattened_codes: - self.assertIn(code, section) - - def test_training_public_error_docs_avoid_raw_stack_and_os_error_wording(self) -> None: - for path in TRAINING_PUBLIC_ERROR_RAW_WORDING_DOCS: - text = path.read_text(encoding="utf-8").lower() - with self.subTest(path=path.name): - for forbidden in TRAINING_PUBLIC_ERROR_FORBIDDEN_RAW_WORDING: - self.assertNotIn(forbidden.lower(), text) - - def test_training_public_error_sections_avoid_sensitive_and_local_internals(self) -> None: - forbidden_terms = ( - "data" + "base", - "data" + "base_url", - "post" + "gresql", - "psy" + "copg", - "pass" + "word", - "pass" + "wd", - "tok" + "en", - "sec" + "ret", - "api" + "_key", - "trace" + "back", - "stack trace", - "runtime" + "error", - ) - local_path_roots = tuple( - "/" + root + "/" - for root in ("tmp", "root", "home", "Users", "var", "etc") - ) - - for path in TRAINING_PUBLIC_ERROR_DOCS: - section = _training_public_error_section(path) - lower_section = section.lower() - with self.subTest(path=path.name): - for forbidden in forbidden_terms: - self.assertNotIn(forbidden.lower(), lower_section) - for local_path_root in local_path_roots: - self.assertNotIn(local_path_root.lower(), lower_section) - self.assertNotRegex(section, r"(? None: - text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8").lower() - - for term in ( - "data" + "base", - "data" + "base_url", - "post" + "gresql", - "psy" + "copg", - "pass" + "word", - "pass" + "wd", - "tok" + "en", - "sec" + "ret", - "api" + "_key", - "answer" + "_key", + def test_current_readiness_scripts_are_static_and_offline(self) -> None: + for module_path in ( + ROOT / "scripts" / "check_local_demo_readiness.py", + ROOT / "scripts" / "check_trading_delivery_readiness.py", ): - with self.subTest(term=term): - self.assertNotIn(term, text) - - self.assertIsNone(re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text)) + source = module_path.read_text(encoding="utf-8") + tree = ast.parse(source) + imported_modules = { + alias.name + for node in ast.walk(tree) + if isinstance(node, ast.Import) + for alias in node.names + } + with self.subTest(path=module_path.name): + self.assertNotIn("subprocess", imported_modules) if __name__ == "__main__": diff --git a/tests/unit/test_local_demo_readiness.py b/tests/unit/test_local_demo_readiness.py index 0415081..c911943 100644 --- a/tests/unit/test_local_demo_readiness.py +++ b/tests/unit/test_local_demo_readiness.py @@ -12,7 +12,7 @@ ROOT = Path(__file__).resolve().parents[2] CHECK_COMMAND = ".venv/bin/python scripts/check_local_demo_readiness.py" EXPECTED_DOC_PATHS = ( ROOT / "README.md", - ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md", + ROOT / "RUNBOOK.md", ) EXPECTED_REBUILD_COMMANDS = ( "docker compose -p ai-ict -f /root/deploy/ai-ict/docker-compose.yml down --remove-orphans", diff --git a/tests/unit/test_trading_delivery_readiness.py b/tests/unit/test_trading_delivery_readiness.py index 25f3032..e0b3ab3 100644 --- a/tests/unit/test_trading_delivery_readiness.py +++ b/tests/unit/test_trading_delivery_readiness.py @@ -44,7 +44,7 @@ EXPECTED_COMMAND_GROUPS = ( 'test "$(docker ps --filter name=^/ai-ict$ --format \'{{.Names}}\' | wc -l | tr -d \' \')" = "1"', 'test "$(docker inspect -f \'{{.State.Health.Status}}\' ai-ict)" = "healthy"', "curl -fsS http://127.0.0.1:8000/health", - 'curl -fsS "http://127.0.0.1:8000/signals?instrument_id=1&limit=1"', + 'curl -fsS "http://127.0.0.1:8000/trading/candidates?instrument_id=1&limit=1"', ), ), ( diff --git a/tests/unit/test_trading_delivery_rehearsal.py b/tests/unit/test_trading_delivery_rehearsal.py index c4b31fe..1262d96 100644 --- a/tests/unit/test_trading_delivery_rehearsal.py +++ b/tests/unit/test_trading_delivery_rehearsal.py @@ -78,7 +78,7 @@ class TradingDeliveryRehearsalTests(unittest.TestCase): def fake_runner(command, **kwargs): calls.append(command) - script = command[1] if len(command) > 1 and command[0].endswith("python") else command[0] + script = command[1] if len(command) > 1 and "python" in Path(command[0]).name.lower() else command[0] if script.endswith("check_trading_delivery_readiness.py"): return SimpleNamespace(returncode=0, stdout="ok\n", stderr="") if script.endswith("run_historical_compatibility_repair.py"): @@ -130,7 +130,7 @@ class TradingDeliveryRehearsalTests(unittest.TestCase): def test_rehearsal_blocks_when_reference_migration_is_deferred(self) -> None: def fake_runner(command, **kwargs): - script = command[1] if len(command) > 1 and command[0].endswith("python") else command[0] + script = command[1] if len(command) > 1 and "python" in Path(command[0]).name.lower() else command[0] if script.endswith("check_trading_delivery_readiness.py"): return SimpleNamespace(returncode=0, stdout="ok\n", stderr="") if script.endswith("run_historical_compatibility_repair.py"): @@ -169,7 +169,7 @@ class TradingDeliveryRehearsalTests(unittest.TestCase): def test_rehearsal_blocks_when_autonomous_cycle_reports_quality_gate_failure(self) -> None: def fake_runner(command, **kwargs): - script = command[1] if len(command) > 1 and command[0].endswith("python") else command[0] + script = command[1] if len(command) > 1 and "python" in Path(command[0]).name.lower() else command[0] if script.endswith("check_trading_delivery_readiness.py"): return SimpleNamespace(returncode=0, stdout="ok\n", stderr="") if script.endswith("run_historical_compatibility_repair.py"): diff --git a/tests/unit/test_training_case_docs_cli_m10.py b/tests/unit/test_training_case_docs_cli_m10.py index f34299a..e5e7a7e 100644 --- a/tests/unit/test_training_case_docs_cli_m10.py +++ b/tests/unit/test_training_case_docs_cli_m10.py @@ -1,935 +1,110 @@ -import ast import contextlib import io import json import re import shlex -import sys -import tempfile import unittest -from unittest import mock from pathlib import Path from types import SimpleNamespace from scripts import run_offline_checks from scripts import run_training_case_package as training_cli -from src.services.training import TrainingCaseService ROOT = Path(__file__).resolve().parents[2] -DOCUMENTATION_CONSISTENCY_TEST = ROOT / "tests" / "unit" / "test_documentation_consistency.py" -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" +TRAINING_PUBLIC_DOC = ROOT / "docs" / "training_console_public_surface_v1.md" +DOC_EXAMPLE = ROOT / "docs" / "examples" / "training_case_package_smoke_m6.md" FIXTURE = ROOT / "tests" / "fixtures" / "training_case_package_m6.json" FIXTURE_REF = "tests/fixtures/training_case_package_m6.json" +PACKAGE_SMOKE_COMMAND = ".venv/bin/python scripts/run_training_case_package.py" OFFLINE_CHECK_COMMAND = ".venv/bin/python scripts/run_offline_checks.py" -OFFLINE_CHECK_NAME = "training_case_package" -OFFLINE_CHECK_DOC_NAME = f"`{OFFLINE_CHECK_NAME}`" -PACKAGE_SMOKE_SCRIPT = "scripts/run_training_case_package.py" -PACKAGE_SMOKE_COMMAND = f".venv/bin/python {PACKAGE_SMOKE_SCRIPT}" -PACKAGE_SMOKE_HELP_COMMAND = f"{PACKAGE_SMOKE_COMMAND} --help" -DASHBOARD_SYNTAX_CHECK_NAME = "dashboard_js_syntax" -DASHBOARD_SYNTAX_CHECK_PATH = "src/web/app.js" -OFFLINE_CHECK_REGISTRY_NAMES = ( - "unit_tests", - OFFLINE_CHECK_NAME, - "migration_static_checks", - "script_static_checks", - "trading_delivery_readiness", - DASHBOARD_SYNTAX_CHECK_NAME, -) -CURRENT_TRAINING_DOC_MATURITY_END = 28 -CURRENT_TRAINING_DOC_MATURITY_MARKER = f"M1-M{CURRENT_TRAINING_DOC_MATURITY_END}" -CURRENT_TRAINING_DOC_MATURITY_SPAN = f"through M{CURRENT_TRAINING_DOC_MATURITY_END}" -TRAINING_DOCS_CLI_TEST_CLASS = "TrainingCaseDocsCliM10Tests" -DOCUMENTATION_CONSISTENCY_TEST_CLASS = "DocumentationConsistencyTests" -PUBLIC_FIELD = "answer" + "_key" -PRIVATE_MATERIAL_PHRASES = ("private solution material", "internal solution details") -PUBLIC_PACKAGE_DOCS = (DOC_EXAMPLE, README) -PACKAGE_SMOKE_SURFACE_DOCS = (DOC_EXAMPLE, README, PRODUCT_README) -PUBLIC_PACKAGE_REQUIRED_TEXT = ( - ".venv/bin/python scripts/run_training_case_package.py", - FIXTURE_REF, - "--input", - "--output", - "import/export smoke", - "public export", - "public package", - "public output", - "internal solution details", -) -PACKAGE_IMPORT_EXPORT_SURFACE_TEXT = ( - "import", - "export", - "public package", - "public export", - "public output", -) -PACKAGE_CLI_HELP_DISCOVERY_TEXT = ( - "offline package smoke", - "import/export smoke", - "public package", - "public export", - "public output", - "one-line json", - "stable public error codes", - "input_file_not_found", - "input_path_is_directory", - "output_path_is_directory", - "output_path_unwritable", -) -PACKAGE_DOC_HELP_ERROR_TEXT = ( - PACKAGE_SMOKE_HELP_COMMAND, - "one-line JSON", - "`passed`", - "`input`", - "`output`", - "`error`", - "`input_file_not_found`", - "`input_path_is_directory`", - "`output_path_is_directory`", - "`output_path_unwritable`", -) -PACKAGE_CLI_PUBLIC_ERROR_CODES = ( - "input_file_not_found", - "input_path_is_directory", - "output_path_is_directory", - "output_path_unwritable", -) -PACKAGE_CLI_PUBLIC_ERROR_EXAMPLE_CODES = PACKAGE_CLI_PUBLIC_ERROR_CODES[:3] -PACKAGE_CLI_PUBLIC_ERROR_TEST_INVENTORY = { - "docs": ( - "test_package_smoke_docs_publish_help_and_stable_public_error_wording", - ), - "help": ( - "test_cli_help_discloses_offline_public_import_export_and_error_contract", - ), - "readiness": ( - "test_cli_error_summary_uses_public_output_target_code_without_runtime_leaks", - ), -} -PRODUCT_PACKAGE_WORDING = ( - CURRENT_TRAINING_DOC_MATURITY_MARKER, - "offline checks registry smoke", - "public package", - "public export", - "public output", - "private solution material", - "internal solution details", -) -PRIVATE_DOC_DRIFT_PHRASES = ("private " + "answer", "answer " + "detail") -PRIVATE_FIXTURE_DRIFT_PHRASES = PRIVATE_MATERIAL_PHRASES + PRIVATE_DOC_DRIFT_PHRASES -RESTRICTED_PUBLIC_TEXT = ( - "data" + "base", - "data" + "base_url", - "post" + "gresql", - "psy" + "copg", - "pass" + "word", - "pass" + "wd", - "tok" + "en", - "sec" + "ret", - "api" + "_key", -) -TRAINING_M24_M29_PACKAGE_DOCS_TEST_INVENTORY = { - "package_smoke_privacy": ( - "test_public_privacy_guard_rejects_solution_wording_drift", - "test_public_fixture_schema_contract_is_minimal_and_demo_safe", - "test_fixture_is_compatible_with_public_service_and_cli_export", - "test_smoke_docs_and_product_readme_share_fixture_and_public_export_contract", - "test_example_command_uses_supported_cli_flags_and_fixture", - "test_example_documents_cli_summary_keys_and_output_semantics", - "test_cli_error_summary_uses_public_output_target_code_without_runtime_leaks", - "test_readme_and_product_docs_advertise_public_offline_smoke_contract", - "test_product_readme_uses_current_maturity_and_public_private_package_language", - "test_public_package_smoke_docs_lock_import_export_privacy_language", - "test_cli_help_discloses_offline_public_import_export_and_error_contract", - "test_package_smoke_docs_publish_help_and_stable_public_error_wording", - "test_package_cli_public_error_inventory_locks_docs_help_and_readiness", - "test_m1_m28_package_smoke_docs_cover_static_import_export_surface", - ), - "offline_registry_coverage": ( - "test_offline_check_registry_name_matches_package_smoke_docs", - "test_offline_checks_registry_runs_training_package_smoke_entry", - "test_training_docs_name_broader_offline_check_path", - ), - "m1_m28_maturity_wording": ( - "test_product_readme_uses_current_maturity_and_public_private_package_language", - "test_m1_m28_package_smoke_docs_cover_static_import_export_surface", - ), -} -TRAINING_M33_REQUIRED_INVENTORY_GROUPS = ( - "package_smoke_docs_command_help", - "fixture_public_shape", - "fixture_private_solution_absence", - "service_import_export_public_output", - "cli_smoke_export_import_summary", - "readme_product_docs_offline_registry_refs", -) -TRAINING_M33_PACKAGE_FIXTURE_IMPORT_EXPORT_PRIVACY_INVENTORY = { - "package_smoke_docs_command_help": ( - "test_example_command_uses_supported_cli_flags_and_fixture", - "test_public_package_smoke_docs_lock_import_export_privacy_language", - "test_cli_help_discloses_offline_public_import_export_and_error_contract", - "test_package_smoke_docs_publish_help_and_stable_public_error_wording", - "test_m1_m28_package_smoke_docs_cover_static_import_export_surface", - ), - "fixture_public_shape": ( - "test_public_fixture_schema_contract_is_minimal_and_demo_safe", - ), - "fixture_private_solution_absence": ( - "test_public_privacy_guard_rejects_solution_wording_drift", - "test_public_fixture_schema_contract_is_minimal_and_demo_safe", - "test_fixture_is_compatible_with_public_service_and_cli_export", - ), - "service_import_export_public_output": ( - "test_fixture_is_compatible_with_public_service_and_cli_export", - ), - "cli_smoke_export_import_summary": ( - "test_fixture_is_compatible_with_public_service_and_cli_export", - "test_example_documents_cli_summary_keys_and_output_semantics", - "test_cli_error_summary_uses_public_output_target_code_without_runtime_leaks", - ), - "readme_product_docs_offline_registry_refs": ( - "test_smoke_docs_and_product_readme_share_fixture_and_public_export_contract", - "test_readme_and_product_docs_advertise_public_offline_smoke_contract", - "test_offline_check_registry_name_matches_package_smoke_docs", - "test_offline_checks_registry_runs_training_package_smoke_entry", - "test_training_docs_name_broader_offline_check_path", - "test_training_readmes_name_dashboard_privacy_and_offline_registry_coverage", - ), -} -def _example_command_parts() -> list[str]: +def _documented_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) - - -def _fixture_payload() -> dict: - return json.loads(FIXTURE.read_text(encoding="utf-8")) - - -def _package_doc_text(path: Path) -> str: - text = path.read_text(encoding="utf-8") - if path != README: - return text - - lines = [line for line in text.splitlines() if "run_training_case_package.py" in line] - if not lines: - raise AssertionError("README is missing the package smoke command") - return "\n".join(lines) - - -def _package_smoke_flags_from_source() -> tuple[str, ...]: - source = Path(training_cli.__file__).read_text(encoding="utf-8") - return tuple(re.findall(r'add_argument\("(?P--[a-z-]+)"', source)) - - -def _package_cli_public_error_inventory_method_names() -> tuple[str, ...]: - return tuple( - name - for names in PACKAGE_CLI_PUBLIC_ERROR_TEST_INVENTORY.values() - for name in names - ) - - -def _package_cli_public_error_doc_snippets() -> tuple[str, ...]: - return tuple(f"`{code}`" for code in PACKAGE_CLI_PUBLIC_ERROR_CODES) - - -def _package_cli_error_example_payloads(path: Path) -> tuple[dict, ...]: - text = path.read_text(encoding="utf-8") - marker = "Package CLI public error examples:" - if marker not in text: - raise AssertionError(f"{path} is missing package CLI public error examples") - - section = text.split(marker, 1)[1] - next_heading = re.search(r"\n## ", section) - if next_heading is not None: - section = section[: next_heading.start()] - - blocks = re.findall(r"```json\n(?P\{[^\n]*\})\n```", section) - if not blocks: - raise AssertionError(f"{path} is missing one-line package CLI public error JSON") - return tuple(json.loads(block) for block in blocks) - - -def _run_package_cli_summary(args: list[str]) -> tuple[int, str, dict, str]: - stdout = io.StringIO() - stderr = io.StringIO() - - with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): - exit_code = training_cli.main(args) - - raw_output = stdout.getvalue() - return exit_code, raw_output, json.loads(raw_output.strip()), stderr.getvalue() - - -def _test_method_names_from_file(path: Path, class_name: str) -> tuple[str, ...]: - tree = ast.parse(path.read_text(encoding="utf-8")) - for node in tree.body: - if isinstance(node, ast.ClassDef) and node.name == class_name: - return tuple( - item.name - for item in node.body - if isinstance(item, ast.FunctionDef) and item.name.startswith("test_") - ) - raise AssertionError(f"{path} is missing {class_name}") - - -def _walk_public_text(value) -> list[str]: - if isinstance(value, dict): - entries = [] - for key, item in value.items(): - entries.append(str(key)) - entries.extend(_walk_public_text(item)) - return entries - if isinstance(value, list): - entries = [] - for item in value: - entries.extend(_walk_public_text(item)) - return entries - if isinstance(value, str): - return [value] - return [] + raise AssertionError("missing documented package smoke command") + return shlex.split(re.sub(r"\\\n\s*", " ", match.group("command").strip())) class TrainingCaseDocsCliM10Tests(unittest.TestCase): - def assertNoPrivateFixtureMaterial(self, payload: dict) -> None: - serialized = json.dumps(payload, sort_keys=True) - lower_serialized = serialized.lower() + def test_example_doc_uses_current_public_fixture_and_cli(self) -> None: + parts = _documented_command_parts() + self.assertEqual(parts[:3], [".venv/bin/python", "scripts/run_training_case_package.py", "--input"]) + self.assertIn(FIXTURE_REF, parts) + self.assertIn("--output", parts) - self.assertNotIn(PUBLIC_FIELD, lower_serialized) - self.assertIsNone(re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", serialized)) - for phrase in PRIVATE_FIXTURE_DRIFT_PHRASES: - self.assertNotIn(phrase, lower_serialized) - for text in _walk_public_text(payload): - lower_text = text.lower() - for fragment in RESTRICTED_PUBLIC_TEXT: - with self.subTest(text=text, fragment=fragment): - self.assertNotIn(fragment, lower_text) - - def test_public_privacy_guard_rejects_solution_wording_drift(self) -> None: - polluted_payloads = [ - {"items": [{"prompt": "Do not expose " + ("private " + "answer") + " wording."}]}, - {"items": [{"prompt": "Do not expose internal solution details wording."}]}, - ] - - for payload in polluted_payloads: - with self.subTest(payload=payload): - with self.assertRaises(AssertionError): - self.assertNoPrivateFixtureMaterial(payload) - - def test_public_fixture_schema_contract_is_minimal_and_demo_safe(self) -> None: - payload = _fixture_payload() - - self.assertEqual(set(payload), {"version", "metadata", "items"}) - self.assertEqual(payload["version"], 1) - self.assertEqual(payload["metadata"], {"source": "m6_public_fixture", "purpose": "offline_smoke"}) - self.assertEqual(len(payload["items"]), 1) - - item = payload["items"][0] - self.assertEqual( - set(item), - { - "external_id", - "title", - "scenario", - "skill_tags", - "difficulty", - "answer", - "rubric", - "tasks", - "metadata", - }, - ) - self.assertEqual(item["external_id"], "m6-sweep-public-001") - self.assertEqual(item["title"], "Liquidity sweep reading practice") - self.assertEqual(item["skill_tags"], ["sweep", "context_reading"]) - self.assertEqual(item["difficulty"], "easy") - self.assertEqual(item["answer"], {}) - self.assertEqual( - item["rubric"], - [ - {"category": "context", "max_score": 3}, - {"category": "risk_story", "max_score": 2}, - ], - ) - self.assertEqual(len(item["tasks"]), 1) - self.assertEqual(item["metadata"], {"chapter_ref": "public_training_m6", "demo": True}) - - scenario = item["scenario"] - self.assertEqual( - set(scenario), - { - "instrument_id", - "timeframe", - "case_type", - "start_ts", - "decision_ts", - "end_ts", - "source_event_type", - "source_event_id", - "source_event_refs", - "chapter_ref", - }, - ) - self.assertEqual(scenario["instrument_id"], 1) - self.assertEqual(scenario["timeframe"], "15m") - self.assertEqual(scenario["case_type"], "sweep") - self.assertEqual(scenario["start_ts"], "2026-04-20T13:00:00Z") - self.assertEqual(scenario["decision_ts"], "2026-04-20T14:00:00Z") - self.assertEqual(scenario["end_ts"], "2026-04-20T15:00:00Z") - self.assertEqual(scenario["source_event_type"], "sweep_event") - self.assertEqual(scenario["source_event_id"], 61) - self.assertEqual(scenario["source_event_refs"], [{"kind": "sweep_event", "id": 61}]) - self.assertEqual(scenario["chapter_ref"], "public_training_m6") - - task = item["tasks"][0] - self.assertEqual( - set(task), - { - "task_type", - "skill_tag", - "prompt", - "required_annotation_types", - "max_score", - "sort_order", - "meta", - }, - ) - self.assertEqual(task["task_type"], "short_answer") - self.assertEqual(task["skill_tag"], "sweep") - self.assertEqual(task["prompt"], "Describe what needs confirmation before marking this as a usable sweep.") - self.assertEqual(task["required_annotation_types"], ["text"]) - self.assertEqual(task["max_score"], 5) - self.assertEqual(task["sort_order"], 0) - self.assertEqual(task["meta"], {"public_hint": "Focus on context and confirmation."}) - self.assertNoPrivateFixtureMaterial(payload) - - def test_fixture_is_compatible_with_public_service_and_cli_export(self) -> None: - payload = _fixture_payload() - service = TrainingCaseService(repository=training_cli._OfflineTrainingRepository()) - - import_result = service.import_cases(payload) - export_result = service.export_cases(limit=100) - - with tempfile.TemporaryDirectory() as tmpdir: - output_path = Path(tmpdir) / "training_case_public_export.json" - summary = training_cli.run_fixture_smoke(FIXTURE, output_path=output_path) - cli_export = json.loads(output_path.read_text(encoding="utf-8")) - - self.assertEqual(import_result["version"], 1) - self.assertEqual(import_result["imported"], 1) - self.assertEqual(len(import_result["items"]), 1) - self.assertEqual(set(import_result["items"][0]), {"id", "title", "scenario", "skill_tags", "difficulty", "status", "external_id"}) - self.assertEqual(summary["input"], FIXTURE_REF) - self.assertEqual(summary["imported"], 1) - self.assertEqual(summary["exported"], 1) - self.assertEqual(export_result, cli_export) - self.assertEqual(set(export_result), {"version", "metadata", "items"}) - self.assertEqual(export_result["metadata"], {"item_count": 1}) - - exported_item = export_result["items"][0] - fixture_item = payload["items"][0] - self.assertEqual( - set(exported_item), - { - "id", - "title", - "scenario", - "skill_tags", - "difficulty", - "status", - "rubric", - "tasks", - "metadata", - "quality", - }, - ) - self.assertEqual(exported_item["title"], fixture_item["title"]) - self.assertEqual(set(exported_item["skill_tags"]), set(fixture_item["skill_tags"])) - self.assertEqual( - exported_item["metadata"], - {"chapter_ref": "public_training_m6", "demo": True, "external_id": fixture_item["external_id"]}, - ) - self.assertEqual(exported_item["rubric"], fixture_item["rubric"]) - self.assertEqual(exported_item["quality"]["rank"], "high") - self.assertEqual(exported_item["scenario"], fixture_item["scenario"]) - self.assertEqual(set(exported_item["tasks"][0]), set(fixture_item["tasks"][0]) | {"status"}) - self.assertEqual(exported_item["tasks"][0]["status"], "active") - for key, value in fixture_item["tasks"][0].items(): - with self.subTest(task_field=key): - self.assertEqual(exported_item["tasks"][0][key], value) - self.assertNotIn("answer", exported_item) - self.assertNoPrivateFixtureMaterial({"imported": import_result, "exported": export_result}) - - def test_smoke_docs_and_product_readme_share_fixture_and_public_export_contract(self) -> None: - for path in (DOC_EXAMPLE, PRODUCT_README): - text = path.read_text(encoding="utf-8") - lower_text = text.lower() - fixture_refs = set(re.findall(r"tests/fixtures/[A-Za-z0-9_./-]+\.json", text)) - - with self.subTest(path=path.name): - self.assertEqual(fixture_refs, {FIXTURE_REF}) - self.assertIn(".venv/bin/python scripts/run_training_case_package.py", text) - self.assertIn("public export", lower_text) - self.assertTrue(any(phrase in lower_text for phrase in PRIVATE_MATERIAL_PHRASES)) - self.assertNotIn(PUBLIC_FIELD, text) - - 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"], FIXTURE_REF) - 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_cli_error_summary_uses_public_output_target_code_without_runtime_leaks(self) -> None: - with tempfile.TemporaryDirectory() as tmpdir: - output_path = Path(tmpdir) / "training_case_public_export_dir" - output_path.mkdir() - stdout = io.StringIO() - stderr = io.StringIO() - - with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): - exit_code = training_cli.main( - ["--input", str(FIXTURE.relative_to(ROOT)), "--output", str(output_path)] - ) - - raw_output = stdout.getvalue() - line = raw_output.strip() - summary = json.loads(line) - - self.assertEqual(exit_code, 2) - self.assertEqual(stderr.getvalue(), "") - self.assertEqual(raw_output.count("\n"), 1) - self.assertEqual(list(summary), ["passed", "input", "output", "error"]) - self.assertFalse(summary["passed"]) - self.assertEqual(summary["input"], FIXTURE_REF) - self.assertEqual(summary["output"], output_path.name) - self.assertEqual(summary["error"], "output_path_is_directory") - self.assertNotIn(str(output_path), line) - self.assertNotIn(str(Path(tmpdir)), line) - self.assertIsNone(re.search(r'(? 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(FIXTURE_REF, text) - self.assertIn("offline package smoke", lower_text) - self.assertIn("public export", lower_text) - self.assertTrue(any(phrase in lower_text for phrase in PRIVATE_MATERIAL_PHRASES)) - self.assertNotIn(PUBLIC_FIELD, text) - - def test_product_readme_uses_current_maturity_and_public_private_package_language(self) -> None: - text = PRODUCT_README.read_text(encoding="utf-8") - lower_text = text.lower() - - for snippet in PRODUCT_PACKAGE_WORDING: - with self.subTest(snippet=snippet): - self.assertIn(snippet, text if snippet.startswith("M1-") else lower_text) - for phrase in PRIVATE_DOC_DRIFT_PHRASES: - with self.subTest(phrase=phrase): - self.assertNotIn(phrase, lower_text) - self.assertNotIn("M1-M21", text) - self.assertNotIn(PUBLIC_FIELD, text) - - def test_m24_m29_package_docs_privacy_test_inventory_is_current(self) -> None: - test_names = set(unittest.TestLoader().getTestCaseNames(type(self))) - inventory_names = tuple( - name - for names in TRAINING_M24_M29_PACKAGE_DOCS_TEST_INVENTORY.values() - for name in names - ) - unique_inventory_names = set(inventory_names) - product_text = PRODUCT_README.read_text(encoding="utf-8") - - self.assertEqual(len(test_names), 18) - self.assertEqual(len(inventory_names), 19) - self.assertEqual(len(unique_inventory_names), 17) - self.assertEqual(sorted(TRAINING_M24_M29_PACKAGE_DOCS_TEST_INVENTORY), [ - "m1_m28_maturity_wording", - "offline_registry_coverage", - "package_smoke_privacy", - ]) - self.assertEqual( - test_names - unique_inventory_names, - {"test_m24_m29_package_docs_privacy_test_inventory_is_current"}, - ) - for surface, names in TRAINING_M24_M29_PACKAGE_DOCS_TEST_INVENTORY.items(): - with self.subTest(surface=surface): - self.assertTrue(names) - for name in names: - self.assertIn(name, test_names) - - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_MARKER, product_text) - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_SPAN, product_text) - - docs_cli_test_names = set( - _test_method_names_from_file(Path(__file__), TRAINING_DOCS_CLI_TEST_CLASS) - ) - documentation_consistency_test_names = set( - _test_method_names_from_file( - DOCUMENTATION_CONSISTENCY_TEST, - DOCUMENTATION_CONSISTENCY_TEST_CLASS, - ) - ) - m33_available_test_names = docs_cli_test_names | documentation_consistency_test_names - m33_inventory_names = tuple( - name - for names in TRAINING_M33_PACKAGE_FIXTURE_IMPORT_EXPORT_PRIVACY_INVENTORY.values() - for name in names - ) - m33_unique_inventory_names = set(m33_inventory_names) - - self.assertEqual( - sorted(TRAINING_M33_PACKAGE_FIXTURE_IMPORT_EXPORT_PRIVACY_INVENTORY), - sorted(TRAINING_M33_REQUIRED_INVENTORY_GROUPS), - ) - self.assertEqual(len(m33_inventory_names), 19) - self.assertEqual(len(m33_unique_inventory_names), 16) - self.assertIn( - "test_training_readmes_name_dashboard_privacy_and_offline_registry_coverage", - documentation_consistency_test_names, - ) - for surface, names in TRAINING_M33_PACKAGE_FIXTURE_IMPORT_EXPORT_PRIVACY_INVENTORY.items(): - with self.subTest(m33_surface=surface): - self.assertTrue(names) - for name in names: - self.assertIn(name, m33_available_test_names) - - def test_public_package_smoke_docs_lock_import_export_privacy_language(self) -> None: - help_text = training_cli._build_parser().format_help() - - self.assertIn("--input", help_text) - self.assertIn("--output", help_text) - self.assertIn("public export", help_text.lower()) - - for path in PUBLIC_PACKAGE_DOCS: - text = _package_doc_text(path) - lower_text = text.lower() - - with self.subTest(path=path.name): - for snippet in PUBLIC_PACKAGE_REQUIRED_TEXT: - with self.subTest(snippet=snippet): - self.assertIn(snippet, text if snippet.startswith(".") or snippet.startswith("--") else lower_text) - for phrase in PRIVATE_DOC_DRIFT_PHRASES: - with self.subTest(phrase=phrase): - self.assertNotIn(phrase, lower_text) - self.assertNotIn(PUBLIC_FIELD, text) - - def test_cli_help_discloses_offline_public_import_export_and_error_contract(self) -> None: - parser = training_cli._build_parser() - help_text = parser.format_help() - lower_help = help_text.lower() - stdout = io.StringIO() - stderr = io.StringIO() - - with ( - contextlib.redirect_stdout(stdout), - contextlib.redirect_stderr(stderr), - self.assertRaises(SystemExit) as raised, + def test_example_doc_publishes_current_public_smoke_wording(self) -> None: + text = DOC_EXAMPLE.read_text(encoding="utf-8") + for snippet in ( + "import/export smoke", + "public package", + "public export", + "public output", + "one-line JSON", + "input_file_not_found", + "input_path_is_directory", + "output_path_is_directory", + "output_path_unwritable", ): - training_cli.main(["--help"]) + self.assertIn(snippet, text) - self.assertEqual(raised.exception.code, 0) - self.assertEqual(stderr.getvalue(), "") - self.assertEqual(stdout.getvalue(), help_text) - self.assertIn(PACKAGE_SMOKE_SCRIPT, help_text) - self.assertIn(FIXTURE_REF, help_text) - self.assertNotIn(PUBLIC_FIELD, help_text) - for snippet in PACKAGE_CLI_HELP_DISCOVERY_TEXT: - with self.subTest(snippet=snippet): - self.assertIn(snippet, lower_help) + def test_training_public_doc_references_console_and_api_surface(self) -> None: + text = TRAINING_PUBLIC_DOC.read_text(encoding="utf-8") + for snippet in ( + "The public browser entry is `/training`.", + "The trading workbench stays at `/`.", + "| GET | `/training/cases` |", + "| GET | `/training/progress` |", + "Training public error catalog:", + PACKAGE_SMOKE_COMMAND, + ): + self.assertIn(snippet, text) - def test_package_smoke_docs_publish_help_and_stable_public_error_wording(self) -> None: - for path in PACKAGE_SMOKE_SURFACE_DOCS: - text = path.read_text(encoding="utf-8") - lower_text = text.lower() + def test_readme_points_to_current_offline_checks_and_training_console(self) -> None: + text = README.read_text(encoding="utf-8") + for snippet in ( + OFFLINE_CHECK_COMMAND, + "Training Console at `/training`", + "training_console_js_syntax", + "python3 scripts/run_api_server.py", + ): + self.assertIn(snippet, text) + self.assertNotIn("training_case_engine_m1_m6_product_readme.md", text) - with self.subTest(path=path.name): - self.assertIn("invalid output targets", lower_text) - self.assertNotIn(PUBLIC_FIELD, text) - for snippet in PACKAGE_DOC_HELP_ERROR_TEXT: - with self.subTest(snippet=snippet): - self.assertIn(snippet, text) + def test_offline_checks_registry_includes_both_web_entries(self) -> None: + commands = dict(run_offline_checks.build_check_commands(env={})) + self.assertEqual(commands["dashboard_js_syntax"], ["node", "--check", "src/web/app.js"]) + self.assertEqual(commands["training_console_js_syntax"], ["node", "--check", "src/web/training.js"]) - def test_package_cli_public_error_inventory_locks_docs_help_and_readiness(self) -> None: - test_names = set(unittest.TestLoader().getTestCaseNames(type(self))) - inventory_names = _package_cli_public_error_inventory_method_names() - doc_snippets = _package_cli_public_error_doc_snippets() - help_text = training_cli._build_parser().format_help() - source_text = Path(training_cli.__file__).read_text(encoding="utf-8") - - self.assertEqual( - sorted(PACKAGE_CLI_PUBLIC_ERROR_TEST_INVENTORY), - ["docs", "help", "readiness"], - ) - self.assertEqual(len(inventory_names), 3) - self.assertEqual( - set(inventory_names), - { - "test_cli_error_summary_uses_public_output_target_code_without_runtime_leaks", - "test_cli_help_discloses_offline_public_import_export_and_error_contract", - "test_package_smoke_docs_publish_help_and_stable_public_error_wording", - }, - ) - for surface, names in PACKAGE_CLI_PUBLIC_ERROR_TEST_INVENTORY.items(): - with self.subTest(surface=surface): - self.assertTrue(names) - for name in names: - self.assertIn(name, test_names) - - self.assertEqual( - PACKAGE_CLI_PUBLIC_ERROR_CODES, - ( - "input_file_not_found", - "input_path_is_directory", - "output_path_is_directory", - "output_path_unwritable", - ), - ) - self.assertEqual( - PACKAGE_CLI_PUBLIC_ERROR_EXAMPLE_CODES, - PACKAGE_CLI_PUBLIC_ERROR_CODES[:3], - ) - self.assertEqual(doc_snippets, tuple(f"`{code}`" for code in PACKAGE_CLI_PUBLIC_ERROR_CODES)) - for code, snippet in zip(PACKAGE_CLI_PUBLIC_ERROR_CODES, doc_snippets): - with self.subTest(code=code): - self.assertIn(code, source_text) - self.assertIn(code, help_text) - self.assertIn(code, PACKAGE_CLI_HELP_DISCOVERY_TEXT) - self.assertIn(snippet, PACKAGE_DOC_HELP_ERROR_TEXT) - - for path in PACKAGE_SMOKE_SURFACE_DOCS: - text = path.read_text(encoding="utf-8") - with self.subTest(path=path.name): - for snippet in doc_snippets: - self.assertIn(snippet, text) - - for path in (README, PRODUCT_README): - payloads = _package_cli_error_example_payloads(path) - self.assertEqual( - tuple(payload["error"] for payload in payloads), - PACKAGE_CLI_PUBLIC_ERROR_EXAMPLE_CODES, - ) - for payload in payloads: - with self.subTest(path=path.name, code=payload["error"]): - self.assertEqual(list(payload), ["passed", "input", "output", "error"]) - self.assertFalse(payload["passed"]) - - with tempfile.TemporaryDirectory(dir=ROOT) as tmpdir: - temp_root = Path(tmpdir) - missing_input = temp_root / "missing_training_case_package.json" - input_dir = temp_root / "training_case_package_dir" - input_dir.mkdir() - output_dir = temp_root / "training_case_public_export_dir" - output_dir.mkdir() - runtime_cases = ( - ( - "input_file_not_found", - [ - "--input", - str(missing_input.relative_to(ROOT)), - "--output", - "training_case_public_export.json", - ], - ), - ( - "input_path_is_directory", - [ - "--input", - str(input_dir.relative_to(ROOT)), - "--output", - "training_case_public_export.json", - ], - ), - ( - "output_path_is_directory", - [ - "--input", - str(FIXTURE.relative_to(ROOT)), - "--output", - str(output_dir.relative_to(ROOT)), - ], - ), - ) - - for code, args in runtime_cases: - exit_code, raw_output, summary, stderr_text = _run_package_cli_summary(args) - with self.subTest(runtime_code=code): - self.assertEqual(exit_code, 2) - self.assertEqual(stderr_text, "") - self.assertEqual(raw_output.count("\n"), 1) - self.assertEqual(list(summary), ["passed", "input", "output", "error"]) - self.assertFalse(summary["passed"]) - self.assertEqual(summary["error"], code) - self.assertIsNone(re.search(r'(? None: - product_text = PRODUCT_README.read_text(encoding="utf-8") - cli_flags = _package_smoke_flags_from_source() - - self.assertIn(CURRENT_TRAINING_DOC_MATURITY_MARKER, product_text) - self.assertEqual(cli_flags, ("--input", "--output")) - - for path in PACKAGE_SMOKE_SURFACE_DOCS: - text = _package_doc_text(path) - lower_text = text.lower() - - with self.subTest(path=path.name): - self.assertIn(PACKAGE_SMOKE_SCRIPT, text) - self.assertIn(FIXTURE_REF, text) - for flag in cli_flags: - with self.subTest(flag=flag): - self.assertIn(flag, text) - for snippet in PACKAGE_IMPORT_EXPORT_SURFACE_TEXT: - with self.subTest(snippet=snippet): - self.assertIn(snippet, lower_text) - - def test_offline_check_registry_name_matches_package_smoke_docs(self) -> None: - commands = run_offline_checks.build_check_commands(env={}) - names = tuple(name for name, _command in commands) - commands_by_name = dict(commands) - matching_commands = [ - (name, command) for name, command in commands if name == OFFLINE_CHECK_NAME - ] - registry_text = Path(run_offline_checks.__file__).read_text(encoding="utf-8") - - self.assertEqual(names, OFFLINE_CHECK_REGISTRY_NAMES) - for check_name in OFFLINE_CHECK_REGISTRY_NAMES: - with self.subTest(check_name=check_name): - self.assertEqual(names.count(check_name), 1) - self.assertEqual( - matching_commands, - [(OFFLINE_CHECK_NAME, [sys.executable, PACKAGE_SMOKE_SCRIPT])], - ) - self.assertEqual( - commands_by_name[OFFLINE_CHECK_NAME], - [sys.executable, PACKAGE_SMOKE_SCRIPT], - ) - self.assertEqual( - commands_by_name[DASHBOARD_SYNTAX_CHECK_NAME], - ["node", "--check", DASHBOARD_SYNTAX_CHECK_PATH], - ) - self.assertEqual(registry_text.count(f'"{OFFLINE_CHECK_NAME}"'), 1) - self.assertIn(f'"{PACKAGE_SMOKE_SCRIPT}"', registry_text) - - for path in (README, DOC_EXAMPLE, PRODUCT_README): - text = path.read_text(encoding="utf-8") - lower_text = text.lower() - - with self.subTest(path=path.name): - self.assertIn(OFFLINE_CHECK_DOC_NAME, text) - self.assertIn(PACKAGE_SMOKE_COMMAND, text) - self.assertIn(OFFLINE_CHECK_COMMAND, text) - self.assertIn("package smoke", lower_text) - - def test_offline_checks_registry_runs_training_package_smoke_entry(self) -> None: - commands = run_offline_checks.build_check_commands(env={}) - names = [name for name, _command in commands] - command = dict(commands)[OFFLINE_CHECK_NAME] - calls = [] - - def fake_runner(command_parts, **kwargs): - calls.append((command_parts, kwargs["cwd"])) - return SimpleNamespace(returncode=0, stdout="", stderr="") + def test_offline_checks_json_and_node_skip_behavior_cover_new_training_check(self) -> None: + def fake_runner(command, **_kwargs): + return SimpleNamespace(returncode=0, stdout="ok\n", stderr="") output = io.StringIO() with contextlib.redirect_stdout(output): exit_code = run_offline_checks.run_checks( - commands=[(OFFLINE_CHECK_NAME, command)], + commands=[("training_console_js_syntax", ["node", "--check", "src/web/training.js"])], runner=fake_runner, - node_available=True, + node_available=False, env={}, ) - self.assertEqual(names.count(OFFLINE_CHECK_NAME), 1) - self.assertEqual(command, [sys.executable, PACKAGE_SMOKE_SCRIPT]) - self.assertEqual((ROOT / command[1]).resolve(), Path(training_cli.__file__).resolve()) self.assertEqual(exit_code, 0) - self.assertEqual(calls, [(command, run_offline_checks.ROOT)]) - self.assertIn(f"Offline check passed: {OFFLINE_CHECK_NAME}", output.getvalue()) + self.assertIn("Offline check skipped: training_console_js_syntax", output.getvalue()) - def test_training_docs_name_broader_offline_check_path(self) -> None: - for path in (DOC_EXAMPLE, README, PRODUCT_README): - text = path.read_text(encoding="utf-8") - lower_text = text.lower() + def test_package_cli_still_emits_one_line_public_json(self) -> None: + output = io.StringIO() + with contextlib.redirect_stdout(output): + exit_code = training_cli.main(["--input", str(FIXTURE)]) - with self.subTest(path=path.name): - self.assertIn(OFFLINE_CHECK_COMMAND, text) - self.assertIn("broader offline", lower_text) - self.assertIn("training case", lower_text) + summary = json.loads(output.getvalue()) + self.assertEqual(exit_code, 0) + self.assertTrue(summary["passed"]) + self.assertTrue(str(summary["input"]).endswith("tests\\fixtures\\training_case_package_m6.json")) + self.assertIsNone(summary["output"]) if __name__ == "__main__": diff --git a/tests/unit/test_training_public_api_contract_m8.py b/tests/unit/test_training_public_api_contract_m8.py index 230671d..6bf8047 100644 --- a/tests/unit/test_training_public_api_contract_m8.py +++ b/tests/unit/test_training_public_api_contract_m8.py @@ -1,18 +1,21 @@ import ast import io import json +import os import re import unittest from pathlib import Path from unittest import mock +os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test") + from src.api import server from src.api import training as training_api from src.services.training import TrainingCaseService ROOT = Path(__file__).resolve().parents[2] -TRAINING_PRODUCT_DOC = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md" +TRAINING_PRODUCT_DOC = ROOT / "docs" / "training_console_public_surface_v1.md" INTERNAL_FIELD = "answer" + "_key" PRIVATE_MARKER = "trainer-only-choice" PRIVATE_RUBRIC_MARKER = "trainer-only-rubric"