"""The shipped YAML carries thresholds measured on the deployment site; the pydantic defaults must not drift away from them (a trimmed config would then silently re-admit the false positives those values were tuned to reject).""" import pytest from behavision.config import (ApiSection, Config, DetectionSection, RecognitionSection, TrackingSection, ensure_api_credentials, load_config) def test_yaml_matches_code_defaults(): cfg = load_config() for section, model in (("detection", DetectionSection), ("recognition", RecognitionSection), ("tracking", TrackingSection)): loaded = getattr(cfg, section) defaults = model() for name in model.model_fields: assert getattr(loaded, name) == getattr(defaults, name), ( f"{section}.{name} drifted between config/default.yaml " f"and config.py") def test_tuned_thresholds_are_the_defaults(): cfg = Config() assert cfg.detection.score_threshold == 0.82 assert cfg.recognition.min_enroll_quality == 0.65 def test_loopback_stays_open(tmp_path): cfg = Config() cfg.app.data_dir = tmp_path cfg.api.host = "127.0.0.1" auth_on, generated = ensure_api_credentials(cfg) assert (auth_on, generated) == (False, False) assert not (tmp_path / "api_credentials.txt").exists() def test_routable_host_never_served_unauthenticated(tmp_path): cfg = Config() cfg.app.data_dir = tmp_path cfg.api.host = "0.0.0.0" auth_on, generated = ensure_api_credentials(cfg) assert auth_on and generated assert cfg.api.username and len(cfg.api.password) >= 16 assert (tmp_path / "api_credentials.txt").exists() def test_generated_credentials_are_stable_across_restarts(tmp_path): first = Config() first.app.data_dir = tmp_path first.api.host = "0.0.0.0" ensure_api_credentials(first) second = Config() second.app.data_dir = tmp_path second.api.host = "0.0.0.0" auth_on, generated = ensure_api_credentials(second) assert auth_on and not generated # reused, not re-minted assert second.api.password == first.api.password def test_configured_credentials_win(tmp_path): cfg = Config() cfg.app.data_dir = tmp_path cfg.api.host = "0.0.0.0" cfg.api.username, cfg.api.password = "alice", "s3cret" auth_on, generated = ensure_api_credentials(cfg) assert (auth_on, generated) == (True, False) assert not (tmp_path / "api_credentials.txt").exists() assert cfg.api.password == "s3cret" def test_blank_env_placeholders_do_not_enable_auth(): """Unset ${ENV} parses as YAML null; auth must not half-enable.""" cfg = ApiSection(host="0.0.0.0", port=None, username=None, password=None) assert cfg.username == "" and cfg.password == "" assert cfg.port == 8010 assert not cfg.auth_enabled def test_color_order_is_declared_per_model_family(): """ArcFace/InsightFace exports want RGB, AdaFace wants BGR. Same scaling, opposite channel order, and feeding the wrong one raises nothing — it just yields off-distribution embeddings. Pin the mapping down.""" from behavision.recognition import color_order_for for name in ("w600k_mbf", "w600k_r50", "arcface", "arcface_int8"): assert color_order_for(name) == "RGB", name for name in ("adaface_ir18", "adaface_ir50", "adaface_ir101", "AdaFace_IR50"): assert color_order_for(name) == "BGR", name def test_better_models_are_preferred_over_the_fallback(): from behavision.recognition import MODEL_CANDIDATES order = {n: i for i, n in enumerate(MODEL_CANDIDATES)} # first that loads wins, so accuracy must come before the 13 MB fallback assert order["w600k_r50.onnx"] < order["w600k_mbf.onnx"] assert order["adaface_ir50.onnx"] < order["w600k_r50.onnx"] def test_unloadable_model_falls_through_to_the_next_candidate(tmp_path): """The deploy box may not have RAM for the big model. A corrupt/unusable file must be skipped, not fatal — otherwise a memory-starved machine can no longer start at all.""" import shutil from pathlib import Path from behavision.recognition import ArcFaceEncoder real = Path(__file__).resolve().parent.parent / "models" / "w600k_mbf.onnx" if not real.exists(): import pytest pytest.skip("no recognition model installed") # a plausible-looking but unloadable "big model" ahead of a good one (tmp_path / "w600k_r50.onnx").write_bytes(b"not an onnx file") shutil.copy(real, tmp_path / "w600k_mbf.onnx") enc = ArcFaceEncoder(tmp_path) assert enc.model_name == "w600k_mbf" # fell through, did not crash def test_no_usable_model_fails_loudly(tmp_path): from behavision.recognition import ArcFaceEncoder (tmp_path / "w600k_mbf.onnx").write_bytes(b"garbage") with pytest.raises(FileNotFoundError, match="no usable recognition model"): ArcFaceEncoder(tmp_path)