10 KiB
История тестов и просмотр результатов — план реализации
Написано 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/). Для истории нужна БД.
Варианты
- SQLite в
/tmp/autotest_results.db— ноль зависимостей, встроен в Python - JSON-файл
/tmp/results.json— проще, но неудобно для запросов/фильтрации
Выбран SQLite — фильтрация, сортировка, аггрегация (статистика).
Схема БД
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/TIMEOUTlimit— сколько (по умолчанию 50)offset— смещение
Ответ:
{
"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
Аггрегация по операциям:
{
"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 (новый)
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() — после завершения операции:
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)
site/operations/results_db.py— модуль SQLite_finish_op()— сохранение результатаGET /api/results— базовый список- UI — простая таблица без фильтров
Фаза 2: детали + фильтры
GET /api/results/<id>— деталиGET /api/results/stats— статистика- UI — фильтры, раскрытие деталей, статистика
Фаза 3: продвинутые фичи
- Экспорт JSON
- Повторный запуск из истории
- Таймлайн
- Сравнение запусков
Вопросы к Соннету
- SQLite в
/tmp/на поде — насколько это надёжно для тестовой истории? Может лучше PostgreSQL-сервис Nubes? - Схема БД — нормально или что-то упущено?
- Стоит ли хранить
stageMsg(большие JSON-строки с логами Jenkins)? - API эндпоинты — достаточный набор или добавить что-то?
- UI — есть ли смысл делать SPA-подобный роутинг или оставить серверный рендеринг как сейчас?
- Экспорт результатов — JSON потоком или файлом?