重组为 backend/clients/docs 三层结构,并清理 git 污染。
将后端迁入 backend/,完善根目录 .gitignore,删除误提交的 .mypy_cache 缓存文件。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
131
backend/tests/test_voice_terminal_binding.py
Normal file
131
backend/tests/test_voice_terminal_binding.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""术间绑定与站点配置(OR_SITE_CONFIG_JSON)解析。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.config import Settings
|
||||
from app.or_site_config import (
|
||||
load_or_site_config_from_path,
|
||||
merge_video_rtsp_urls_into_file,
|
||||
parse_or_site_config_object,
|
||||
)
|
||||
from app.services.voice_terminal_binding import VoiceTerminalBindingIndex
|
||||
|
||||
|
||||
def test_parse_and_resolve() -> None:
|
||||
idx = VoiceTerminalBindingIndex.from_binding_list(
|
||||
[
|
||||
{
|
||||
"or_room_id": "OR-05",
|
||||
"camera_ids": ["or-cam-01", "or-cam-02"],
|
||||
"voice_terminal_id": "T-05",
|
||||
}
|
||||
]
|
||||
)
|
||||
assert idx is not None
|
||||
assert idx.resolve_terminal(["or-cam-02", "or-cam-01"]) == "T-05"
|
||||
assert idx.resolve_terminal(["or-cam-01"]) == "T-05"
|
||||
|
||||
|
||||
def test_resolve_subset_uses_smallest_superset_when_ambiguous() -> None:
|
||||
idx = VoiceTerminalBindingIndex.from_binding_list(
|
||||
[
|
||||
{
|
||||
"or_room_id": "A",
|
||||
"camera_ids": ["c1", "c2"],
|
||||
"voice_terminal_id": "T-AB",
|
||||
},
|
||||
{
|
||||
"or_room_id": "B",
|
||||
"camera_ids": ["c1", "c2", "c3"],
|
||||
"voice_terminal_id": "T-ABC",
|
||||
},
|
||||
]
|
||||
)
|
||||
assert idx is not None
|
||||
assert idx.resolve_terminal(["c1"]) == "T-AB"
|
||||
|
||||
|
||||
def test_duplicate_terminal_rejected() -> None:
|
||||
assert (
|
||||
VoiceTerminalBindingIndex.from_binding_list(
|
||||
[
|
||||
{"or_room_id": "A", "camera_ids": ["c1"], "voice_terminal_id": "T"},
|
||||
{"or_room_id": "B", "camera_ids": ["c2"], "voice_terminal_id": "T"},
|
||||
]
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_duplicate_camera_set_rejected() -> None:
|
||||
assert (
|
||||
VoiceTerminalBindingIndex.from_binding_list(
|
||||
[
|
||||
{"or_room_id": "A", "camera_ids": ["c1"], "voice_terminal_id": "T1"},
|
||||
{"or_room_id": "B", "camera_ids": ["c1"], "voice_terminal_id": "T2"},
|
||||
]
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_from_binding_list_empty() -> None:
|
||||
idx = VoiceTerminalBindingIndex.from_binding_list([])
|
||||
assert idx is not None
|
||||
assert idx.resolve_terminal(["x"]) is None
|
||||
|
||||
|
||||
def test_parse_site_config_rejects_unknown_keys() -> None:
|
||||
with pytest.raises(ValueError, match="unknown top-level"):
|
||||
parse_or_site_config_object(
|
||||
{
|
||||
"video_rtsp_urls": {},
|
||||
"voice_or_room_bindings": [],
|
||||
"extra": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_site_config_requires_both_sections() -> None:
|
||||
with pytest.raises(ValueError, match="must include"):
|
||||
parse_or_site_config_object({"video_rtsp_urls": {}})
|
||||
|
||||
|
||||
def test_load_from_path_roundtrip(tmp_path: Path) -> None:
|
||||
p = tmp_path / "site.json"
|
||||
p.write_text(
|
||||
'{"video_rtsp_urls":{"c1":"rtsp://h"},"voice_or_room_bindings":['
|
||||
'{"or_room_id":"R","camera_ids":["a"],"voice_terminal_id":"T"}]}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
cfg = load_or_site_config_from_path(p)
|
||||
assert cfg.video_rtsp_urls == {"c1": "rtsp://h"}
|
||||
assert cfg.voice_bindings.resolve_terminal(["a"]) == "T"
|
||||
|
||||
|
||||
def test_settings_video_map(tmp_path: Path) -> None:
|
||||
p = tmp_path / "site.json"
|
||||
p.write_text(
|
||||
'{"video_rtsp_urls":{"c1":"rtsp://x"},"voice_or_room_bindings":[]}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
s = Settings(or_site_config_json_file=str(p))
|
||||
assert s.video_rtsp_url_map() == {"c1": "rtsp://x"}
|
||||
|
||||
|
||||
def test_merge_preserves_bindings(tmp_path: Path) -> None:
|
||||
p = tmp_path / "site.json"
|
||||
p.write_text(
|
||||
'{"video_rtsp_urls":{"old":"rtsp://old"},'
|
||||
'"voice_or_room_bindings":[{"or_room_id":"R","camera_ids":["x"],'
|
||||
'"voice_terminal_id":"T"}]}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
merge_video_rtsp_urls_into_file(p, {"x": "rtsp://127.0.0.1:1/p"}, replace_host="127.0.0.1")
|
||||
cfg = load_or_site_config_from_path(p)
|
||||
assert cfg.video_rtsp_urls == {"x": "rtsp://127.0.0.1:1/p"}
|
||||
assert cfg.voice_bindings.resolve_terminal(["x"]) == "T"
|
||||
Reference in New Issue
Block a user