Files
autotest/DOCS/test-results-history-plan.md
2026-07-27 22:05:19 +04:00

10 KiB
Raw Permalink Blame History

⚠️ LEGACY — НЕАКТУАЛЬНО. Исторический документ.

История тестов и просмотр результатов — план реализации

Написано 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 — фильтрация, сортировка, аггрегация (статистика).

Схема БД

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 — смещение

Ответ:

{
  "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)

  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 потоком или файлом?