146 lines
4.1 KiB
Python
146 lines
4.1 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from pydantic import ValidationError
|
||
|
|
|
||
|
|
from cmvr_edge_ai.config import (
|
||
|
|
MissingEnvironmentVariable,
|
||
|
|
load_config_data,
|
||
|
|
load_config_text,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_load_config_expands_environment_at_every_level() -> None:
|
||
|
|
config = load_config_text(
|
||
|
|
"""
|
||
|
|
api_version: cmvr.edge.ai/v1
|
||
|
|
runtime:
|
||
|
|
thread_workers: ${EDGE_THREADS:-2}
|
||
|
|
endpoints:
|
||
|
|
robot:
|
||
|
|
transport: GRPC
|
||
|
|
target: env://CMVR_TARGET
|
||
|
|
platform:
|
||
|
|
transport: http
|
||
|
|
base_url: ${PLATFORM_URL:-http://127.0.0.1:8080}
|
||
|
|
pipelines:
|
||
|
|
detection:
|
||
|
|
nodes:
|
||
|
|
source:
|
||
|
|
uses: core.sequence_source@1
|
||
|
|
with:
|
||
|
|
items: [env://SAMPLE_ITEM]
|
||
|
|
""",
|
||
|
|
environ={
|
||
|
|
"CMVR_TARGET": "192.168.0.10:50052",
|
||
|
|
"EDGE_THREADS": "3",
|
||
|
|
"SAMPLE_ITEM": "frame-1",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
assert config.runtime.thread_workers == 3
|
||
|
|
assert config.endpoints["robot"].transport == "grpc"
|
||
|
|
assert config.endpoints["robot"].target == "192.168.0.10:50052"
|
||
|
|
assert config.endpoints["platform"].base_url == "http://127.0.0.1:8080"
|
||
|
|
assert config.pipelines["detection"].nodes["source"].params["items"] == ["frame-1"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_load_config_rejects_missing_required_environment_variable() -> None:
|
||
|
|
with pytest.raises(MissingEnvironmentVariable, match="CMVR_TARGET"):
|
||
|
|
load_config_text(
|
||
|
|
"""
|
||
|
|
api_version: cmvr.edge.ai/v1
|
||
|
|
endpoints:
|
||
|
|
robot:
|
||
|
|
transport: grpc
|
||
|
|
target: ${CMVR_TARGET}
|
||
|
|
pipelines:
|
||
|
|
smoke:
|
||
|
|
nodes:
|
||
|
|
source:
|
||
|
|
uses: core.sequence_source@1
|
||
|
|
""",
|
||
|
|
environ={},
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_config_rejects_edges_to_unknown_nodes() -> None:
|
||
|
|
with pytest.raises(ValidationError, match="references unknown node"):
|
||
|
|
load_config_data(
|
||
|
|
{
|
||
|
|
"api_version": "cmvr.edge.ai/v1",
|
||
|
|
"pipelines": {
|
||
|
|
"invalid": {
|
||
|
|
"nodes": {
|
||
|
|
"source": {"uses": "test.source@1"},
|
||
|
|
},
|
||
|
|
"edges": [{"from": "source.output", "to": "missing.input"}],
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_config_rejects_cycles_before_runtime_construction() -> None:
|
||
|
|
with pytest.raises(ValidationError, match="must be acyclic"):
|
||
|
|
load_config_data(
|
||
|
|
{
|
||
|
|
"api_version": "cmvr.edge.ai/v1",
|
||
|
|
"pipelines": {
|
||
|
|
"cyclic": {
|
||
|
|
"nodes": {
|
||
|
|
"first": {"uses": "test.operator@1"},
|
||
|
|
"second": {"uses": "test.operator@1"},
|
||
|
|
},
|
||
|
|
"edges": [
|
||
|
|
{"from": "first.output", "to": "second.input"},
|
||
|
|
{"from": "second.output", "to": "first.input"},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_config_models_forbid_unknown_fields() -> None:
|
||
|
|
with pytest.raises(ValidationError, match="extra_forbidden"):
|
||
|
|
load_config_data(
|
||
|
|
{
|
||
|
|
"api_version": "cmvr.edge.ai/v1",
|
||
|
|
"unexpected": True,
|
||
|
|
"pipelines": {
|
||
|
|
"smoke": {"nodes": {"source": {"uses": "test.source@1"}}}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
("endpoint", "message"),
|
||
|
|
[
|
||
|
|
({"transport": "grpc"}, "gRPC endpoint requires target"),
|
||
|
|
({"transport": "http"}, "HTTP endpoint requires base_url"),
|
||
|
|
(
|
||
|
|
{
|
||
|
|
"transport": "http",
|
||
|
|
"base_url": "https://platform.example",
|
||
|
|
"tls": False,
|
||
|
|
},
|
||
|
|
"HTTP endpoint must not set tls",
|
||
|
|
),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_builtin_endpoints_reject_incomplete_or_ambiguous_addresses(
|
||
|
|
endpoint: dict[str, object], message: str
|
||
|
|
) -> None:
|
||
|
|
with pytest.raises(ValidationError, match=message):
|
||
|
|
load_config_data(
|
||
|
|
{
|
||
|
|
"api_version": "cmvr.edge.ai/v1",
|
||
|
|
"endpoints": {"external": endpoint},
|
||
|
|
"pipelines": {
|
||
|
|
"smoke": {"nodes": {"source": {"uses": "test.source@1"}}}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|