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:
jze9
2026-08-11 17:11:08 +05:00
parent a2d3625f2a
commit ecc10a4413
17 changed files with 480 additions and 4 deletions

View File

@@ -0,0 +1,32 @@
"""Юнит-тесты кратких библиографических ссылок по ГОСТ Р 7.0.5-2008."""
from app.formatters.gost_7_0_5 import GOST705Formatter, format_short
def test_short_author_and_year():
assert format_short({"authors": [{"last_name": "Иванов"}], "year": 2023}) == "[Иванов, 2023]"
def test_short_without_author_uses_title():
out = format_short({"title": "Большая книга про всё сразу", "year": 2020})
assert out == "[Большая книга про..., 2020]"
def test_short_no_author_no_title():
assert format_short({"year": 2019}) == "[2019]"
def test_short_missing_year_defaults():
assert format_short({"authors": [{"last_name": "Петров"}]}) == "[Петров, б. г.]"
def test_inline_with_page():
f = GOST705Formatter()
doc = {"authors": [{"last_name": "Иванов"}], "year": 2023}
assert f.format_inline(doc, page="15") == "[Иванов, 2023, с. 15]"
def test_inline_without_page():
f = GOST705Formatter()
doc = {"authors": [{"last_name": "Иванов"}], "year": 2023}
assert f.format_inline(doc) == "[Иванов, 2023]"

View File

@@ -0,0 +1,80 @@
"""Юнит-тесты полного библиографического описания по ГОСТ 7.1-2003."""
from app.formatters.gost_7_1 import (
GOST71Formatter,
_format_author,
_format_authors,
_get_sort_key,
format_full,
)
def test_author_with_initials():
assert _format_author({"last_name": "Иванов", "initials": "И. И."}) == "Иванов И. И."
def test_author_initials_get_trailing_dot():
assert _format_author({"last_name": "Иванов", "initials": "И. И"}) == "Иванов И. И."
def test_author_from_first_name():
assert (
_format_author({"last_name": "Петров", "first_name": "Пётр Петрович"})
== "Петров П.П."
)
def test_author_last_name_only():
assert _format_author({"last_name": "Сидоров"}) == "Сидоров"
def test_author_empty():
assert _format_author({}) == ""
def test_authors_up_to_three_all_listed():
authors = [{"last_name": "А"}, {"last_name": "Б"}, {"last_name": "В"}]
assert _format_authors(authors) == "А, Б, В"
def test_authors_four_plus_truncated_ru():
authors = [{"last_name": n} for n in ("А", "Б", "В", "Г")]
assert _format_authors(authors, lang="ru") == "А, Б, В, и др."
def test_authors_four_plus_truncated_en():
authors = [{"last_name": n} for n in ("A", "B", "C", "D")]
assert _format_authors(authors, lang="en") == "A, B, C, et al."
def test_article_has_journal_year_and_doi():
doc = {
"title": "Заголовок статьи",
"journal": "Вестник науки",
"year": 2023,
"volume": "5",
"issue": "2",
"pages": "10-20",
"doi": "10.1234/abc",
"authors": [{"last_name": "Иванов", "initials": "И.И."}],
}
out = format_full(doc)
assert out.startswith("Иванов И.И.. Заголовок статьи")
assert "// Вестник науки" in out
assert "— 2023" in out
assert "Т. 5" in out and "№ 2" in out
assert "С. 10-20" in out
assert out.endswith("DOI: 10.1234/abc")
def test_web_resource_marked_and_has_url():
doc = {"title": "Портал", "url": "https://example.org", "year": 2024}
out = GOST71Formatter().format_full(doc)
assert "[Электронный ресурс]" in out
assert "URL: https://example.org" in out
def test_sort_key_cyrillic_before_latin():
ru = _get_sort_key({"authors": [{"last_name": "Яковлев"}]})
en = _get_sort_key({"authors": [{"last_name": "Adams"}]})
assert ru < en # кириллица (префикс 0) сортируется раньше латиницы (префикс 1)