Download tests/test_integration.py from vomebook/Search: direct link, hf CLI and curl.
- Browser
- Download file 31.9 kB
-
https://huggingface.co/spaces/vomebook/Search/resolve/main/tests/test_integration.py
- Command line
-
hf download hf://spaces/vomebook/Search/tests/test_integration.py
-
curl -L -o test_integration.py https://huggingface.co/spaces/vomebook/Search/resolve/main/tests/test_integration.py
31.9 kB
| from __future__ import annotations | |
| import gzip | |
| import io | |
| import json | |
| import sqlite3 | |
| import tempfile | |
| import threading | |
| import unittest | |
| import zipfile | |
| from pathlib import Path | |
| from unittest.mock import patch | |
| from urllib.parse import urlencode | |
| from fastapi.testclient import TestClient | |
| import app | |
| import build_fulltext_db | |
| def create_fts_database(path: Path, rows: list[tuple[int, str]]) -> None: | |
| with sqlite3.connect(path) as connection: | |
| connection.execute("CREATE TABLE documents (doc_id TEXT PRIMARY KEY, summary TEXT NOT NULL)") | |
| connection.execute("CREATE TABLE postings (token_hash BLOB PRIMARY KEY, docs BLOB NOT NULL, doc_count INTEGER NOT NULL)") | |
| connection.execute("CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)") | |
| connection.execute("INSERT INTO metadata VALUES ('tokenizer_version', ?)", (app.TOKENIZER_VERSION,)) | |
| connection.execute("CREATE VIRTUAL TABLE content_fts USING fts5(content, tokenize='trigram', detail='full')") | |
| connection.execute("CREATE TABLE snippet_anchors (token_hash BLOB NOT NULL, doc_number INTEGER NOT NULL, char_offset INTEGER NOT NULL, PRIMARY KEY (token_hash, doc_number)) WITHOUT ROWID") | |
| for number, content in rows: | |
| connection.execute("INSERT INTO documents VALUES (?, ?)", (f"CCRD:{number}", content[:700])) | |
| connection.execute("INSERT INTO content_fts(rowid, content) VALUES (?, ?)", (number, content)) | |
| connection.executemany( | |
| "INSERT INTO snippet_anchors VALUES (?, ?, ?)", | |
| ((token_hash, number, char_offset) for token_hash, char_offset in build_fulltext_db.snippet_anchors(content).items()), | |
| ) | |
| for token in build_fulltext_db.index_tokens(content) | build_fulltext_db.literal_tokens(content): | |
| token_hash = __import__("hashlib").sha256(token.encode("utf-8")).digest()[:16] | |
| existing = connection.execute("SELECT docs FROM postings WHERE token_hash = ?", (token_hash,)).fetchone() | |
| numbers = [number] if existing is None else sorted(build_doc_numbers(existing[0]) | {number}) | |
| payload = build_fulltext_db.encode_doc_ids(numbers) | |
| connection.execute( | |
| "INSERT OR REPLACE INTO postings VALUES (?, ?, ?)", | |
| (token_hash, payload, len(numbers)), | |
| ) | |
| def build_doc_numbers(payload: bytes) -> set[int]: | |
| return app.decode_doc_numbers(payload) | |
| class FulltextIntegrationTests(unittest.TestCase): | |
| def test_match_cache_is_complete_isolated_mode_scoped_and_expiring(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| create_fts_database(root / "CCRD.sqlite3", [(1, "文化 革命"), (2, "文化革命")]) | |
| databases = app.FulltextDatabases(root) | |
| original = databases._search_source_connection | |
| try: | |
| with patch.object(databases, "_search_source_connection", wraps=original) as search, \ | |
| patch.object(app.time, "monotonic", return_value=100) as clock: | |
| first = databases.search_source("CCRD", "文化 革命") | |
| first.clear() | |
| self.assertEqual(databases.search_source("CCRD", "文化 革命"), {1, 2}) | |
| self.assertEqual(search.call_count, 1) | |
| self.assertEqual(databases.search_source("CCRD", "文化 革命", True), {1}) | |
| self.assertEqual(search.call_count, 2) | |
| clock.return_value = 401 | |
| self.assertEqual(databases.search_source("CCRD", "文化 革命"), {1, 2}) | |
| self.assertEqual(search.call_count, 3) | |
| self.assertEqual(databases.match_inflight, {}) | |
| finally: | |
| databases.close() | |
| def test_match_cache_obeys_entry_and_total_id_budgets_without_truncation(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| create_fts_database(root / "CCRD.sqlite3", [(1, "正文")]) | |
| databases = app.FulltextDatabases(root) | |
| try: | |
| with patch.object(app, "FULLTEXT_MATCH_CACHE_MAX_ENTRIES", 2), \ | |
| patch.object(app, "FULLTEXT_MATCH_CACHE_MAX_TOTAL_IDS", 3), \ | |
| patch.object(databases, "_search_source_connection", return_value={1, 2}): | |
| for query in ("甲", "乙", "丙"): | |
| self.assertEqual(databases.search_source("CCRD", query), {1, 2}) | |
| self.assertEqual(len(databases.match_cache), 1) | |
| self.assertLessEqual(sum(len(value[1]) for value in databases.match_cache.values()), 3) | |
| databases.match_cache.clear() | |
| with patch.object(databases, "_search_source_connection", return_value={1}) as search: | |
| for query in ("甲", "乙", "丙"): | |
| self.assertEqual(databases.search_source("CCRD", query), {1}) | |
| self.assertEqual(len(databases.match_cache), 2) | |
| databases.match_cache.clear() | |
| with patch.object(databases, "_search_source_connection", return_value={1, 2, 3, 4}) as search: | |
| for _ in range(2): | |
| self.assertEqual(databases.search_source("CCRD", "oversized"), {1, 2, 3, 4}) | |
| self.assertEqual(search.call_count, 2) | |
| self.assertEqual(databases.match_cache, {}) | |
| self.assertEqual(databases.match_inflight, {}) | |
| finally: | |
| databases.close() | |
| def test_concurrent_source_queries_share_complete_work_and_failures_release(self): | |
| import concurrent.futures | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| create_fts_database(root / "CCRD.sqlite3", [(1, "正文")]) | |
| databases = app.FulltextDatabases(root) | |
| entered = threading.Event() | |
| joined = threading.Event() | |
| release = threading.Event() | |
| calls = [] | |
| def execute(*_args): | |
| calls.append(1) | |
| entered.set() | |
| self.assertTrue(release.wait(5)) | |
| return {1, 2, 3} | |
| try: | |
| with patch.object(databases, "_search_source_connection", side_effect=execute): | |
| with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: | |
| first = executor.submit(databases.search_source, "CCRD", "query") | |
| try: | |
| self.assertTrue(entered.wait(5)) | |
| pending = next(iter(databases.match_inflight.values())) | |
| original_wait = pending.wait | |
| def wait(timeout=None): | |
| joined.set() | |
| return original_wait(timeout) | |
| with patch.object(pending, "wait", side_effect=wait): | |
| second = executor.submit(databases.search_source, "CCRD", "query") | |
| self.assertTrue(joined.wait(5)) | |
| self.assertEqual(len(calls), 1) | |
| finally: | |
| release.set() | |
| self.assertEqual(first.result(timeout=5), {1, 2, 3}) | |
| self.assertEqual(second.result(timeout=5), {1, 2, 3}) | |
| self.assertEqual(len(calls), 1) | |
| self.assertEqual(databases.match_inflight, {}) | |
| with patch.object(databases, "_search_source_connection", side_effect=[RuntimeError("failed"), {9}]) as search: | |
| with self.assertRaises(RuntimeError): | |
| databases.search_source("CCRD", "failure") | |
| self.assertEqual(databases.match_inflight, {}) | |
| self.assertEqual(databases.search_source("CCRD", "failure"), {9}) | |
| self.assertEqual(search.call_count, 2) | |
| finally: | |
| databases.close() | |
| def test_fulltext_databases_can_load_in_memory(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| path = Path(directory) / "CCRD.sqlite3" | |
| create_fts_database(path, [(1, "开头 文化革命 正文")]) | |
| databases = app.FulltextDatabases(Path(directory), in_memory=True) | |
| try: | |
| self.assertEqual(databases.search_source("CCRD", "文化革命", exact=True), {1}) | |
| self.assertEqual(databases.search_source("CCRD", "文化 革命", exact=False), {1}) | |
| finally: | |
| databases.close() | |
| def test_short_latin_exact_and_latin_wildcards_do_not_miss_substrings(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| path = Path(directory) / "CCRD.sqlite3" | |
| create_fts_database(path, [(1, "smartphone abc123xyz"), (2, "alpha beta"), (3, "unrelated")]) | |
| databases = app.FulltextDatabases(Path(directory)) | |
| try: | |
| self.assertEqual(databases.search_source("CCRD", "abc", exact=True), {1}) | |
| self.assertEqual(databases.search_source("CCRD", "phone", exact=True), {1}) | |
| self.assertEqual(databases.search_source("CCRD", "abc*xyz", exact=True), {1}) | |
| self.assertEqual(databases.search_source("CCRD", "a?c*xyz", exact=True), {1}) | |
| finally: | |
| databases.close() | |
| def test_builder_creates_complete_reusable_database(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| corpus = root / "corpus" | |
| corpus.mkdir() | |
| (corpus / "one.txt").write_text("开头 文化革命 正文", encoding="utf-8") | |
| (corpus / "two.txt").write_text("全角ABC和手机", encoding="utf-8") | |
| records = [ | |
| {"doc_id": "TEST:1", "storage_root": "corpus", "storage_rel_path": "one.txt"}, | |
| {"doc_id": "TEST:2", "storage_root": "corpus", "storage_rel_path": "two.txt"}, | |
| ] | |
| with patch.object(build_fulltext_db, "BASE_DIR", root), patch.object(build_fulltext_db, "FULLTEXT_DIR", root / "fulltext"): | |
| build_fulltext_db.FULLTEXT_DIR.mkdir() | |
| build_fulltext_db.build_source_db("TEST", records) | |
| database = build_fulltext_db.FULLTEXT_DIR / "TEST.sqlite3" | |
| first_bytes = database.read_bytes() | |
| self.assertTrue(build_fulltext_db.database_ready(database, 2)) | |
| with sqlite3.connect(database) as connection: | |
| self.assertEqual(connection.execute("SELECT COUNT(*) FROM documents").fetchone()[0], 2) | |
| self.assertEqual(connection.execute("SELECT COUNT(*) FROM content_fts").fetchone()[0], 2) | |
| self.assertGreater(connection.execute("SELECT COUNT(*) FROM snippet_anchors").fetchone()[0], 0) | |
| self.assertEqual(connection.execute("SELECT value FROM metadata WHERE key='tokenizer_version'").fetchone()[0], app.TOKENIZER_VERSION) | |
| self.assertGreater(connection.execute("SELECT COUNT(*) FROM postings").fetchone()[0], 0) | |
| build_fulltext_db.build_source_db("TEST", records) | |
| self.assertEqual(database.read_bytes(), first_bytes) | |
| def test_builder_rejects_invalid_document_ids_without_publishing_database(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| corpus = root / "corpus" | |
| corpus.mkdir() | |
| (corpus / "one.txt").write_text("正文", encoding="utf-8") | |
| record = {"doc_id": "invalid", "storage_root": "corpus", "storage_rel_path": "one.txt"} | |
| with patch.object(build_fulltext_db, "BASE_DIR", root), patch.object(build_fulltext_db, "FULLTEXT_DIR", root / "fulltext"): | |
| build_fulltext_db.FULLTEXT_DIR.mkdir() | |
| with self.assertRaises(ValueError): | |
| build_fulltext_db.build_source_db("TEST", [record]) | |
| self.assertFalse((build_fulltext_db.FULLTEXT_DIR / "TEST.sqlite3").exists()) | |
| def test_builder_does_not_publish_database_with_missing_files(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| record = {"doc_id": "TEST:1", "storage_root": "corpus", "storage_rel_path": "missing.txt"} | |
| with patch.object(build_fulltext_db, "BASE_DIR", root), patch.object(build_fulltext_db, "FULLTEXT_DIR", root / "fulltext"): | |
| build_fulltext_db.FULLTEXT_DIR.mkdir() | |
| with self.assertRaises(RuntimeError): | |
| build_fulltext_db.build_source_db("TEST", [record]) | |
| self.assertFalse((build_fulltext_db.FULLTEXT_DIR / "TEST.sqlite3").exists()) | |
| self.assertFalse((build_fulltext_db.FULLTEXT_DIR / "TEST.sqlite3.tmp").exists()) | |
| def test_atomic_database_replacement_reopens_and_increments_generation(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| path = root / "CCRD.sqlite3" | |
| create_fts_database(path, [(1, "旧数据库文化")]) | |
| databases = app.FulltextDatabases(root) | |
| first_generation = databases.generation | |
| replacement = root / "replacement.sqlite3" | |
| create_fts_database(replacement, [(2, "新数据库革命资料")]) | |
| replacement.replace(path) | |
| databases.refresh() | |
| try: | |
| self.assertGreater(databases.generation, first_generation) | |
| self.assertEqual(databases.search_source("CCRD", "革命"), {2}) | |
| self.assertEqual(databases.search_source("CCRD", "文化"), set()) | |
| finally: | |
| databases.close() | |
| def test_refresh_builds_replacement_without_blocking_existing_queries(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| path = root / "CCRD.sqlite3" | |
| create_fts_database(path, [(1, "旧数据库文化")]) | |
| databases = app.FulltextDatabases(root, in_memory=True) | |
| replacement = root / "replacement.sqlite3" | |
| create_fts_database(replacement, [(2, "新数据库革命资料")]) | |
| replacement.replace(path) | |
| opening = threading.Event() | |
| release = threading.Event() | |
| original_open = databases._open_candidate | |
| def blocked_open(candidate_path): | |
| opening.set() | |
| self.assertTrue(release.wait(2)) | |
| return original_open(candidate_path) | |
| thread = threading.Thread(target=databases.refresh) | |
| try: | |
| with patch.object(databases, "_open_candidate", side_effect=blocked_open): | |
| thread.start() | |
| self.assertTrue(opening.wait(2)) | |
| self.assertEqual(databases.search_source("CCRD", "文化"), {1}) | |
| release.set() | |
| thread.join(2) | |
| self.assertFalse(thread.is_alive()) | |
| self.assertEqual(databases.search_source("CCRD", "革命"), {2}) | |
| finally: | |
| release.set() | |
| thread.join(2) | |
| databases.close() | |
| def test_active_query_keeps_retired_connection_alive_until_release(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| path = root / "CCRD.sqlite3" | |
| create_fts_database(path, [(1, "旧数据库文化")]) | |
| databases = app.FulltextDatabases(root, in_memory=True) | |
| entered = threading.Event() | |
| release = threading.Event() | |
| result = [] | |
| original_search = databases._search_source_connection | |
| def blocked_search(connection, query, exact, state): | |
| entered.set() | |
| self.assertTrue(release.wait(2)) | |
| return original_search(connection, query, exact, state) | |
| thread = threading.Thread(target=lambda: result.append(databases.search_source("CCRD", "文化"))) | |
| try: | |
| with patch.object(databases, "_search_source_connection", side_effect=blocked_search): | |
| thread.start() | |
| self.assertTrue(entered.wait(2)) | |
| replacement = root / "replacement.sqlite3" | |
| create_fts_database(replacement, [(2, "新数据库革命资料")]) | |
| replacement.replace(path) | |
| databases.refresh() | |
| self.assertTrue(databases.retired_connections) | |
| release.set() | |
| thread.join(2) | |
| self.assertFalse(thread.is_alive()) | |
| self.assertEqual(result, [{1}]) | |
| self.assertFalse(databases.retired_connections) | |
| self.assertEqual(databases.match_cache, {}) | |
| self.assertEqual(databases.search_source("CCRD", "革命"), {2}) | |
| finally: | |
| release.set() | |
| thread.join(2) | |
| databases.close() | |
| class HttpIntegrationTests(unittest.TestCase): | |
| def test_fulltext_in_memory_defaults_enabled(self): | |
| self.assertTrue(app.FULLTEXT_IN_MEMORY) | |
| def setUpClass(cls): | |
| app.load_data() | |
| cls.original_databases = app.fulltext_databases | |
| app.fulltext_databases = app.FulltextDatabases(Path("/tmp/ccrd-test-no-index")) | |
| cls.client = TestClient(app.app) | |
| def tearDownClass(cls): | |
| app.fulltext_databases.close() | |
| app.fulltext_databases = cls.original_databases | |
| def test_search_http_validation_and_option_isolation(self): | |
| valid = self.client.post("/api/search", json={"q": "文化 革命", "fulltext": False, "page_size": 1}) | |
| self.assertEqual(valid.status_code, 200) | |
| self.assertEqual(valid.json()["page_size"], 1) | |
| self.assertEqual(self.client.post("/api/search", json={"q": "x" * 257}).status_code, 422) | |
| self.assertEqual(self.client.post("/api/search", json={"page": 0}).status_code, 422) | |
| self.assertEqual(self.client.post("/api/search", json={"page_size": 201}).status_code, 422) | |
| self.assertEqual(self.client.post("/api/search", json={"min_size": -1}).status_code, 422) | |
| self.assertEqual(self.client.post("/api/search/missing", json={}).status_code, 404) | |
| def test_preview_snippet_and_download_http_contracts(self): | |
| doc_id, record = next(iter(app.record_map.items())) | |
| preview = self.client.get(f"/api/preview/{doc_id}") | |
| self.assertEqual(preview.status_code, 200) | |
| self.assertEqual(preview.json()["doc_id"], doc_id) | |
| snippet = self.client.get(f"/api/snippet/{doc_id}", params={"q": "文化", "exact": "false"}) | |
| self.assertEqual(snippet.status_code, 200) | |
| self.assertIn("snippet", snippet.json()) | |
| download = self.client.get(f"/api/download/{doc_id}") | |
| self.assertEqual(download.status_code, 200) | |
| self.assertTrue(download.headers["content-type"].startswith("text/plain")) | |
| self.assertIn("attachment", download.headers["content-disposition"]) | |
| self.assertEqual(download.content, app.get_doc_storage_path(record).read_bytes()) | |
| self.assertEqual(self.client.get("/api/preview/missing:1").status_code, 404) | |
| self.assertEqual(self.client.get("/api/snippet/missing:1").status_code, 404) | |
| self.assertEqual(self.client.get("/api/download/missing:1").status_code, 404) | |
| self.assertEqual(self.client.get(f"/api/snippet/{doc_id}", params={"q": "x" * 257}).status_code, 422) | |
| def test_json_and_form_zip_are_valid_and_enforce_limits(self): | |
| selected = sorted(app.records, key=lambda record: record["size"])[:2] | |
| ids = [record["doc_id"] for record in selected] | |
| for response in ( | |
| self.client.post("/api/zip", json={"doc_ids": ids}), | |
| self.client.post( | |
| "/api/zip-download", | |
| content=urlencode([("doc_id", ids[0]), ("doc_id", ids[1]), ("doc_id", ids[0])]), | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, | |
| ), | |
| ): | |
| self.assertEqual(response.status_code, 200) | |
| self.assertEqual(response.headers["content-type"], "application/zip") | |
| self.assertIn("attachment", response.headers["content-disposition"]) | |
| self.assertEqual(response.headers["cache-control"], "no-store") | |
| self.assertEqual(response.headers["content-encoding"], "identity") | |
| self.assertEqual(int(response.headers["content-length"]), len(response.content)) | |
| self.assertTrue(response.content.startswith(b"PK")) | |
| self.assertNotIn(b"PK\x06\x06", response.content) | |
| with zipfile.ZipFile(io.BytesIO(response.content)) as archive: | |
| self.assertEqual(len(archive.namelist()), 2) | |
| self.assertEqual(archive.testzip(), None) | |
| with self.assertRaises(zipfile.BadZipFile): | |
| with zipfile.ZipFile(io.BytesIO(response.content[:-22])) as truncated: | |
| truncated.testzip() | |
| self.assertEqual(self.client.post("/api/zip", json={"doc_ids": []}).status_code, 400) | |
| self.assertEqual(self.client.post("/api/zip", json={"doc_ids": ["x"] * 501}).status_code, 422) | |
| too_many = [("doc_id", f"CCRD:{index}") for index in range(501)] | |
| self.assertEqual(self.client.post( | |
| "/api/zip-download", | |
| content=urlencode(too_many), | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, | |
| ).status_code, 400) | |
| self.assertEqual(self.client.post("/api/zip-download", json={"doc_id": ids}).status_code, 415) | |
| self.assertEqual(self.client.get("/api/zip-download").status_code, 400) | |
| legacy = self.client.post( | |
| "/api/zip-download", | |
| content=urlencode([("doc_id", ids[0]), ("doc_id", ids[1])]), | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, | |
| follow_redirects=False, | |
| ) | |
| self.assertEqual(legacy.status_code, 303) | |
| self.assertTrue(legacy.headers["location"].startswith("/api/zip-download/")) | |
| legacy_download = self.client.get(legacy.headers["location"]) | |
| self.assertTrue(legacy_download.content.startswith(b"PK")) | |
| prepared = self.client.post("/api/zip-prepare", json={"doc_ids": ids}) | |
| self.assertEqual(prepared.status_code, 200) | |
| self.assertEqual(prepared.json()["file_count"], 2) | |
| download_url = prepared.json()["download_url"] | |
| for _attempt in range(2): | |
| downloaded = self.client.get(download_url, headers={"Accept-Encoding": "gzip"}) | |
| self.assertEqual(downloaded.status_code, 200) | |
| self.assertEqual(downloaded.headers["content-encoding"], "identity") | |
| self.assertTrue(downloaded.content.startswith(b"PK")) | |
| with zipfile.ZipFile(io.BytesIO(downloaded.content)) as archive: | |
| self.assertEqual(len(archive.namelist()), 2) | |
| self.assertIsNone(archive.testzip()) | |
| self.assertEqual(self.client.get("/api/zip-download/invalid-token").status_code, 404) | |
| def test_zip_disambiguates_duplicate_display_paths_and_deduplicates_ids(self): | |
| with tempfile.TemporaryDirectory() as directory: | |
| root = Path(directory) | |
| first = root / "first.txt" | |
| second = root / "second.txt" | |
| first.write_text("first", encoding="utf-8") | |
| second.write_text("second", encoding="utf-8") | |
| original_map = app.record_map | |
| app.record_map = { | |
| "CCRD:1": {"source": "CCRD", "display_name": 'same:name*?"<>|', "display_rel_path": "same/name.txt"}, | |
| "CCRD:2": {"source": "CCRD", "display_name": 'same:name*?"<>|', "display_rel_path": "same/name.txt"}, | |
| } | |
| paths = {"CCRD:1": first, "CCRD:2": second} | |
| try: | |
| with patch.object(app, "get_doc_storage_path", side_effect=lambda record: paths[next(key for key, value in app.record_map.items() if value is record)]): | |
| response = app.zip_response(["CCRD:1", "CCRD:1", "CCRD:2"]) | |
| self.assertEqual(response.status_code, 200) | |
| with open(response.path, "rb") as archive_file: | |
| payload = archive_file.read() | |
| self.assertNotIn(b"PK\x06\x06", payload) | |
| with zipfile.ZipFile(io.BytesIO(payload)) as archive: | |
| self.assertEqual(len(set(archive.namelist())), 2) | |
| self.assertTrue(all(not any(character in name for character in ':*?"<>|\\') for name in archive.namelist())) | |
| self.assertTrue(all(len(name.encode("utf-8")) <= 220 for name in archive.namelist())) | |
| self.assertEqual(sorted(archive.read(name).decode() for name in archive.namelist()), ["first", "second"]) | |
| finally: | |
| app.record_map = original_map | |
| def test_static_status_and_cors_contracts(self): | |
| self.assertEqual(self.client.get("/manifest.json").status_code, 200) | |
| self.assertIn("javascript", self.client.get("/sw.js").headers["content-type"]) | |
| status = self.client.get("/api/index-status") | |
| self.assertEqual(status.status_code, 200) | |
| self.assertEqual(status.json()["loaded_sources"], []) | |
| cors = self.client.options( | |
| "/api/search", | |
| headers={"Origin": "https://example.com", "Access-Control-Request-Method": "POST"}, | |
| ) | |
| self.assertEqual(cors.status_code, 200) | |
| self.assertEqual(cors.headers["access-control-allow-origin"], "*") | |
| self.assertEqual(self.client.get("/data/fulltext/CCRD.sqlite3").status_code, 404) | |
| self.assertEqual(self.client.get("/data/fulltext/CCRD.sqlite3.tmp").status_code, 404) | |
| def test_ping_is_empty_and_not_cached(self): | |
| response = self.client.get("/api/ping") | |
| self.assertEqual(response.status_code, 204) | |
| self.assertEqual(response.content, b"") | |
| self.assertEqual(response.headers["cache-control"], "no-store") | |
| def test_sources_extensions_folders_random_and_spa_routes(self): | |
| sources = self.client.get("/api/sources") | |
| self.assertEqual(sources.status_code, 200) | |
| self.assertEqual({item["slug"] for item in sources.json()}, {"CCRD", "CW"}) | |
| self.assertEqual(self.client.get("/api/extensions").json(), [{"name": "txt", "count": 47250}]) | |
| self.assertEqual(self.client.get("/api/extensions", params={"source": "CCRD"}).json(), [{"name": "txt", "count": 35586}]) | |
| folders = self.client.get("/api/folders/CCRD") | |
| self.assertEqual(folders.status_code, 200) | |
| self.assertIsInstance(folders.json(), list) | |
| contents = self.client.get("/api/folders/CCRD/contents") | |
| self.assertEqual(contents.status_code, 200) | |
| self.assertEqual(contents.json()["current_path"], "") | |
| random_doc = self.client.get("/api/random", params={"source": "CW"}) | |
| self.assertEqual(random_doc.status_code, 200) | |
| self.assertEqual(random_doc.json()["Source"], "CW") | |
| self.assertEqual(self.client.get("/api/random", params={"source": "missing"}).status_code, 404) | |
| # `sources` is the canonical filter and must not be silently ignored. | |
| self.assertEqual(self.client.get("/api/random", params={"sources": "missing"}).status_code, 404) | |
| self.assertEqual(self.client.get("/api/random", params={"sources": ["CW"]}).json()["Source"], "CW") | |
| self.assertEqual( | |
| self.client.get("/api/random", params=[("sources", "CCRD"), ("sources", "CW")]).status_code, 200 | |
| ) | |
| for route in ("/", "/CCRD", "/CW"): | |
| page = self.client.get(route) | |
| self.assertEqual(page.status_code, 200) | |
| self.assertIn("text/html", page.headers["content-type"]) | |
| self.assertIn("initial-sidebar-data", page.text) | |
| def test_unknown_sources_and_unknown_api_paths_return_json_404(self): | |
| for path in ("/api/folders/missing", "/api/folders/missing/contents"): | |
| response = self.client.get(path) | |
| self.assertEqual(response.status_code, 404, path) | |
| self.assertIn("application/json", response.headers["content-type"], path) | |
| self.assertEqual(response.json().get("error"), "source not found", path) | |
| for path in ("/api/nope", "/api/search", "/api/folders"): | |
| response = self.client.get(path) | |
| self.assertEqual(response.status_code, 404, path) | |
| self.assertIn("application/json", response.headers["content-type"], path) | |
| self.assertEqual(response.json().get("error"), "not found", path) | |
| def test_search_cache_ttl_is_longer_than_burst_idle_gap(self): | |
| self.assertGreaterEqual(app.SEARCH_CACHE_TTL_SECONDS, 300) | |
| def test_search_cache_returns_identical_payload(self): | |
| app.search_response_cache.clear() | |
| body = {"q": "server-timing-unique-query", "fulltext": False, "page_size": 1} | |
| first = self.client.post("/api/search", json=body) | |
| second = self.client.post("/api/search", json=body) | |
| self.assertEqual(first.status_code, 200) | |
| self.assertEqual(first.json(), second.json()) | |
| def test_missing_physical_files_return_stable_errors(self): | |
| original_map = app.record_map | |
| app.record_map = { | |
| "CCRD:missing": { | |
| "display_name": "missing", "display_rel_path": "missing.txt", "source_name": "CCRD", | |
| "storage_root": "CCRD", "storage_rel_path": "does-not-exist.txt", | |
| } | |
| } | |
| try: | |
| for endpoint in ("preview", "snippet", "download"): | |
| response = self.client.get(f"/api/{endpoint}/CCRD:missing") | |
| self.assertEqual(response.status_code, 404) | |
| self.assertIn("missing file", response.json()["error"]) | |
| finally: | |
| app.record_map = original_map | |
| class CorpusIntegrityTests(unittest.TestCase): | |
| def test_metadata_counts_and_every_storage_file_exists(self): | |
| payload = json.loads(gzip.decompress((Path(app.BASE_DIR) / "data/search_data.json.gz").read_bytes())) | |
| records = payload["records"] | |
| self.assertEqual(len(records), 47250) | |
| counts = {source["slug"]: source["count"] for source in payload["sources"]} | |
| self.assertEqual(counts, {"CCRD": 35586, "CW": 11664}) | |
| doc_ids = {record["doc_id"] for record in records} | |
| self.assertEqual(len(doc_ids), len(records)) | |
| missing = [ | |
| record["doc_id"] for record in records | |
| if not (Path(app.BASE_DIR) / record["storage_root"] / record["storage_rel_path"]).is_file() | |
| ] | |
| self.assertEqual(missing, []) | |
| # Every source TXT must be strict UTF-8 after the CW:28 conversion. A GB18030 | |
| # file here would be indexed by the builder only through its fallback path. | |
| non_utf8 = [] | |
| for record in records: | |
| path = Path(app.BASE_DIR) / record["storage_root"] / record["storage_rel_path"] | |
| try: | |
| path.read_bytes().decode("utf-8") | |
| except UnicodeDecodeError: | |
| non_utf8.append(record["doc_id"]) | |
| self.assertEqual(non_utf8, []) | |
| self.assertEqual(sum(record["source"] == "CCRD" for record in records), 35586) | |
| self.assertEqual(sum(record["source"] == "CW" for record in records), 11664) | |
| archive_keys = [(record["source"], record["display_rel_path"], record["doc_id"]) for record in records] | |
| self.assertEqual(len(archive_keys), len(set(archive_keys))) | |