Files
anti-plagiarism/services/worker-indexer/tests/test_winnowing.py
jze9 ecc10a4413 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>
2026-08-11 17:11:08 +05:00

80 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Юнит-тесты алгоритма 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