Files
app-autotest/DOCS/HISTORY.md
T

27 KiB
Raw Blame History

История разработки app-autotest

v1.0.89 (27.07.2026) — Sonnet Round 3: практические вопросы

1. PostgreSQL connection string

  • os.getenv("DATABASE_URL") — никакого service discovery
  • Инжектится через jsonEnv параметры pythonk8s-сервиса (как NUBES_API_TOKEN)
  • Порядок: создать PostgreSQL-инстанс в Nubes → взять host/port/user/pass из state_out → собрать DSN → jsonEnv → деплой

2. Миграции при деплое

  • init_db() на уровне модуля в app.py, до register_blueprint
  • CREATE TABLE/INDEX IF NOT EXISTS — идемпотентно, каждый воркер выполнит независимо
  • PostgreSQL обрабатывает конкурентный DDL корректно
  • Никаких ручных шагов при деплое
  • DROP COLUMN — не идемпотентно, осторожно

3. Multi-service UI (map-fixed)

  • dataDescriptor уже в API-ответе — инфраструктура готова
  • map (свободный): textarea + JSON-валидация (как сейчас)
  • map-fixed: таблица подполей с отдельными input/select из dataDescriptor
  • Бэкенд не меняется — paramValue всегда строка
  • Изменения только во фронте: showParams() + сбор подполей в JSON перед отправкой

4. Тесты

  • pytest для чистых функций без моков: _find_uid, _uid_from_location, _with_prefix, слияние params, tracker
  • Структура: tests/conftest.py, test_utils.py, test_tracker.py, test_get_params.py
  • Роуты с Flask test client + mock HttpClient — отложить (высокая стоимость мокирования)

v1.0.89 (27.07.2026) — Sonnet Round 2: PostgreSQL, auth.py, детальный план

Результаты анализа

Sonnet дал конкретные ответы на все 7 вопросов Round 2:

1. PostgreSQL схема:

  • Таблица runs с JSONB-полями params и stages
  • 3 индекса: (client_id, stand, created_at DESC), (instance_uid), (created_at)
  • Stages в JSONB (не отдельная таблица), instances не нужны (API — source of truth)
  • Чувствительные params маскировать при сохранении

2. Connection pool:

  • psycopg2.pool.ThreadedConnectionPool — минимум зависимостей
  • Lazy-init в db/pool.py — безопасно для fork-модели gunicorn
  • flask.g + @app.teardown_appcontext для возврата соединений

3. Миграции:

  • db/init_db.py с IF NOT EXISTS — идемпотентно, авто-применение при старте
  • Alembic — когда 3+ таблицы или нужен rollback (сейчас не нужно)

4. Трекер vs PostgreSQL:

  • НЕ заменять трекер на БД — это разные слои
  • Трекер = кеш (секунды), PostgreSQL = история (недели)
  • Сетевое обращение к БД на критическом пути CREATE недопустимо

5. auth.py:

  • Явные функции вместо before_request — проще, нет скрытых зависимостей
  • Одна реализация get_client_id() вместо двух копий
  • Кеширование token → endpoint (опционально, TTL 5 мин)

6. Динамические сервисы:

  • /api/services из API → фильтр по enabled: true из config.yaml
  • UI: заменить хардкод SVC_ID=1 на динамическую загрузку в selectService()

7. JS в файл:

  • Классический скрипт, один файл app.js (300 строк — не нужно ES modules)
  • window.APP = {...} через tojson фильтр Jinja2 для передачи переменных

Итоговый порядок реализации (9 шагов)

  1. api/auth.py — вынести get_token/get_client/get_client_id/get_stand
  2. Заменить дублирующиеся функции в main.py и api_test.py
  3. Cookie httponly=True, samesite='Strict'
  4. _op_results TTL/очистка
  5. JS вынести в /static/app.js + window.APP
  6. Динамические сервисы из /api/services
  7. db/pool.py + db/init_db.py (PostgreSQL)
  8. Запись истории в runs при завершении операции
  9. /api/history эндпоинт + UI

v1.0.89 (27.07.2026) — Sonnet Round 1: архитектурный аудит

  • Cloud-first подход с tracker-fallback
  • HTML-экранирование в JS

