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:
6
services/worker-gost/conftest.py
Normal file
6
services/worker-gost/conftest.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Добавляет корень сервиса в sys.path, чтобы тесты импортировали пакет `app`."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
3
services/worker-gost/pytest.ini
Normal file
3
services/worker-gost/pytest.ini
Normal file
@@ -0,0 +1,3 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
addopts = -q
|
||||
2
services/worker-gost/requirements-test.txt
Normal file
2
services/worker-gost/requirements-test.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
# Зависимости для юнит-тестов worker-gost (ГОСТ-форматтеры — чистая логика).
|
||||
pytest==8.2.0
|
||||
32
services/worker-gost/tests/test_gost_7_0_5.py
Normal file
32
services/worker-gost/tests/test_gost_7_0_5.py
Normal 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]"
|
||||
80
services/worker-gost/tests/test_gost_7_1.py
Normal file
80
services/worker-gost/tests/test_gost_7_1.py
Normal 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)
|
||||
Reference in New Issue
Block a user