| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- """P4-M1 regression: single-source plan schema (src.plan_schema) + web wiring.
- Run: python scripts/test_p4_schema.py (exit 0 = PASS)
- Covers:
- 1. src.plan_schema.parse_plan / validate_plan_dict:
- - happy path (values + start/stop/step vars, topology, strategy)
- - alias tolerance (min_value/max_value -> start/stop)
- - empty / zero-value inputs
- - malformed input (non-dict, bad structure) -> structured error
- - require_model_path stage (draft vs execution)
- - point generation consistency
- 2. Web wiring: POST /api/plans rejects invalid plan_data (400), accepts
- valid draft (201); PUT rejects invalid plan_data (400). Uses FastAPI
- TestClient on an isolated temp SQLite DB. No real Motor-CAD.
- """
- import os
- import sys
- import tempfile
- _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- _BACKEND = os.path.join(_ROOT, "web", "backend")
- _DB = os.path.join(tempfile.mkdtemp(prefix="p4schema_"), "web.db")
- os.environ["AFM_DB_PATH"] = _DB
- os.environ["KIMI_API_KEY"] = "" # hermetic: no AI calls
- sys.path.insert(0, _BACKEND)
- sys.path.insert(0, _ROOT)
- from src.plan_schema import ( # noqa: E402
- parse_plan, validate_plan_dict, SimulationPlan,
- )
- VALID_VARS = [
- {"name": "airgap_mm", "display_name": "Airgap", "unit": "mm",
- "start": 0.8, "stop": 2.0, "step": 0.1},
- {"name": "Magnet_Arc_[ED]", "values": [120.0, 130.0, 140.0]},
- ]
- VALID_FPS = [{"name": "Outer_Rotor_Diameter", "value": 200.0,
- "category": "Geometry"}]
- def make_plan(**over):
- d = {
- "plan_version": "2.0",
- "topology": "SSSR",
- "model_path": "models/MARS-12S10P_SSSR_D76-C150_V5.0-0819.mot",
- "fixed_params": VALID_FPS,
- "variables": VALID_VARS,
- "cases": [{"id": "default", "name": "Default"}],
- "search_strategy": {"method": "adaptive", "batch_size": 4,
- "max_solver_calls": 80},
- "acceptance_criteria": {"hard_constraints": ["efficiency_pct >= 92"]},
- }
- d.update(over)
- return d
- # ---------------- 1) happy path + point generation ----------------
- p = parse_plan(make_plan())
- assert p.topology == "SSSR"
- assert len(p.variables) == 2
- assert p.variables[0].get_values()[0] == 0.8
- assert len(p.variables[0].get_values()) == 13 # 0.8..2.0 step 0.1
- ok, errs = validate_plan_dict(make_plan())
- assert ok, errs
- pts, names = p.generate_points()
- assert len(pts) == 13 * 3 and set(names) == {"airgap_mm", "Magnet_Arc_[ED]"}
- print("[1] happy path + point generation OK (39 points)")
- # ---------------- 2) alias tolerance ----------------
- p2 = parse_plan(make_plan(variables=[
- {"name": "airgap_mm", "min_value": 0.8, "max_value": 2.0, "step": 0.1}]))
- assert p2.variables[0].get_values()[0] == 0.8, p2.variables[0].get_values()
- print("[2] min_value/max_value alias -> start/stop OK")
- # ---------------- 3) empty / zero-value inputs ----------------
- ok3, errs3 = validate_plan_dict(make_plan(variables=[]))
- assert not ok3 and any("scan variable" in e for e in errs3), errs3
- ok3b, errs3b = validate_plan_dict({})
- assert not ok3b, errs3b
- ok3c, errs3c = validate_plan_dict(make_plan(variables=[
- {"name": "x", "start": 5.0, "stop": 1.0, "step": 0.5}]))
- assert not ok3c and any("no values" in e for e in errs3c), errs3c
- print("[3] empty / zero-value inputs rejected OK")
- # ---------------- 4) malformed input ----------------
- ok4, errs4 = validate_plan_dict("not-a-dict")
- assert not ok4 and any("malformed" in e for e in errs4), errs4
- ok4b, errs4b = validate_plan_dict(make_plan(variables=[{"no_name": 1}]))
- assert not ok4b, errs4b # KeyError on name -> malformed
- print("[4] malformed input -> structured error OK")
- # ---------------- 5) require_model_path stage ----------------
- ok5, _ = validate_plan_dict(
- make_plan(model_path=""), require_model_path=False)
- assert ok5, "draft stage must not require a model"
- ok5b, errs5b = validate_plan_dict(make_plan(model_path=""))
- assert not ok5b and any("model_path" in e for e in errs5b)
- print("[5] require_model_path stage OK")
- # ---------------- 6) unsupported topology / strategy ----------------
- ok6, errs6 = validate_plan_dict(make_plan(topology="NOPE"))
- assert not ok6 and any("topology" in e for e in errs6), errs6
- ok6b, errs6b = validate_plan_dict(
- make_plan(search_strategy={"method": "no_such_method"}))
- assert not ok6b and any("strategy" in e for e in errs6b), errs6b
- print("[6] unsupported topology/strategy rejected OK")
- # ---------------- 7) web wiring (TestClient) ----------------
- from fastapi.testclient import TestClient # noqa: E402
- from app.database import init_db as web_init_db # noqa: E402
- web_init_db()
- from app.main import app # noqa: E402
- client = TestClient(app)
- # create project
- r = client.post("/api/projects", json={"name": "t", "topology": "SSSR"})
- assert r.status_code == 201, r.text
- pid = r.json()["id"]
- # invalid plan_data -> 400
- bad = make_plan(variables=[])
- r = client.post("/api/plans", json={"project_id": pid, "name": "bad",
- "plan_data": bad})
- assert r.status_code == 400, r.status_code
- assert "Invalid plan_data" in r.json()["detail"], r.text
- # valid draft (no model path) -> 201
- good = make_plan(model_path="")
- r = client.post("/api/plans", json={"project_id": pid, "name": "good",
- "plan_data": good})
- assert r.status_code == 201, r.text
- plan_id = r.json()["id"]
- # invalid update -> 400
- r = client.put("/api/plans/%d" % plan_id,
- json={"plan_data": make_plan(variables=[])})
- assert r.status_code == 400, r.status_code
- # valid update -> 200
- r = client.put("/api/plans/%d" % plan_id,
- json={"plan_data": make_plan(model_path="")})
- assert r.status_code == 200, r.text
- # download round-trips through the same schema
- r = client.get("/api/plans/%d/download" % plan_id)
- assert r.status_code == 200
- assert r.json()["plan_data"]["variables"]
- print("[7] web create/update validation wired OK (400 on invalid)")
- print("\nALL P4-M1 SCHEMA TESTS PASSED")
|