Initial project import

This commit is contained in:
2026-08-14 09:40:51 +03:00
parent 00040e5ce4
commit d7099bf80d
146 changed files with 30509 additions and 1055 deletions
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Controlled public smoke for extension ConfigCASSave preparation.
The smoke uses no storage coordinates. It creates one extension saved-state
copy, verifies readback, rolls it back by opaque receipt, then proves that the
same public selector is immediately ready for another prepare.
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
def rpc(base_url: str, method: str, payload: dict, timeout: float) -> dict:
request = Request(
base_url.rstrip("/") + "/rpc",
data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"),
method="POST",
headers={"Content-Type": "application/json; charset=utf-8"},
)
try:
with urlopen(request, timeout=timeout) as response:
result = json.loads(response.read().decode("utf-8"))
except (HTTPError, URLError) as exc:
raise AssertionError(f"{method} transport failure: {exc}") from exc
if not isinstance(result, dict):
raise AssertionError(f"{method} returned a non-object response")
return result
def require(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def main() -> int:
parser = argparse.ArgumentParser(description="Public extension saved-state prepare/rollback smoke.")
parser.add_argument("--base-url", default="http://docker.cin.su:8011")
parser.add_argument("--base-id", default="upo_test")
parser.add_argument("--extension", default="фс_ДоработкиОбщее")
parser.add_argument("--ref", default="Catalog.Номенклатура")
parser.add_argument("--timeout", type=float, default=120.0)
parser.add_argument("--report", type=Path)
parser.add_argument("--apply", action="store_true", help="Perform the controlled SQL prepare and rollback.")
args = parser.parse_args()
report: dict = {
"schema": "onec_extension_saved_state_prepare_smoke.v1",
"base_url": args.base_url,
"base_id": args.base_id,
"extension": args.extension,
"ref": args.ref,
"status": "pending",
"passed": False,
}
target = {
"base_id": args.base_id,
"extension": args.extension,
"ref": args.ref,
"layer": "extension_saved_state",
}
try:
initial = rpc(args.base_url, "metadata.saved_state.prepare", target | {"mode": "plan"}, args.timeout)
report["initial_plan"] = {"status": initial.get("status"), "counts": initial.get("counts")}
require(initial.get("status") == "plan_ready", f"initial plan must be plan_ready, got {initial.get('status')}")
if not args.apply:
report["status"] = "plan_ready"
report["passed"] = True
else:
applied = rpc(
args.base_url,
"metadata.saved_state.prepare",
target | {"mode": "apply_and_verify", "allow_sql_saved_state_prepare": True},
args.timeout,
)
report["prepare"] = {"status": applied.get("status"), "counts": applied.get("counts"), "verification": applied.get("verification")}
require(applied.get("status") == "verified" and applied.get("applied") is True, "prepare must be verified")
receipt_id = str(applied.get("prepare_receipt_id") or "")
require(receipt_id, "prepare response must include an opaque receipt")
rolled_back = rpc(
args.base_url,
"metadata.saved_state.ensure.rollback",
{"base_id": args.base_id, "prepare_receipt_id": receipt_id, "allow_sql_saved_state_rollback": True},
args.timeout,
)
report["rollback"] = {"status": rolled_back.get("status"), "counts": rolled_back.get("counts")}
require(rolled_back.get("status") == "rolled_back" and rolled_back.get("applied") is True, "prepare rollback must succeed")
started = time.monotonic()
final_plan = rpc(args.base_url, "metadata.saved_state.prepare", target | {"mode": "plan"}, args.timeout)
elapsed_ms = round((time.monotonic() - started) * 1000, 1)
report["post_rollback_plan"] = {"status": final_plan.get("status"), "counts": final_plan.get("counts"), "elapsed_ms": elapsed_ms}
require(final_plan.get("status") == "plan_ready", f"post-rollback plan must be plan_ready, got {final_plan.get('status')}")
require(int((final_plan.get("counts") or {}).get("existing_saved_records") or 0) == 0, "rollback must leave no saved records")
report["status"] = "verified_and_rolled_back"
report["passed"] = True
except Exception as exc:
report["status"] = "failed"
report["error"] = str(exc)
if args.report:
args.report.parent.mkdir(parents=True, exist_ok=True)
args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0 if report["passed"] else 1
if __name__ == "__main__":
sys.exit(main())