🔴 Критические находки:

  1. _op_results dict растёт бесконечно — утечка памяти, нужен TTL/очистка
  2. Cookie токена без httponly и samesite
  3. /api/log без проверки авторизации

🟡 Дублирование:

  1. _client_id() — идентичный код в main.py и api_test.py
  2. _client() — разное поведение в main.py и api_test.py
  3. Инлайн <style> дублирует style.css

🟡 Захардкодено:

  1. SVC_ID=1 и список сервисов в HTML — хотя /api/services существует
  2. create_client() в main.py vs _client() в api_test.py — разные подходы

Приоритетный план исправлений:

  1. 🔴 _op_results — добавить очистку/TTL
  2. 🔴 Cookie httponly=True, samesite='Strict'
  3. 🟡 Вынести _client_id() / get_token() в общий модуль api/auth.py
  4. 🟡 Загрузка сервисов из /api/services вместо хардкода
  5. 🟡 Вынести JS в /static/app.js
  6. 🟢 SQLite история запусков
  7. 🟢 /api/log проверка токена

Создан запрос Round 2

Файл: DOCS/sonnet-architecture-review-v1.0.89-r2.md — уточняющие вопросы по реализации.

v1.0.89 (27.07.2026) — документация и комментарии кода

v1.0.87 (27.07.2026) — документация: ARCHITECTURE.md переписан, legacy-доки помечены

v1.0.84 (27.07.2026) — лог-панель скрыта по умолчанию

Что изменилось

  • Лог-панель теперь display:none, показывается кнопкой log (правый нижний угол).
  • При скрытой панели /api/log не поллится.

v1.0.83 (27.07.2026) — HTML-escape + JSON-валидация map-полей

