Files
contracts-flask/tests/README.md
T

5.2 KiB
Raw Blame History

Тесты 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, /upload, /unzip-upload, /api/groups
test_upload_security.py routes/upload_bp.py: _check_ext (допустимые/недопустимые), _unzip (path traversal нейтрализуется, битый zip)

Ключевые проверки

  • test_full_replace_no_duplicates — два full_replace подряд → в spec_current 3 строки, а не 6; история spec_events (6 событий) сохранена.
  • test_update_appliestarget_id: "r1" транслируется в target_hash → UPDATE применяется (status applied), а не уходит в UNRESOLVED.
  • test_path_traversal_neutralized../etc/passwd внутри ZIP → имя файла passwd без пути.

Изоляция

  • Каждый тест, работающий с БД, получает свою копию SQLite через фикстуру db (monkeypatch DB_PATHtmp_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-стресс (/upload, /health) — параллельные запросы, RPS/ошибки/таймауты

Масштаб через переменные окружения (дефолты — большие):

LOAD_CONTRACTS=100 LOAD_SUPPS=20 LOAD_SERVICES=10 \
LOAD_OPS=5000 LOAD_THREADS=8 LOAD_PER=200 \
pytest -m load -q

Примечание: `LOAD_THREADS >= 16` — стресс-режим конкурентной записи:
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