Docs: test history/results plan for Sonnet review

This commit is contained in:
2026-07-26 14:36:18 +04:00
parent ff10d894ae
commit 46d5d6ae5c
+260
View File
@@ -0,0 +1,260 @@
# История тестов и просмотр результатов — план реализации
> Написано 2026-07-26 для ревью Соннетом перед реализацией.
## Контекст
Приложение `app-autotest` (Flask, `site/app.py`) — автотесты операций облачных сервисов Nubes.
Работает с dummy-сервисом (svcId=1, Болванка). Уже реализовано:
- UI: инфраструктура, сервисы, инстансы, кнопки операций, стадии в реальном времени
- Бэкенд: полный поток CREATE/MODIFY/SUSPEND/DELETE/RESUME/REDEPLOY 1:1 с Terraform-провайдером
- Трекер: `site/operations/tracker.py``/tmp/instances.json` (только инстансы созданные приложением)
- Поллинг: `/api/test` возвращает `opUid` сразу, `/api/test/status/<opUid>` — стадии
## Проблема
В deck-test (UI облака) для каждого инстанса хранится **полная история операций**:
список всех modify/suspend/delete/... с датами, статусами, этапами, параметрами. Можно открыть любую операцию и посмотреть как выполнялась.
У нас история **не сохраняется**. После завершения операции результат теряется. Нельзя:
- Посмотреть что и когда запускали
- Сравнить два запуска
- Понять какая операция упала и почему
## Цель
Сделать аналог истории deck-test, **но лучше** — потому что наше приложение не просто UI для сервиса, а **инструмент тестирования**.
## Что есть в deck-test (базовый минимум)
| Фича | Описание |
|------|---------|
| Список операций инстанса | Таблица: UUID, название, дата, код ответа, начало, окончание |
| Детали операции | Параметры, этапы (stages), статус, длительность, ошибка |
| Фильтр по типу | Показать только modify / только suspend / ... |
## Что МОЖЕМ сделать лучше (тест-ориентированные фичи)
| Фича | Зачем |
|------|-------|
| **Сводная таблица всех тестов** (кросс-инстанс) | Не прыгать между инстансами — видеть всё в одном месте |
| **Статистика:** % успешных, среднее время по операции | Быстро понять что ломается чаще всего |
| **Сравнение двух запусков** | «После modify время выросло на 2 секунды» |
| **Экспорт в JSON** | Забрать результаты в CI/CD пайплайн |
| **Повторный запуск** из истории (те же параметры) | Не заполнять форму заново |
| **Таймлайн** с цветовой кодировкой | Визуально видеть последовательность: create → modify → suspend → resume → delete |
## Архитектура хранения
Сейчас: `instances.json` (JSON-файл в `/tmp/`). Для истории нужна БД.
### Варианты
1. **SQLite** в `/tmp/autotest_results.db` — ноль зависимостей, встроен в Python
2. JSON-файл `/tmp/results.json` — проще, но неудобно для запросов/фильтрации
**Выбран SQLite** — фильтрация, сортировка, аггрегация (статистика).
### Схема БД
```sql
CREATE TABLE test_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
instance_uid TEXT NOT NULL,
instance_name TEXT NOT NULL,
operation TEXT NOT NULL, -- create/modify/suspend/delete/resume/redeploy
svc_operation_id INTEGER NOT NULL,
op_uid TEXT NOT NULL, -- UUID операции в облаке
status TEXT NOT NULL, -- OK/FAIL/TIMEOUT
error TEXT DEFAULT '',
duration_ms REAL DEFAULT 0, -- миллисекунды
started_at TEXT NOT NULL, -- ISO datetime
finished_at TEXT DEFAULT ''
);
CREATE TABLE test_stages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
test_run_id INTEGER NOT NULL REFERENCES test_runs(id),
stage_name TEXT NOT NULL, -- "1. Валидация"
is_successful INTEGER, -- 0/1/NULL
duration_sec REAL DEFAULT 0,
stage_msg TEXT DEFAULT '',
sort_order INTEGER DEFAULT 0
);
CREATE TABLE test_params (
id INTEGER PRIMARY KEY AUTOINCREMENT,
test_run_id INTEGER NOT NULL REFERENCES test_runs(id),
param_name TEXT NOT NULL,
param_value TEXT NOT NULL
);
```
### Почему SQLite а не `/tmp/*.json`
- `/tmp/` на поде **не затирается** при clone (в отличие от `site/`)
- SQLite даёт `WHERE`, `ORDER BY`, `GROUP BY` из коробки
- Файл БД можно скачать для офлайн-анализа
- Нет дополнительных зависимостей (sqlite3 встроен в Python)
## API эндпоинты
### `GET /api/results`
Параметры:
- `instance_uid` — фильтр по инстансу
- `operation` — фильтр по типу
- `status` — OK/FAIL/TIMEOUT
- `limit` — сколько (по умолчанию 50)
- `offset` — смещение
Ответ:
```json
{
"total": 42,
"results": [
{
"id": 1,
"instance_uid": "884356cf-...",
"instance_name": "autotest-1-first",
"operation": "modify",
"status": "OK",
"duration_ms": 4200,
"started_at": "2026-07-26T14:12:18",
"stages_count": 5,
"stages_failed": 0
}
]
}
```
### `GET /api/results/<id>`
Полные детали: все стадии + все параметры.
### `DELETE /api/results/<id>`
Удалить запись.
### `GET /api/results/stats`
Аггрегация по операциям:
```json
{
"modify": {"total": 10, "ok": 9, "fail": 1, "avg_duration_ms": 5200},
"suspend": {"total": 8, "ok": 8, "fail": 0, "avg_duration_ms": 3400}
}
```
## Интеграция с текущим кодом
### `site/operations/results_db.py` (новый)
```python
import sqlite3
import os
DB_PATH = "/tmp/autotest_results.db"
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
_ensure_tables(conn)
return conn
def save_result(instance_uid, instance_name, operation, svc_op_id, op_uid, status, error, duration_ms, stages, params):
...
def get_results(instance_uid=None, operation=None, status=None, limit=50, offset=0):
...
def get_result(id):
...
def delete_result(id):
...
def get_stats():
...
```
### Изменения в `site/routes/api_test.py`
В `_finish_op()` — после завершения операции:
```python
from operations.results_db import save_result
save_result(
instance_uid=instance_uid,
instance_name=display_name,
operation="modify",
svc_op_id=svc_op_id,
op_uid=op_uid,
status=status,
error=error,
duration_ms=duration_ms,
stages=stages,
params=params
)
```
Добавить 4 новых роута для `/api/results*`.
## UI
### Новая карточка «История тестов»
Под карточкой «Инстансы»:
```
┌─────────────────────────────────────────┐
│ История тестов │
│ [Все▼] [create] [modify] [suspend] ... │ ← фильтры-кнопки
│ │
│ # Инстанс Операция Рез-т │
│ 1 autotest-1-first modify ✅ 4.2s │ ← кликабельно
│ 2 curl-test-144834 suspend ✅ 3.1s │
│ 3 autotest-1-first delete ❌ err │
│ │
│ Статистика: modify 9/10 OK (90%) ... │
└─────────────────────────────────────────┘
```
Клик по строке → раскрываются детали (стадии + параметры).
### Цветовая кодировка
- ✅ зелёный — OK
- ❌ красный — FAIL
- ⏱ жёлтый — TIMEOUT
## План реализации (фазы)
### Фаза 1: база + сохранение (MVP)
1. `site/operations/results_db.py` — модуль SQLite
2. `_finish_op()` — сохранение результата
3. `GET /api/results` — базовый список
4. UI — простая таблица без фильтров
### Фаза 2: детали + фильтры
1. `GET /api/results/<id>` — детали
2. `GET /api/results/stats` — статистика
3. UI — фильтры, раскрытие деталей, статистика
### Фаза 3: продвинутые фичи
1. Экспорт JSON
2. Повторный запуск из истории
3. Таймлайн
4. Сравнение запусков
## Вопросы к Соннету
1. SQLite в `/tmp/` на поде — насколько это надёжно для тестовой истории? Может лучше PostgreSQL-сервис Nubes?
2. Схема БД — нормально или что-то упущено?
3. Стоит ли хранить `stageMsg` (большие JSON-строки с логами Jenkins)?
4. API эндпоинты — достаточный набор или добавить что-то?
5. UI — есть ли смысл делать SPA-подобный роутинг или оставить серверный рендеринг как сейчас?
6. Экспорт результатов — JSON потоком или файлом?