4.9 KiB
4.9 KiB
Тесты contracts-flask
Набор pytest-тестов под актуальный код сервиса (site/).
Покрытие: юнит (чистая логика), интеграционные (SQLite), HTTP-эндпоинты (Flask test client), безопасность.
Запуск
cd contracts-flask
pytest tests/ -q
pytest установлен в venv: /home/naeel/nubes/contracts/.venv/bin/python -m pytest tests/ -q.
Файлы
| Файл | Что тестирует |
|---|---|
conftest.py |
site/ в sys.path; фикстура db — изолированная БД (временный DB_PATH + свежая схема); защита реальной /tmp/contracts.db от пересоздания |
test_metrics.py |
services/metrics.py: normalize_date, check_arithmetic (sum == price×qty), _to_decimal, ClassifyMetrics |
test_grouping.py |
services/grouping.py: normalize_number, group_documents (группировка contract+supplement, unresolved), apply_groups (mock db) |
test_llm_client.py |
services/llm_client.py: FakeLLMClient (точное/частичное совпадение, default, история вызовов), HttpxLLMClient |
test_llm.py |
services/llm.py: call_llm — парсинг plain JSON и markdown-обёрток (```json) |
test_classify.py |
services/classify.py: garbage-фильтры по имени/заголовку, _safe_json_parse (7 edge-case: trailing comma, chatter, пусто), _smart_extract |
test_parse.py |
services/parse.py: parse_file (text/docx/pdf, ошибки), _parse_text |
test_connection.py |
db/connection.py: _pg_to_sqlite (все замены %s/::jsonb/BOOLEAN/ILIKE/TRUE), _extract_table |
test_spec_events.py |
db/spec_events.py: apply_ops (ADD/UPDATE/DELETE/UNRESOLVED/unknown), clear_current, reset, _hash, get_next_seq |
test_spec_current.py |
db/spec_current.py: list_by_contract (порядок), get_elements_json |
test_process_pipeline.py |
services/process.py: run_pipeline с Fake LLM — full_replace не дублирует строки, трансляция target_id→target_hash, _elements_to_text |
test_routes.py |
HTTP-эндпоинты (Flask test client): /health, /api/spec-current, /api/groups |
Ключевые проверки
test_full_replace_no_duplicates— дваfull_replaceподряд → вspec_current3 строки, а не 6; историяspec_events(6 событий) сохранена.test_update_applies—target_id: "r1"транслируется вtarget_hash→ UPDATE применяется (statusapplied), а не уходит в UNRESOLVED.
Изоляция
- Каждый тест, работающий с БД, получает свою копию SQLite через фикстуру
db(monkeypatchDB_PATH→tmp_path,init_db()). - LLM-вызовы в пайплайне подменяются через
monkeypatch.setattr("services.llm.call_llm", ...)— сеть не используется.
Нагрузочные тесты (tests/load/, маркер load)
Долгие стресс-тесты, реально нагружают SQLite/пайплайн. Запуск отдельно:
pytest -m load -q # только нагрузочные
pytest -m "not load" -q # быстрые юнит-тесты без нагрузочных
| Файл | Что нагружает |
|---|---|
load/test_load_pipeline.py |
массовый run_pipeline: N контрактов × M допников (Fake LLM) — проверка, что full_replace не дублирует строки в масштабе |
load/test_load_apply_ops.py |
массовые apply_ops: N ADD → N UPDATE → N DELETE, целостность spec_events |
load/test_load_concurrency.py |
конкурентная запись в SQLite (WAL + busy_timeout), проверка отсутствия потери данных |
load/stress_http.py |
standalone HTTP-стресс: PUT на ВМ → POST /api/upload_refs (реальный VM-путь) + /health |
Масштаб через переменные окружения (дефолты — большие):
LOAD_CONTRACTS=100 LOAD_SUPPS=20 LOAD_SERVICES=10 \
LOAD_OPS=5000 LOAD_THREADS=4 LOAD_PER=200 \
pytest -m load -q
Примечание по конкурентной записи: стабильный уровень `LOAD_THREADS=4`
(= `MAX_WORKERS` приложения). `>= 8` — нестабильно: SQLite с
`busy_timeout=5000` даёт `database is locked` (см. `History/2026-08-26-load-sqlite-locked.md`).
HTTP-стресс против локального/прод сервиса:
python tests/load/stress_http.py --url http://127.0.0.1:5000 --n 1000 --threads 32 --size 100000