from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from cmvr_edge_ai.capabilities import ( CapabilityDeploymentStatus, CapabilityRegistry, CapabilityRegistryError, CapabilitySpec, DuplicateCapabilityError, ServingMode, UnknownCapabilityError, ) def _factory(params: dict[str, Any]) -> dict[str, Any]: return dict(params) def _spec( model_id: str = "mobile-phone@1", *, category: str = "detect.mobile_phone", serving_modes: tuple[ServingMode, ...] = (ServingMode.PASSIVE_INVOKE,), ) -> CapabilitySpec: return CapabilitySpec( category=category, family="detect", model_id=model_id, serving_modes=serving_modes, input_kinds=("image",), output_kinds=("detections",), artifact_roles=("annotated",), parameters_schema={ "type": "object", "properties": {"confidence": {"type": "number"}}, }, factory=_factory, name="Mobile phone detector", backend="onnxruntime", ) def test_capability_spec_normalizes_and_defensively_copies_metadata() -> None: schema: dict[str, Any] = { "type": "object", "required": ["confidence"], } spec = CapabilitySpec( category="detect.analog_gauge", family="detect", model_id="analog-gauge-reader@1", serving_modes=("active_push", "passive_invoke"), input_kinds=("image",), output_kinds=("scalar", "detections"), artifact_roles=("annotated", "debug"), parameters_schema=schema, factory=_factory, name=" Analog gauge reader ", backend=" isolated-worker ", ) schema["required"].append("unexpected") assert spec.serving_modes == ( ServingMode.ACTIVE_PUSH, ServingMode.PASSIVE_INVOKE, ) assert spec.name == "Analog gauge reader" assert spec.backend == "isolated-worker" assert spec.parameters_schema["required"] == ["confidence"] @pytest.mark.parametrize( ("overrides", "exception", "message"), [ ({"model_id": "mobile-phone"}, ValueError, "versioned"), ({"category": "talk.asr"}, ValueError, "must belong to family"), ({"category": "Detect.Phone"}, ValueError, "lowercase dotted"), ({"serving_modes": ()}, ValueError, "must not be empty"), ( {"serving_modes": ("passive_invoke", "passive_invoke")}, ValueError, "duplicates", ), ({"input_kinds": "image"}, TypeError, "ordered collection"), ({"output_kinds": ()}, ValueError, "must not be empty"), ({"factory": None}, TypeError, "factory must be callable"), ( {"parameters_schema": {"default": object()}}, ValueError, "only JSON values", ), ], ) def test_capability_spec_rejects_invalid_metadata( overrides: dict[str, Any], exception: type[Exception], message: str ) -> None: values: dict[str, Any] = { "category": "detect.mobile_phone", "family": "detect", "model_id": "mobile-phone@1", "serving_modes": ("passive_invoke",), "input_kinds": ("image",), "output_kinds": ("detections",), "factory": _factory, } values.update(overrides) with pytest.raises(exception, match=message): CapabilitySpec(**values) def test_registry_registers_resolves_filters_and_creates() -> None: registry = CapabilityRegistry() phone = registry.register(_spec("phone@1")) gauge = registry.register( _spec( "gauge@2", category="detect.analog_gauge", serving_modes=(ServingMode.ACTIVE_PUSH, ServingMode.PASSIVE_INVOKE), ) ) assert registry.resolve("phone@1") is phone assert registry.specs() == (gauge, phone) assert registry.specs_for_category( "detect.analog_gauge", serving_mode="active_push" ) == (gauge,) assert registry.create("phone@1", {"confidence": 0.6}) == {"confidence": 0.6} def test_registry_rejects_duplicates_and_reports_available_capabilities() -> None: registry = CapabilityRegistry() registry.register(_spec("phone@1")) with pytest.raises(DuplicateCapabilityError, match="phone@1"): registry.register(_spec("phone@1")) with pytest.raises( UnknownCapabilityError, match=r"available capabilities: phone@1" ): registry.resolve("missing@1") def test_catalog_groups_modes_and_keeps_registration_and_deployment_separate() -> None: registry = CapabilityRegistry() dual = registry.register( _spec( "gauge@1", category="detect.analog_gauge", serving_modes=(ServingMode.ACTIVE_PUSH, ServingMode.PASSIVE_INVOKE), ) ) passive = registry.register(_spec("phone@1")) catalog = registry.build_catalog( { dual.model_id: CapabilityDeploymentStatus.READY, passive.model_id: CapabilityDeploymentStatus.DISABLED, } ) assert [item.model_id for item in catalog.active_push] == ["gauge@1"] assert [item.model_id for item in catalog.passive_invoke] == [ "gauge@1", "phone@1", ] assert catalog.active_push[0].registration_status.value == "registered" assert ( catalog.active_push[0].deployment_status is CapabilityDeploymentStatus.READY ) assert ( catalog.passive_invoke[1].deployment_status is CapabilityDeploymentStatus.DISABLED ) assert catalog.model_dump(mode="json")["schema_version"] == ( "cmvr.model-catalog/v1" ) with pytest.raises(UnknownCapabilityError, match="unknown capability"): registry.build_catalog({"missing@1": CapabilityDeploymentStatus.READY}) @dataclass class _EntryPoint: name: str value: Any def load(self) -> Any: return self.value class _EntryPoints(list[_EntryPoint]): def select(self, *, group: str) -> _EntryPoints: assert group == CapabilityRegistry.ENTRY_POINT_GROUP return self def test_registry_discovers_specs_and_callbacks( monkeypatch, ) -> None: # type: ignore[no-untyped-def] def callback(registry: CapabilityRegistry) -> None: registry.register(_spec("callback@1")) monkeypatch.setattr( "cmvr_edge_ai.capabilities.registry.metadata.entry_points", lambda: _EntryPoints( [ _EntryPoint("spec", _spec("spec@1")), _EntryPoint("callback", callback), ] ), ) registry = CapabilityRegistry() registry.load_entry_points() assert [spec.model_id for spec in registry.specs()] == ["callback@1", "spec@1"] def test_registry_rejects_invalid_entry_point( monkeypatch, ) -> None: # type: ignore[no-untyped-def] monkeypatch.setattr( "cmvr_edge_ai.capabilities.registry.metadata.entry_points", lambda: _EntryPoints([_EntryPoint("invalid", object())]), ) with pytest.raises(CapabilityRegistryError, match="invalid"): CapabilityRegistry().load_entry_points()