# История тестов и просмотр результатов — план реализации > Написано 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/` — стадии ## Проблема В 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/` Полные детали: все стадии + все параметры. ### `DELETE /api/results/` Удалить запись. ### `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/` — детали 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 потоком или файлом?