import os import pytest from unittest.mock import MagicMock, patch @pytest.fixture def csv_file(tmp_path): p = tmp_path / "index.csv" p.write_text( "path,token,phrase,word\n" "video_dir/hello.mp4,HELLO,,\n" "video_dir/thank_you.mp4,THANK-YOU,,\n" "video_dir/good_morning.mp4,,GOOD MORNING,\n" ) return str(p) @pytest.fixture def dict_dir(tmp_path): d = tmp_path / "dictionary" d.mkdir() (d / "hello.mp4").write_bytes(b"") (d / "thank_you.mp4").write_bytes(b"") (d / "good_morning.mp4").write_bytes(b"") return str(d) def _mock_vi(result=None): vi = MagicMock() vi.query_gloss.return_value = result or [] return vi def test_ensure_extracted_skips_when_dir_exists(tmp_path): """Does nothing if dictionary directory already exists.""" (tmp_path / "dictionary").mkdir() from mapping import ensure_dictionary_extracted with patch("mapping.zipfile.ZipFile") as mock_zip: ensure_dictionary_extracted( zip_path=str(tmp_path / "dict.zip"), extract_to=str(tmp_path), ) mock_zip.assert_not_called() def test_ensure_extracted_unzips_when_missing(tmp_path): """Extracts zip when dictionary directory is absent.""" zip_path = str(tmp_path / "dict.zip") from mapping import ensure_dictionary_extracted with patch("mapping.zipfile.ZipFile") as mock_zip_cls: ensure_dictionary_extracted(zip_path=zip_path, extract_to=str(tmp_path)) mock_zip_cls.assert_called_once_with(zip_path) mock_zip_cls.return_value.__enter__.return_value.extractall.assert_called_once_with( str(tmp_path) ) def test_exact_single_token_match(csv_file, dict_dir): """get_paths returns path on exact single-token match.""" with patch("mapping.ensure_dictionary_extracted"): from mapping import ASLDictionary d = ASLDictionary(csv_path=csv_file, dictionary_dir=dict_dir, vector_index=_mock_vi()) paths = d.get_paths(["hello"]) assert len(paths) == 1 assert paths[0].endswith("hello.mp4") def test_greedy_phrase_match(csv_file, dict_dir): """get_paths greedily matches multi-word phrase tokens.""" with patch("mapping.ensure_dictionary_extracted"): from mapping import ASLDictionary d = ASLDictionary(csv_path=csv_file, dictionary_dir=dict_dir, vector_index=_mock_vi()) paths = d.get_paths(["good", "morning"]) assert len(paths) == 1 assert paths[0].endswith("good_morning.mp4") def test_semantic_fallback_on_exact_miss(csv_file, dict_dir): """get_paths calls VectorIndex when exact match fails.""" vi = _mock_vi(result=[("HELLO", 0.85, "hello.mp4")]) with patch("mapping.ensure_dictionary_extracted"): from mapping import ASLDictionary d = ASLDictionary(csv_path=csv_file, dictionary_dir=dict_dir, vector_index=vi) paths = d.get_paths(["HI"]) assert len(paths) == 1 assert paths[0].endswith("hello.mp4") vi.query_gloss.assert_called_once_with("HI", top_k=1) def test_fuzzy_fallback_on_semantic_miss(csv_file, dict_dir): """get_paths uses difflib when semantic search returns nothing.""" vi = _mock_vi(result=[]) with patch("mapping.ensure_dictionary_extracted"): from mapping import ASLDictionary d = ASLDictionary(csv_path=csv_file, dictionary_dir=dict_dir, vector_index=vi) # "HELO" scores ~0.89 vs "HELLO" with difflib — above default cutoff 0.6 paths = d.get_paths(["HELO"]) assert len(paths) == 1 assert paths[0].endswith("hello.mp4") def test_no_match_returns_empty(csv_file, dict_dir): """get_paths skips token when all three tiers fail.""" vi = _mock_vi(result=[]) with patch("mapping.ensure_dictionary_extracted"), \ patch.dict(os.environ, {"FUZZY_CUTOFF": "0.99"}): from mapping import ASLDictionary d = ASLDictionary(csv_path=csv_file, dictionary_dir=dict_dir, vector_index=vi) paths = d.get_paths(["XYZZY"]) assert paths == []