69 lines
2.8 KiB
Python
69 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "backend"))
|
|
|
|
from app.services.feature_plan import FeaturePlanError, compute_node_statuses, validate_feature_plan # noqa: E402
|
|
|
|
|
|
class FeaturePlanTests(unittest.TestCase):
|
|
def plan(self) -> dict:
|
|
return {
|
|
"schema_version": "cad.feature-plan.v1",
|
|
"plan_id": "plan_test",
|
|
"task_id": "cad_test",
|
|
"nodes": [
|
|
{"id": "base", "atomic_id": "extrude_add_blind", "depends_on": []},
|
|
{"id": "round", "atomic_id": "fillet", "depends_on": ["base"], "requires_topology": True},
|
|
],
|
|
}
|
|
|
|
def test_rejects_cycles_and_missing_dependencies(self) -> None:
|
|
cyclic = self.plan()
|
|
cyclic["nodes"][0]["depends_on"] = ["round"]
|
|
with self.assertRaises(FeaturePlanError):
|
|
validate_feature_plan(cyclic, supported_atomic_ids={"extrude_add_blind", "fillet"})
|
|
missing = self.plan()
|
|
missing["nodes"][1]["depends_on"] = ["missing"]
|
|
with self.assertRaises(FeaturePlanError):
|
|
validate_feature_plan(missing, supported_atomic_ids={"extrude_add_blind", "fillet"})
|
|
|
|
def test_ready_prefix_waits_for_topology(self) -> None:
|
|
statuses = compute_node_statuses(self.plan(), cdsl={"features": []})
|
|
self.assertEqual(statuses["ready_nodes"], ["base"])
|
|
self.assertEqual(statuses["waiting_nodes"], [])
|
|
|
|
def test_built_prefix_unlocks_topology_dependent_node(self) -> None:
|
|
statuses = compute_node_statuses(
|
|
self.plan(),
|
|
cdsl={"features": [{"id": "base"}]},
|
|
topology={"records": [{"record_id": "body:base", "kind": "body", "executable": True}]},
|
|
)
|
|
self.assertEqual(statuses["completed_nodes"], ["base"])
|
|
self.assertEqual(statuses["waiting_nodes"], ["round"])
|
|
|
|
def test_inspected_snapshot_unlocks_topology_dependent_node(self) -> None:
|
|
plan = self.plan()
|
|
plan["topology_snapshot_id"] = "cad_test/rev_001"
|
|
statuses = compute_node_statuses(
|
|
plan,
|
|
cdsl={"features": [{"id": "base"}]},
|
|
topology={
|
|
"snapshot_id": "cad_test/rev_001",
|
|
"records": [{"record_id": "body:base", "kind": "body", "executable": True}],
|
|
},
|
|
)
|
|
|
|
self.assertEqual(statuses["ready_nodes"], ["round"])
|
|
self.assertEqual(statuses["waiting_nodes"], [])
|
|
|
|
def test_dependency_must_appear_before_dependent_node(self) -> None:
|
|
plan = self.plan()
|
|
plan["nodes"] = [plan["nodes"][1], plan["nodes"][0]]
|
|
with self.assertRaisesRegex(FeaturePlanError, "appear after dependency"):
|
|
validate_feature_plan(plan, supported_atomic_ids={"extrude_add_blind", "fillet"})
|