"""서버 임계값이 실제로 감지기에 적용되는지. 이 경로는 지금까지 통째로 비어 있었다: 서버에 표가 있고, /api/config 가 값을 실어 보내고, edge/zones.py 가 파싱한 뒤 그 dict 를 버렸다. 관리자가 무엇을 바꾸든 장비는 부팅 시 edge.env 값으로 영원히 돌았다. 아래 테스트가 그 배선을 고정한다. """ import math from edge.config import load_config from edge.runtime import PipelineRuntime from edge.tuning import apply_thresholds class _Src: def read(self): return False, None class _Pose: def infer(self, frame): return [] class _Sink: def try_send(self, event, snaps): pass def _runtime() -> PipelineRuntime: cfg = load_config({"DEVICE_ID": "pi-1", "CAMERA_ID": "cam-1", "CAFE24_URL": "https://x.example", "DEVICE_SECRET": "0" * 32}) return PipelineRuntime(cfg, _Src(), _Pose(), _Sink()) def test_every_catalogued_key_reaches_its_detector(): rt = _runtime() applied = rt.apply_thresholds({ "fall_prone_s": 5.0, "fall_no_motion_s": 20.0, "immobile_s": 300.0, "ghost_reappear_s": 25.0, "zone_debounce_s": 1.5, "cooldown_s": 60.0, "motion_thr": 0.4, "infer_every": 2, }) e = rt.engine assert e.fall.cfg.prone_confirm_s == 5.0 assert e.fall.cfg.no_motion_s == 20.0 assert e.immobility.cfg.immobile_s == 300.0 assert e.ghost.cfg.reappear_s == 25.0 assert e.zone.cfg.debounce_s == 1.5 assert e.cfg.cooldown_s == 60.0 assert rt.infer_every == 2 assert set(applied) == {"fall_prone_s", "fall_no_motion_s", "immobile_s", "ghost_reappear_s", "zone_debounce_s", "cooldown_s", "motion_thr", "infer_every"} def test_motion_threshold_is_shared_by_both_detectors_that_use_it(): # edge/config.py 는 기동 시 같은 MOTION_THR 을 fall 과 immobility 양쪽에 넣는다. # 한쪽만 갱신하면 두 감지기가 서로 다른 기준으로 "정지"를 판단하게 된다. rt = _runtime() rt.apply_thresholds({"motion_thr": 0.75}) assert rt.engine.fall.cfg.motion_thr == 0.75 assert rt.engine.immobility.cfg.motion_thr == 0.75 def test_bad_values_are_ignored_not_fatal(caplog): # 이 경로는 24/7 동기화 루프다. 서버가 보낸 한 값이 이상하다고 공장 안 장비를 # 재시작 루프에 빠뜨리면 그 카메라는 그 순간부터 아무것도 못 본다. rt = _runtime() before = rt.engine.fall.cfg.prone_confirm_s logged = [] applied = apply_thresholds(rt, { "fall_prone_s": float("nan"), # 비교가 항상 False -> 감지기가 조용히 꺼진다 "cooldown_s": 0, # 비교가 항상 True -> 매 틱 재발행 "immobile_s": -5, "ghost_reappear_s": "abc", }, log=logged.append) assert applied == {} assert rt.engine.fall.cfg.prone_confirm_s == before assert not math.isnan(rt.engine.fall.cfg.prone_confirm_s) assert len(logged) == 4, "무시한 값은 반드시 로그로 남는다 — 조용한 무시가 가장 나쁘다" def test_unknown_keys_are_ignored_silently(): # 서버가 신버전, 엣지가 구버전인 정상 상황. 경고를 낼 일이 아니다. rt = _runtime() logged = [] applied = apply_thresholds(rt, {"some_future_knob": 1.0}, log=logged.append) assert applied == {} assert logged == [] def test_infer_every_is_bounded(): # 서버가 실수로 1000 을 보내면 그 카메라는 사실상 감시를 멈춘다. rt = _runtime() rt.apply_thresholds({"infer_every": 1000}) assert rt.infer_every == 10 rt.apply_thresholds({"infer_every": 0}) assert rt.infer_every == 10, "0 은 유효하지 않으므로 직전 값을 유지한다" def test_fractional_infer_every_is_rejected(): rt = _runtime() logged = [] apply_thresholds(rt, {"infer_every": 1.5}, log=logged.append) assert rt.infer_every == 1 assert logged def test_empty_or_non_dict_is_a_no_op(): rt = _runtime() assert apply_thresholds(rt, {}) == {} assert apply_thresholds(rt, None) == {} assert apply_thresholds(rt, "nope") == {}