import json import os import stat import numpy as np import pytest from hermes_vision.recognize import best_match, cosine, load_db, save_db def vec(seed, noise=0.0, base=None): r = np.random.default_rng(seed) v = r.normal(size=128).astype(np.float32) if base is None else base return v + noise * r.normal(size=128).astype(np.float32) def test_cosine_basics(): a = vec(1) assert cosine(a, a) == pytest.approx(1.0) assert cosine(a, -a) == pytest.approx(-1.0) def test_best_match_picks_the_closest_person_and_rejects_strangers(): alice, bob = vec(1), vec(2) db = {'alice': [alice, vec(1, 0.3, alice)], 'bob': [bob]} assert best_match(vec(9, 0.2, alice), db)[0] == 'alice' assert best_match(vec(9, 0.2, bob), db)[0] == 'bob' name, sim = best_match(vec(77), db) # an unrelated face assert name == '' and sim < 0.363 def test_empty_db_matches_nobody(): assert best_match(vec(1), {}) == ('', -1.0) def test_db_roundtrip_is_private_and_atomic(tmp_path): path = str(tmp_path / 'sub' / 'faces.json') save_db({'alice': [vec(1), vec(2)]}, path) assert stat.S_IMODE(os.stat(path).st_mode) == 0o600 assert stat.S_IMODE(os.stat(os.path.dirname(path)).st_mode) == 0o700 back = load_db(path) assert list(back) == ['alice'] and len(back['alice']) == 2 assert np.allclose(back['alice'][0], vec(1)) assert [f for f in os.listdir(os.path.dirname(path)) if f.startswith('.faces-')] == [] # no temp litter assert json.load(open(path)).keys() == {'alice'} def test_load_missing_db_is_empty(tmp_path): assert load_db(str(tmp_path / 'nope.json')) == {}