v1.0.178: документация результатов тестирования (131 тест, API smoke, баги)
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
# Результаты тестирования — v1.0.178
|
||||
|
||||
Дата: 25.06.2026
|
||||
|
||||
## Сводка
|
||||
|
||||
| Уровень | Тестов | Результат |
|
||||
|---------|--------|-----------|
|
||||
| Юнит-тесты (Node.js) | **131** | **✅ 131/131 PASS** |
|
||||
| `node -c` синтаксис | 6 файлов | ✅ чистый |
|
||||
| API smoke (curl) | 8 эндпоинтов | ✅ HTTP 200 |
|
||||
| Аудит кода | — | ✅ 4 бага исправлено |
|
||||
|
||||
## Запуск тестов
|
||||
|
||||
```bash
|
||||
cd contractor/deploy && node tests.js
|
||||
```
|
||||
|
||||
Тесты не требуют браузера — все зависимости замоканы (document, window, lucide, crypto, XMLHttpRequest).
|
||||
|
||||
---
|
||||
|
||||
## Юнит-тесты (131 шт.)
|
||||
|
||||
### statusToHTML (21 тест)
|
||||
Чистая функция: `{ kind, pct?, text?, count?, elapsed? }` → HTML.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Граничные | `null`, `undefined`, `{}`, `{ kind: '' }`, `{ kind: 'unknown' }` → пусто |
|
||||
| `uploading` | pct=0, 75, 100, -5, без pct (→0%) |
|
||||
| `uploaded` | базовая проверка |
|
||||
| `unzipping` / `parsing` | текстовые статусы |
|
||||
| `parsed` | count=5, 0; elapsed='2.3', '0', без elapsed |
|
||||
| `error` | с текстом, без текста (→дефолт), с пустым текстом (→дефолт) |
|
||||
|
||||
### applyParseResult (13 тестов)
|
||||
Чистая функция: применяет `{ status, element_count, error }` к entry.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| `parsed` | с elapsed, без elapsed, element_count=0, без element_count |
|
||||
| `error` | с текстом, без текста (→дефолт "ошибка парсинга") |
|
||||
| `null` parsed | → `{ kind: 'error', text: 'Неизвестная ошибка' }` |
|
||||
| Иммутабельность | старые поля entry не затираются |
|
||||
|
||||
### reconcileSelection (7 тестов)
|
||||
Чистая функция: фильтрует `files` по именам из `newFiles`.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Норма | 3 файла → 2 осталось, порядок сохранён |
|
||||
| Пустые | оба массива пусты → `[]` |
|
||||
| Все совпадают | 1→1 |
|
||||
| Ни один | 2→0 |
|
||||
| Дубликаты | Set корректно обрабатывает повторы |
|
||||
| Иммутабельность | исходный массив не мутирован |
|
||||
|
||||
### renderGroupCard (10 тестов)
|
||||
Чистая HTML-функция: карточка необработанной группы.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Базовая | номер, контрагент, типы документов, дата, data-gi, кнопка «Сравнить» |
|
||||
| Нет «✓ Готово» | для необработанной группы |
|
||||
| `compare.status === 'running'` | кнопка disabled |
|
||||
| Без контрагента | заглушка «контрагент не определён» |
|
||||
| Неизвестный doc_type | выводится как есть |
|
||||
| Пустые документы | карточка рендерится, кнопка есть |
|
||||
| Типы | contract→договор, supplement→допсоглашение, specification→спецификация |
|
||||
|
||||
### renderGroupCardDone (5 тестов)
|
||||
Чистая HTML-функция: карточка обработанной группы.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Заголовок | ✓ Готово (12.5с), стрелка сворачивания |
|
||||
| bodyHTML | вставлен в карточку |
|
||||
| data-gi | на заголовке для обработчика клика |
|
||||
| Типы | specification→спецификация |
|
||||
|
||||
### renderUnresolvedCard (3 теста)
|
||||
Чистая HTML-функция: карточка нераспознанных файлов.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Заголовок | «Не распознано» |
|
||||
| Причина 1 | «нет базового договора №999» (есть parent_number) |
|
||||
| Причина 2 | «ошибка классификации: LLM error» (classify_status=failed) |
|
||||
|
||||
### applyCompareEvent (14 тестов)
|
||||
Чистая функция: мутирует `sections` по SSE-событиям.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| `extract_start` | создаёт секцию с filename, status='extracting' |
|
||||
| `llm_done` | обновляет status, ops_count, mode, time_s |
|
||||
| `applied` | сохраняет summary, ops |
|
||||
| `extract_error` | на существующей секции → status='error' |
|
||||
| `extract_error` | на НЕсуществующей → создаёт новую с ошибкой |
|
||||
| `apply_error` | аналогично extract_error |
|
||||
| `applied` без summary | не падает |
|
||||
| `applied` с пустыми ops | не падает |
|
||||
| Неизвестный тип | секция НЕ создаётся (не падает) |
|
||||
| `llm_done` на несуществующей | не падает |
|
||||
|
||||
### renderCompareSectionHeader (5 тестов)
|
||||
Чистая функция: заголовок секции сравнения.
|
||||
|
||||
| Статус | Проверка |
|
||||
|--------|----------|
|
||||
| `extracting` | ⏳ + filename |
|
||||
| `llm_done` | ✓ + filename + ops_count + mode + time_s |
|
||||
| `error` | ✗ + filename + текст ошибки |
|
||||
|
||||
### renderCompareOpsTable (6 тестов)
|
||||
Чистая функция: HTML-таблица операций.
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| ADD/DELETE | правильные CSS-классы (diff-added, diff-deleted) |
|
||||
| Данные | имя услуги, цена, количество, сумма, дата |
|
||||
| Пустые ops | таблица рендерится с пустым tbody |
|
||||
| null new_row | пустые ячейки (не падает) |
|
||||
| MERGE (неизв.) | без классов diff-* |
|
||||
|
||||
### renderCompareSectionBody (7 тестов)
|
||||
Чистая функция: тело секции (summary + таблица).
|
||||
|
||||
| Группа | Тесты |
|
||||
|--------|-------|
|
||||
| Граничные | null, {}, status='llm_done' → пусто |
|
||||
| applied | +3 ~1 -0 ?2, UPDATE → diff-changed |
|
||||
| Без unresolved | нет знака ? |
|
||||
| Отрицательные summary | рендерится без ошибок |
|
||||
| Частичный new_row | нет undefined в выводе |
|
||||
|
||||
### escHtml (7 тестов)
|
||||
| Вход | Выход |
|
||||
|------|-------|
|
||||
| `null`, `undefined` | `''` |
|
||||
| `'hello'` | `'hello'` |
|
||||
| `'<script>'` | `'<script>'` |
|
||||
| `'a&b'` | `'a&b'` |
|
||||
| `'"quote"'` | `'"quote"'` |
|
||||
| `123` | `'123'` |
|
||||
|
||||
---
|
||||
|
||||
## API Smoke Tests (curl)
|
||||
|
||||
| Эндпоинт | Код |
|
||||
|----------|-----|
|
||||
| `GET /` | 200 |
|
||||
| `POST /api/cleanup` | 200 |
|
||||
| `GET /static/state.js` | 200 |
|
||||
| `GET /static/app_utils.js` | 200 |
|
||||
| `GET /static/files.js` | 200 |
|
||||
| `GET /static/groups.js` | 200 |
|
||||
| `GET /static/compare.js` | 200 |
|
||||
| `GET /static/app.js` | 200 |
|
||||
|
||||
---
|
||||
|
||||
## Баги, найденные и исправленные при аудите
|
||||
|
||||
| # | Баг | Файл | Исправление |
|
||||
|---|-----|------|-------------|
|
||||
| 1 | `SITE_URL` — неиспользуемая переменная | app.js:8 | Удалена |
|
||||
| 2 | `UNZIP_URL` — двойное объявление | app.js:6-7 | Удалён дубль |
|
||||
| 3 | `promptEditor` — двойное объявление | app.js:467,470 | Удалён дубль |
|
||||
| 4 | `llmStart` — неиспользуемая после Фазы 3 | app.js:301 | Удалена |
|
||||
|
||||
---
|
||||
|
||||
## Структура модулей (финальная)
|
||||
|
||||
```
|
||||
contractor/deploy/
|
||||
├── state.js (70 строк) Центральное состояние
|
||||
├── app_utils.js (120 строк) escHtml, formatSize, removeFile, ...
|
||||
├── files.js (480 строк) upload, parse, ZIP, renderFiles, syncDB
|
||||
├── groups.js (230 строк) loadGroupsAction, renderGroups, renderGroupCard
|
||||
├── compare.js (230 строк) startCompareSSE, applyCompareEvent, renderCompareOpsTable
|
||||
├── app.js (450 строк) Оркестратор: render, classify, compare, prompts, chat
|
||||
└── tests.js (400 строк) 131 юнит-тест
|
||||
```
|
||||
|
||||
Загрузка в `index.cfm`: state → app_utils → files → groups → compare → app.
|
||||
|
||||
---
|
||||
|
||||
## Команды для регресса
|
||||
|
||||
```bash
|
||||
# Синтаксис всех модулей
|
||||
cd contractor/deploy && for f in state.js app_utils.js files.js groups.js compare.js app.js; do node -c "$f" || echo "FAIL: $f"; done
|
||||
|
||||
# Юнит-тесты
|
||||
node tests.js
|
||||
|
||||
# Деплой JS на ВМ
|
||||
scp -i ~/.ssh/naeel_vm_id_ed25519 *.js naeel@5.172.178.213:/home/naeel/contracts/
|
||||
|
||||
# Дым API
|
||||
curl -s --max-time 5 -o /dev/null -w "%{http_code}\n" https://contracts.kube5s.ru/static/state.js
|
||||
```
|
||||
Reference in New Issue
Block a user