Docs: test history/results plan for Sonnet review
This commit is contained in:
@@ -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 потоком или файлом?
|
||||||
Reference in New Issue
Block a user