from __future__ import annotations import json import tempfile import unittest from pathlib import Path from app.services.engine_service import load_engine, normalize_cdsl_for_engine, validate_cdsl from app.settings import get_settings class ProfileSchemaTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.settings = get_settings() cls.engine = load_engine(cls.settings) cls.schema = json.loads((cls.settings.engine_root / "profile_schema.json").read_text(encoding="utf-8")) cls.cdsl_schema = json.loads( (cls.settings.engine_root / cls.schema["cdsl_json_schema_file"]).read_text(encoding="utf-8") ) def test_schema_and_registered_profiles_stay_in_sync(self) -> None: self.assertEqual(set(self.schema["runtime_supported_profiles"]), set(self.engine.SHAPE_GENERATORS)) def test_legacy_profile_macros_require_explicit_adapter_lowering(self) -> None: legacy = { "schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "legacy-rectangle", "meta": {"unit": "mm"}, "geometry": {"sketches": [{ "id": "base", "workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 10, "height_mm": 10}, }]}, "features": [{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "base", "params": {"distance_mm": 2}}], } with self.assertRaisesRegex(ValueError, "CDSL schema violation"): validate_cdsl(legacy, self.engine) from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles lowered = lower_legacy_profiles(legacy) self.assertEqual(lowered["geometry"]["sketches"][0]["profile"]["type"], "analytic_contours") validate_cdsl(lowered, self.engine) def test_schema_and_executable_atomic_operations_stay_in_sync(self) -> None: self.assertEqual(set(self.schema["runtime_supported_atomic_ids"]), set(self.engine.SUPPORTED_ATOMIC_IDS)) self.assertEqual(set(self.engine.SUPPORTED_ATOMIC_IDS), set(self.schema["feature_atomic_ids"])) self.assertEqual( set(self.cdsl_schema["$defs"]["feature_atomic_ids"]["enum"]), set(self.engine.SUPPORTED_ATOMIC_IDS), ) def test_machine_schema_and_human_contract_stay_in_sync(self) -> None: self.assertEqual( set(self.cdsl_schema["$defs"]["profile_type"]["enum"]), set(self.schema["runtime_supported_profiles"]), ) self.assertEqual(set(self.schema["profiles"]), set(self.schema["runtime_supported_profiles"])) def test_rejects_unsupported_atomic_operation_before_rebuild(self) -> None: cdsl = { "schema": "cad.cdsl.llm.v1", "part_id": "invalid-extrude", "features": [{ "id": "f01", "atomic_id": "extrude", "depends_on": [], "params": {"depth_mm": 10}, "sketch_id": "s01", }], "geometry": {"sketches": [{ "id": "s01", "workplane": {}, "profile": {"type": "circle", "radius_mm": 5}, }]}, } with self.assertRaisesRegex(ValueError, "features\\[0\\]\\.atomic_id"): validate_cdsl(cdsl, self.engine) def test_semantic_validator_preserves_deferred_features_but_runtime_checks_current_capabilities(self) -> None: cdsl = { "schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "part_id": "deferred-fillet", "meta": {"unit": "mm"}, "features": [{ "id": "f01", "atomic_id": "fillet", "depends_on": [], "params": {"radius_mm": 1}, "execution_status": "deferred", }], "geometry": {"sketches": [{ "id": "s01", "workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, "profile": {"type": "circle", "radius_mm": 5}, }]}, } result = self.engine.validate_semantic_cdsl(cdsl) self.assertEqual(result["deferred_feature_ids"], ["f01"]) with self.assertRaisesRegex(ValueError, "missing_selector"): validate_cdsl(cdsl, self.engine) def test_rejects_bare_hole_coordinate_arrays_before_rebuild(self) -> None: cdsl = { "schema": "cad.cdsl.llm.v1", "part_id": "invalid-hole-position", "features": [{ "id": "f01", "atomic_id": "hole_blind", "depends_on": [], "sketch_id": "s01", "params": {"diameter_mm": 6, "depth_mm": 10, "positions": [[0, 0]]}, }], "geometry": {"sketches": [{ "id": "s01", "workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, "profile": {"type": "circle", "radius_mm": 20}, }]}, } with self.assertRaisesRegex(ValueError, "positions\\[0\\].*not of type 'object'"): validate_cdsl(cdsl, self.engine) def test_normalizes_unambiguous_legacy_llm_field_names_before_validation(self) -> None: legacy_cdsl = { "schema": "cad.cdsl.llm.v1", "part_id": "legacy-flange-base", "geometry": {"sketches": [{ "id": "base_sketch", "plane": "XY", "offset_mm": 12, "profile": {"type": "circle", "radius_mm": 20}, }]}, "features": [{ "id": "base_add", "atomic_id": "extrude_add_blind", "sketch": "base_sketch", "params": {"distance_mm": 8}, }], } normalized, repairs = normalize_cdsl_for_engine(legacy_cdsl) self.assertEqual(legacy_cdsl["geometry"]["sketches"][0]["plane"], "XY") self.assertEqual(normalized["features"][0]["sketch_id"], "base_sketch") self.assertNotIn("sketch", normalized["features"][0]) self.assertEqual(normalized["features"][0]["depends_on"], []) self.assertEqual(normalized["geometry"]["sketches"][0]["workplane"], { "origin_mm": [0.0, 0.0, 12.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0], }) self.assertEqual(len(repairs), 3) validate_cdsl(normalized, self.engine) def test_normalizer_rewrites_the_legacy_revolve_axis_point_name(self) -> None: normalized, repairs = normalize_cdsl_for_engine({ "features": [{ "id": "turn", "atomic_id": "revolve_add", "params": {"axis": {"point_mm": [0, 0, 0], "direction": [0, 0, 1]}}, }], }) axis = normalized["features"][0]["params"]["axis"] self.assertEqual(axis["origin_mm"], [0, 0, 0]) self.assertNotIn("point_mm", axis) self.assertIn("features[0].params.axis: point_mm -> origin_mm", repairs) def test_cdsl_only_rebuild_preserves_its_actual_failure(self) -> None: cdsl = { "schema": "cad.cdsl.llm.v1", "part_id": "invalid-extrude", "features": [{ "id": "f01", "atomic_id": "extrude", "depends_on": [], "params": {"depth_mm": 10}, "sketch_id": "s01", }], "geometry": {"sketches": [{ "id": "s01", "workplane": {}, "profile": {"type": "circle", "radius_mm": 5}, }]}, } with tempfile.TemporaryDirectory() as temporary_directory: out_step = Path(temporary_directory) / "model.step" with self.assertRaisesRegex(RuntimeError, "CDSL-only rebuild failed: unsupported atomic_id: extrude"): self.engine.run_rebuild(cdsl, out_step) def test_product_revision_path_uses_the_cdsl_only_entry_point(self) -> None: source = (Path(__file__).resolve().parents[2] / "backend" / "app" / "services" / "engine_service.py").read_text(encoding="utf-8") build_revision_source = source[source.index("def build_revision"):] self.assertIn("engine.run_cdsl_only(cdsl_copy, step_path)", build_revision_source) self.assertNotIn("engine.run_rebuild(cdsl_copy, step_path)", build_revision_source) def test_all_official_samples_match_the_engine_schema(self) -> None: from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles samples = sorted(self.settings.library_root.glob("samples/**/model.cdsl.json")) self.assertGreater(len(samples), 0) for sample_path in samples: with self.subTest(sample=sample_path.parent.name): legacy_sample = json.loads(sample_path.read_text(encoding="utf-8")) lowered = lower_legacy_profiles(legacy_sample) validate_cdsl(lowered, self.engine) self.assertTrue(self.engine.analyze_cdsl(lowered).runtime_eligible) if __name__ == "__main__": unittest.main()