import json import os import tempfile import unittest from pathlib import Path from friday_voice.authorization import classify_voice_authorization from friday_voice.config import Settings from friday_voice.hermes_client import build_cli_command from friday_voice.speaker_store import SpeakerStore, cosine_similarity class SpeakerMathTests(unittest.TestCase): def test_cosine_similarity_handles_identical_orthogonal_and_zero_vectors(self): self.assertAlmostEqual(cosine_similarity([1, 2, 3], [1, 2, 3]), 1.0) self.assertAlmostEqual(cosine_similarity([1, 0], [0, 1]), 0.0) self.assertEqual(cosine_similarity([0, 0], [1, 2]), 0.0) def test_speaker_store_identifies_best_matching_profile(self): with tempfile.TemporaryDirectory() as tmp: store = SpeakerStore(Path(tmp) / "speakers.json") store.add_embedding("jeremy", [1.0, 0.0, 0.0], source="office") store.add_embedding("other", [0.0, 1.0, 0.0], source="test") match = store.identify([0.98, 0.02, 0.0]) self.assertEqual(match.speaker_id, "jeremy") self.assertGreater(match.confidence, 0.95) def test_speaker_store_persists_profiles(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "speakers.json" SpeakerStore(path).add_embedding("jeremy", [0.1, 0.2, 0.3], source="phone") reloaded = SpeakerStore(path) self.assertEqual(len(reloaded.profiles["jeremy"].embeddings), 1) self.assertEqual(reloaded.profiles["jeremy"].embeddings[0].source, "phone") class AuthorizationTests(unittest.TestCase): def test_authorization_thresholds_map_voice_confidence_to_risk_level(self): self.assertEqual(classify_voice_authorization("jeremy", 0.88).level, "confirmed") self.assertEqual(classify_voice_authorization("jeremy", 0.75).level, "probable") self.assertEqual(classify_voice_authorization("jeremy", 0.50).level, "unknown") self.assertEqual(classify_voice_authorization(None, 0.99).level, "unknown") def test_unknown_speaker_is_read_only(self): decision = classify_voice_authorization(None, 0.0) self.assertTrue(decision.read_only) self.assertTrue(decision.requires_confirmation) class ConfigAndHermesTests(unittest.TestCase): def test_settings_defaults_match_residence_services(self): settings = Settings() self.assertEqual(settings.speaches_url, "http://192.168.86.150:8000") self.assertEqual(settings.hermes_command, "hermes") self.assertEqual(settings.speaker_confirmed_threshold, 0.85) def test_settings_from_env_uses_defaults_when_env_is_empty(self): settings = Settings.from_env({}) self.assertEqual(settings.speaches_url, "http://192.168.86.150:8000") self.assertEqual(settings.speaker_confirmed_threshold, 0.85) def test_hermes_cli_command_uses_single_query_mode_and_source(self): command = build_cli_command("status please", Settings(hermes_command="hermes")) self.assertEqual(command[:3], ["hermes", "chat", "-q"]) self.assertIn("status please", command) self.assertIn("--source", command) self.assertIn("friday-voice", command) if __name__ == "__main__": unittest.main()