test: юнит-суит L1/L2/L3 + ГОСТ (41 тест) и гейт в CI перед деплоем
Первый настоящий автоматический тест-суит проекта — раньше регрессии ловились руками. Покрыта чистая логика детекции и форматирования (без БД/Redis/GPU/Ollama): - worker-indexer: L1 Winnowing (точные совпадения, идемпотентность отпечатка, диапазон signed int64) и L2 MinHash LSH (шинглы, Jaccard, near-duplicate + upsert через in-memory-фолбэк). - worker-gpu: L3 FAISS — возврат doc_id из PostgreSQL (IndexIDMap2), идемпотентность add_vectors (remove-before-add, без дублей), self-match ≈ 1, ранжирование. Прямо стережёт баги, из-за которых индекс переписывался. - worker-gost: ГОСТ 7.1-2003 и 7.0.5-2008 — авторы (≤3 / 4+ «и др.»/et al.), статья/книга/web, DOI, порядок сортировки кириллица→латиница, стр. в ссылке. Обвязка: per-service pytest.ini/conftest/requirements-test. scripts/run_tests.sh гоняет тесты в изолированных python:3.11-slim контейнерах (не засоряя хост), через Tsinghua-зеркало. CI: job `test` теперь гейтит `deploy` (needs: test) — падение тестов блокирует прод-деплой. make test / make test-one SVC=... Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
79
services/worker-indexer/tests/test_winnowing.py
Normal file
79
services/worker-indexer/tests/test_winnowing.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Юнит-тесты алгоритма Winnowing (уровень 1 — точные/частичные совпадения)."""
|
||||
|
||||
from app.algorithms.winnowing import (
|
||||
compute_similarity,
|
||||
get_ngrams,
|
||||
hash_ngram,
|
||||
jaccard_similarity,
|
||||
winnow,
|
||||
)
|
||||
|
||||
# Достаточно длинный текст, чтобы окно Winnowing реально отработало
|
||||
LONG = (
|
||||
"машинное обучение позволяет извлекать закономерности из больших объёмов "
|
||||
"данных без явного программирования каждого правила вручную аналитиком"
|
||||
)
|
||||
|
||||
|
||||
def test_get_ngrams_basic():
|
||||
assert get_ngrams(["a", "b", "c", "d"], k=2) == ["a b", "b c", "c d"]
|
||||
|
||||
|
||||
def test_get_ngrams_too_short_is_empty():
|
||||
assert get_ngrams(["a", "b"], k=5) == []
|
||||
|
||||
|
||||
def test_hash_ngram_is_deterministic():
|
||||
assert hash_ngram("одна и та же строка") == hash_ngram("одна и та же строка")
|
||||
|
||||
|
||||
def test_hash_ngram_fits_signed_int64():
|
||||
# Хэш обязан умещаться в PostgreSQL BIGINT (signed 64-bit)
|
||||
for s in ("a", "тест", "one two three four five", "𝔘𝔫𝔦𝔠𝔬𝔡𝔢"):
|
||||
h = hash_ngram(s)
|
||||
assert -(2**63) <= h <= 2**63 - 1
|
||||
|
||||
|
||||
def test_winnow_short_text_is_empty():
|
||||
assert winnow("три слова тут", k=5) == set()
|
||||
|
||||
|
||||
def test_winnow_identical_text_identical_fingerprint():
|
||||
assert winnow(LONG) == winnow(LONG)
|
||||
|
||||
|
||||
def test_winnow_is_case_insensitive():
|
||||
assert winnow(LONG) == winnow(LONG.upper())
|
||||
|
||||
|
||||
def test_jaccard_identical_is_one():
|
||||
fp = winnow(LONG)
|
||||
assert fp # непустой отпечаток
|
||||
assert jaccard_similarity(fp, fp) == 1.0
|
||||
|
||||
|
||||
def test_jaccard_disjoint_is_zero():
|
||||
assert jaccard_similarity({1, 2, 3}, {4, 5, 6}) == 0.0
|
||||
|
||||
|
||||
def test_jaccard_empty_is_zero():
|
||||
assert jaccard_similarity(set(), {1, 2}) == 0.0
|
||||
|
||||
|
||||
def test_compute_similarity_identical_documents_is_one():
|
||||
# Ключевая гарантия L1: копия документа детектится как 100% совпадение
|
||||
assert compute_similarity(LONG, LONG) == 1.0
|
||||
|
||||
|
||||
def test_compute_similarity_unrelated_documents_is_low():
|
||||
other = (
|
||||
"рецепт домашнего хлеба на закваске требует терпения муки воды соли "
|
||||
"и тёплого места для медленного подъёма теста в течение ночи"
|
||||
)
|
||||
assert compute_similarity(LONG, other) < 0.1
|
||||
|
||||
|
||||
def test_compute_similarity_partial_overlap_is_between():
|
||||
modified = LONG + " добавлен ещё один совершенно новый хвост предложения здесь"
|
||||
sim = compute_similarity(LONG, modified)
|
||||
assert 0.0 < sim < 1.0
|
||||
Reference in New Issue
Block a user