#!/usr/bin/env python3 # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc """Check native-sol migrated contracts against their Flask routes.""" from __future__ import annotations import sys from typing import Any from flask import Flask from solstone.convey.contract.assemble import build_document, rule_to_openapi_path try: from scripts.build_native_sol_inventory import REPO_ROOT, AuthorityEntry, discover from scripts.check_native_sol_conformance import register_native_blueprints except ModuleNotFoundError: # pragma: no cover - direct script execution path. from build_native_sol_inventory import ( # type: ignore[no-redef] REPO_ROOT, AuthorityEntry, discover, ) from check_native_sol_conformance import ( # type: ignore[no-redef] register_native_blueprints, ) def _route_app() -> Flask: app = Flask(__name__) register_native_blueprints(app) return app def _flask_routes(app: Flask) -> set[tuple[str, str]]: routes: set[tuple[str, str]] = set() for rule in app.url_map.iter_rules(): for method in sorted(rule.methods or ()): if method in {"HEAD", "OPTIONS"}: continue routes.add((method, rule_to_openapi_path(rule.rule))) return routes def _contract_operations( document: dict[str, Any], ) -> tuple[dict[tuple[str, str], str], list[str]]: operations: dict[tuple[str, str], str] = {} duplicate_errors: list[str] = [] seen_operation_ids: dict[str, tuple[str, str]] = {} for path, path_item in document["paths"].items(): for raw_method, operation in path_item.items(): method = raw_method.upper() operation_id = str(operation.get("operationId", "")) key = (method, path) operations[key] = operation_id if operation_id in seen_operation_ids: duplicate_errors.append( "duplicate contract operation_id " f"{operation_id}: {seen_operation_ids[operation_id]} and {key}" ) seen_operation_ids[operation_id] = key return operations, duplicate_errors def _expected_routes( authorities: list[AuthorityEntry] | None = None, ) -> tuple[dict[tuple[str, str], str], list[str]]: expected: dict[tuple[str, str], str] = {} errors: list[str] = [] for entry in authorities if authorities is not None else discover(REPO_ROOT): if entry.surface != "sol-call" or entry.entry_type != "http": continue if ( entry.method is None or entry.route is None or entry.contract_operation_id is None ): errors.append( f"{entry.operation_id}: HTTP authority is missing route fields" ) continue key = (entry.method, entry.route) existing = expected.get(key) if existing is not None and existing != entry.contract_operation_id: errors.append( f"{entry.operation_id}: duplicate native route {entry.method} " f"{entry.route} already bound to {existing}" ) continue expected[key] = entry.contract_operation_id return expected, errors def main() -> int: expected, errors = _expected_routes() expected_operation_ids = set(expected.values()) flask_routes = _flask_routes(_route_app()) contract_routes, contract_errors = _contract_operations(build_document()) errors.extend(contract_errors) for key, operation_id in sorted(expected.items()): if key not in flask_routes: errors.append(f"missing Flask route for {key[0]} {key[1]}") actual_operation_id = contract_routes.get(key) if actual_operation_id is None: errors.append( f"missing contract operation for {key[0]} {key[1]} " f"(expected {operation_id})" ) elif actual_operation_id != operation_id: errors.append( f"contract operation mismatch for {key[0]} {key[1]}: " f"expected {operation_id}, found {actual_operation_id}" ) for key, operation_id in sorted(contract_routes.items()): if operation_id not in expected_operation_ids: continue expected_key = next( expected_key for expected_key, expected_id in expected.items() if expected_id == operation_id ) if key != expected_key: errors.append( f"migrated contract operation {operation_id} is bound to " f"{key[0]} {key[1]}, expected {expected_key[0]} {expected_key[1]}" ) for key, operation_id in sorted(expected.items()): if key in flask_routes and key not in contract_routes: errors.append( f"migrated Flask route has no contract: {key[0]} {key[1]} " f"(expected {operation_id})" ) if errors: print("native sol contract-route coverage failed:", file=sys.stderr) for error in errors: print(f"- {error}", file=sys.stderr) return 1 print("native sol contract-route coverage ok") return 0 if __name__ == "__main__": raise SystemExit(main())