Что изменилось

  • _esc(s) — HTML-экранирование значений ("&quot;, &&amp;, <&lt;).
  • validateJson(el, quiet) — проверка JSON.parse() при onblur (красная рамка + текст ошибки).
  • Batch-проверка всех map-полей перед отправкой — ошибка → запрос не уходит.

Что это чинит

  • Поломку HTML при значениях с кавычками (напр. {"f":1}).
  • Отправку битого JSON в API.

v1.0.82 (27.07.2026) — displayName из API для non-create операций

Что изменилось

  • _get_instance_display_name(client, uid)GET /instances/{uid}displayName.
  • Используется в не-create ветке POST /api/test вместо instance_uid как fallback.

Что это чинит

  • UUID вместо имени инстанса в финальном статусе.

v1.0.81 (27.07.2026) — get_params.py: текущие значения из state.params

Что изменилось

  • Новый файл site/operations/get_params.py — независимый модуль.
  • get_params_with_current_values(client, op_id, instance_uid):
    • GET /instances/{uid}state.params (текущие значения)
    • GET /instanceOperations/default/{opId} → шаблон
    • Слияние: defaultValue = state.params["код"] ?? template.defaultValue
  • Убран previewOpUid полностью (и из бэкенда, и из фронтенда).

v1.0.80 (27.07.2026) — попытка fix через svcOperationId (НЕ СРАБОТАЛО)

v1.0.79 (27.07.2026) — файловый лог для multi-worker

Что изменилось

  • _log() пишет в /tmp/app-autotest.log под fcntl.flock (вместо in-memory deque).
  • /api/log читает из файла последние 200 строк.
  • Ротация при 512 КБ.
  • UI-панель логов (180px, автоскролл, поллинг 2s).

v1.0.77 (27.07.2026) — debug-логирование в api_params

v1.0.76 (27.07.2026) — modify: preview-операция для получения paramValue (НЕ СРАБОТАЛО)

v1.0.74 (27.07.2026) — version bump for push

Что изменилось

  • В site/templates/index.html create-операция отделена от обычных операций: CREATE использует фиксированный префикс autotest-, а non-create берёт имя выбранного инстанса.
  • Из non-create ветки убран случайный fallback tut и общий create-style displayName.
  • В site/routes/api_test.py backend больше не подставляет create-имя по умолчанию для обычных операций и возвращает displayName для финального статуса.
  • В site/routes/main.py список autotest-инстансов теперь строится cloud-first без старой склейки через map по имени; tracker остался только как временный fallback, если cloud ещё не вернул новый инстанс.

Что это чинит

  • Путаницу между CREATE и обычными операциями.
  • Ситуацию, когда в UI появлялся лишний tut.
  • Дубли running/creating на одном autotest-инстансе.
  • Неясный финальный статус, где было видно операцию, но не было понятно, над каким инстансом она выполнялась.

v1.0.72 (27.07.2026) — service list keeps one autotest row per displayName

Что изменилось

  • В site/routes/main.py сервисный список теперь cloud-first и не склеивает разные строки в одну map.
  • Tracker остаётся только как fallback, если cloud ещё не вернул конкретный autotest-инстанс.

Что это чинит

  • Дубли running/creating на одном и том же autotest-инстансе.

v1.0.71 (27.07.2026) — finished status shows instance name

Что изменилось

  • В site/templates/index.html финальная строка после OK/FAIL теперь показывает имя инстанса.
  • Имя операции и длительность остаются рядом, отдельно от имени инстанса.

Что это чинит

  • Ситуацию, когда после завершения delete/modify было видно только название операции, но не ясно, над каким инстансом она выполнялась.

v1.0.70 (27.07.2026) — dedupe autotest instances by displayName

Что изменилось

  • В site/routes/main.py сервисный список теперь склеивает записи по displayName.
  • Если cloud уже отдал running, он выигрывает у tracker-fallback creating.

Что это чинит

  • Дубли одной и той же autotest-записи после CREATE, когда в списке появлялись и running, и creating.

v1.0.69 (27.07.2026) — autotest prefix fixed in CREATE field

Что изменилось

  • В site/templates/index.html префикс autotest- отображается отдельным фиксированным текстом.
  • Рядом с ним остаётся только редактируемый суффикс имени.
  • При отправке CREATE имя собирается как autotest- + введённый хвост.

Что это чинит

  • Ситуацию, когда весь displayName показывался в одном редактируемом поле.
  • Риск случайно стереть обязательный префикс autotest-.

v1.0.68 (27.07.2026) — operation name shown after OK + muted operation buttons

Что изменилось

  • В site/templates/index.html финальный статус теперь показывает имя операции рядом со временем.
  • Кнопки операций получили спокойные неброские оттенки по типу операции.

Что это чинит

  • Неясность после завершения операции, когда было видно только OK и время без указания, что именно выполнялось.
  • Слишком кислотный вид кнопок операций.

v1.0.67 (27.07.2026) — autotest instances are shown by cloud prefix

Что изменилось

  • В site/routes/main.py список инстансов сервиса больше не отфильтровывается через /tmp/instances.json.
  • Теперь в таблицу попадают все облачные инстансы с префиксом autotest-.
  • Трекер оставлен только как fallback, если новый autotest-инстанс ещё не успел появиться в ответе облака.

Что это чинит

  • Ситуацию, когда autotest-инстанс уже есть в облаке, но не показывается в таблице из-за отсутствия записи в json.

v1.0.66 (27.07.2026) — backend status resolver for operations

Что изменилось

  • В site/routes/main.py добавлен канонический резолвер статуса инстанса для /api/operations/<svc_id>.
  • Backend теперь отдаёт status для инстансов, а UI читает именно его вместо угадывания по explainedStatus.
  • В site/templates/index.html убрано раннее отображение «Готово» во время выполнения операции.

Что это чинит

  • Путаницу между creating, running и фактическим статусом в UI.
  • Ситуацию, когда фронтенд сам интерпретировал статусы и показывал не то состояние.

v1.0.65 (27.07.2026) — cloud list pagination fix

Что изменилось

  • В site/operations/get_instances.py пагинация списка инстансов теперь останавливается по размеру страницы, а не по total.
  • Это убирает ситуацию, когда инстанс уже создан в облаке, но приложение не дочитало следующую страницу и показывает creating.
  • В DOCS/ARCHITECTURE.md и site/app.py синхронизирована версия 1.0.65.

Что это чинит

  • Ложный creating для уже существующего инстанса, если он попал не на первую страницу /instances.

v1.0.64 (27.07.2026) — header version + editable displayName placeholder

Что изменилось

  • В site/routes/main.py версия теперь гарантированно попадает в config.VERSION даже после load_config().
  • В site/templates/index.html поле displayName остаётся редактируемым, но автотест-имя показывается как placeholder.
  • При CREATE пользователь может ввести своё имя, а backend всё равно добавит autotest- и проверит уникальность.
  • В DOCS/ARCHITECTURE.md зафиксированы оба изменения.

Что это чинит

  • Пропадающую версию в шапке.
  • Неправильное поведение поля displayName, когда оно выглядело как готовое значение вместо подсказки.

v1.0.63 (27.07.2026) — autotest namespace for instances

Что изменилось

  • В site/templates/index.html CREATE теперь генерирует displayName с префиксом autotest- и проверяет его на уникальность среди текущих инстансов.
  • В site/routes/api_test.py backend тоже нормализует displayName с этим префиксом и, если нужно, добавляет суффикс для уникальности перед созданием в облаке.
  • В site/routes/main.py список инстансов фильтруется по namespace autotest-, чтобы в UI попадали только тестовые инстансы приложения.
  • В DOCS/ARCHITECTURE.md зафиксирован переход на namespace-based фильтрацию.

Что это чинит

  • Дубли displayName при CREATE.
  • Чужие инстансы в списке.
  • Зависимость от локального трекера как от основного признака принадлежности.

v1.0.62 (27.07.2026) — immediate visual refresh after CREATE

Что изменилось

  • В site/templates/index.html после завершения CREATE список инстансов перерисовывается полностью.
  • refreshInstances() больше не зависит от DOM-diff и сразу ставит новый инстанс в список.
  • DOCS/ARCHITECTURE.md и site/app.py синхронизированы на 1.0.62.

Что это чинит

  • После появления кнопки «Готово» созданный инстанс должен сразу быть виден в списке.

v1.0.61 (27.07.2026) — immediate list rebuild after CREATE

Что изменилось

  • В site/templates/index.html refreshInstances() теперь всегда полностью перерисовывает список инстансов после завершения операции.
  • После OK новый инстанс должен появляться в списке сразу, без зависимости от DOM-diff и без перезагрузки страницы.
  • В DOCS/ARCHITECTURE.md и site/app.py синхронизирована версия 1.0.61.

Что это чинит

  • Убрана ситуация, когда после CREATE статус уже OK, но в UI инстанс ещё не виден.

v1.0.60 (27.07.2026) — full list rerender after CREATE

Что изменилось

  • В site/templates/index.html refreshInstances() теперь полностью перерисовывает список инстансов, а не пытается вставить только diff.
  • После завершения CREATE новый инстанс должен появляться в списке сразу после OK.
  • В DOCS/ARCHITECTURE.md добавлен отдельный шаг про полную перерисовку списка.

Что это чинит

  • Снята зависимость от частичного DOM-diff-а, из-за которого созданный инстанс мог не появляться сразу.

v1.0.59 (27.07.2026) — restore tracked instance after reload

Что изменилось

  • В site/routes/main.py tracked-инстансы, которых Nubes уже не отдает в ответе после reload, снова добавляются в список из /tmp/instances.json.
  • В site/templates/index.html статус CREATE теперь показывает имя инстанса, а не только create.
  • В DOCS/ARCHITECTURE.md зафиксирован новый шаг восстановления списка после F5.

Что это чинит

  • После F5 инстанс не пропадает из UI, если он есть в локальном трекере.
  • В статусе выполнения видно, по какому displayName идет CREATE.

v1.0.58 (27.07.2026) — version bump

  • Поднята версия проекта до 1.0.58.
  • Синхронизированы заголовки в site/app.py и DOCS/ARCHITECTURE.md.

v1.0.57 (27.07.2026) — UI hardening + history sync

Что изменилось

  • В site/templates/index.html финальное состояние кнопки вынесено в setFinishedState(), чтобы не держать логику завершения операции в двух местах.
  • После завершения операции кнопка «Готово» теперь гарантированно сбрасывает обработчик через btn.onclick = null.
  • Повторный CREATE через старый обработчик больше не воспроизводится.
  • displayName больше не теряется в нормальном сценарии завершения CREATE.
  • В site/app.py версия поднята до 1.0.57.
  • В DOCS/ARCHITECTURE.md отмечено закрытие бага с повторным CREATE и синхронизирован статус по displayName.

Зачем это было сделано

  • Убрать хрупкость вокруг состояния кнопки после завершения операции.
  • Привести документацию и версию к текущему состоянию кода.

v1.0.54 (27.07.2026) — Аудит #3: 4 бага исправлено

Баг #1: _client() не автоопределял стенд (КРИТИЧЕСКИЙ)

  • Где: api_test.py:_client() — использовал current_app.config["NUBES_API_ENDPOINT"] (raw env var)
  • Симптом: main.py автоопределял стенд для главной, но POST /api/test падал с 401 если токен от другого стенда
  • Исправление: detect_endpoint() вынесен в api/http_client.py, используется в _client()
  • Как пропустили: анализировал main.py и api_test.py в изоляции, не сравнил

Баг #2: _find_uid() возвращал не тот UUID (КРИТИЧЕСКИЙ)

  • Где: api_test.py:_find_uid() — итерировал по ВСЕМ значениям dict, искал 36-символьную строку
  • Симптом: /instanceOperations возвращает оба instanceUid + instanceOperationUid, функция брала первый попавшийся → run шёл на неправильный URL
  • Исправление: ищет по конкретным ключам: instanceOperationUidinstanceUiduidUid
  • Как пропустили: не проверил реальный формат ответа API

Баг #3: Сироты при ошибке params (СРЕДНИЙ)

  • Где: api_test.py: порядок — POST /instancesPOST /instanceOperations → params loop → tracker_add
  • Симптом: если params падает — инстанс и операция созданы в Nubes, трекер пуст → сироты
  • Исправление: tracker_add вызывается ДО params loop, сразу после получения instanceUid
  • Как пропустили: не трассировал линейно порядок вызовов

Баг #4: flock без таймаута (НИЗКИЙ)

  • Где: tracker.py:_locked_read/_writefcntl.flock(fd, LOCK_EX) без LOCK_NB
  • Симптом: при зависшем процессе с локом все воркеры блокируются навсегда
  • Исправление: _acquire_lock() с LOCK_EX | LOCK_NB + retry до 2 секунд
  • Как пропустили: новый код flock не перечитал с нуля после добавления

Архитектурное: detect_endpoint вынесен в http_client.py

  • Раньше был в main.py → недоступен для api_test.py
  • Теперь в api/http_client.py → оба модуля используют

v1.0.53 (27.07.2026) — Файловый трекер с fcntl.flock

  • In-memory dict заменён на /tmp/instances.json с fcntl.LOCK_EX
  • Multi-worker gunicorn: все воркеры читают/пишут один файл под локом

v1.0.52 (27.07.2026) — _tmpl() helper

  • Все переменные шаблона передаются во всех трёх return-путях index()
  • Исправлен UndefinedError при «Выйти»

v1.0.51 (27.07.2026) — Автоопределение стенда + UnboundLocalError

  • detect_endpoint() в main.py: пробует токен против dev и test API
  • instance_groups = {} инициализирован до if
  • _finish_op обёрнут в полный try/except

v1.0.50 (27.07.2026) — showStages + api_operations fix

  • showStages(): dtFinish вместо isSuccessful для //
  • api_operations(): не фильтровать "not created", добавлять tracked-сирот

v1.0.49 (27.07.2026) — In-memory tracker + refreshInstances

  • Трекер: in-memory dict вместо файла /tmp/instances.json
  • refreshInstances() добавляет новые инстансы в DOM (не только бейджи)
  • selectService() заменён на refreshInstances() после OK

v1.0.48 (27.07.2026) — tracker_add синхронно

  • tracker_add в api_test() до threading.Thread
  • Лог is_ok в _finish_op
  • Flexbox-кнопки горизонтально

v1.0.47 (27.07.2026) — displayName fix

  • displayName захватывается до очистки формы
  • tracker_add до _op_results[OK]
  • autotest-1 в _INITIAL

v1.0.46 (27.07.2026) — _finish_op signature

  • Добавлены op_name, svc_op_id в _finish_op()

v1.0.45 и ранее

  • Базовый CREATE/MODIFY/SUSPEND/DELETE/RESUME/REDEPLOY
  • UI с этапами, поллинг, params форма
  • Множественные баги CREATE flow (документированы в DOCS/api-create-flow.md)