Files
autotest/DOCS/test-results-history-plan.md
T

261 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# История тестов и просмотр результатов — план реализации
> Написано 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 потоком или файлом?