Files
polygon/site/routes/mock_routes.py
T
naeel 58d54f2a95 v0.5.0: 3 стенда — изоляция состояния, WSGI middleware, OpenAPI server selector, POLYGON_STANDS env
- loader.py: STANDS dict, get_services/get_ops_index per stand, POLYGON_STANDS env
- mock_state.py: get_state(stand_id) — ленивая инициализация
- app.py: StandMiddleware — перезапись PATH_INFO до Flask-роутинга
- Все роуты: LocalProxy(state), LocalProxy(SERVICES/OPS_INDEX) через g.stand_id
- openapi.py: _build_servers() — по одному серверу на стенд
- services/stand{1,2,3}/ — 37 YAML на стенд из STANDS/dev и STANDS/test
- tests/test_api.py: +4 multi-stand теста (изоляция, fail-next, OpenAPI servers)
- Обратная совместимость: /api/v1/svc/... → стенд по умолчанию
2026-08-01 20:10:19 +04:00

155 lines
6.7 KiB
Python
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.
"""
routes/mock_routes.py — служебные эндпоинты /_mock/*.
Blueprint "mock" с префиксом /api/v1/svc/_mock.
POST /reset — полный сброс состояния (для тестов)
GET /state — отладочный дамп instances + operations
GET /services — отладочный список загруженных сервисов
POST /delay/<s> — изменить MOCK_OP_DELAY на лету (не перезапуская сервер)
"""
import os
from flask import Blueprint, g, jsonify, request, abort
from werkzeug.local import LocalProxy
import mock_state
import config.loader as _cfg
state = LocalProxy(lambda: mock_state.get_state(g.stand_id))
SERVICES = LocalProxy(lambda: _cfg.get_services(g.stand_id))
bp = Blueprint("mock", __name__, url_prefix="/api/v1/svc/_mock")
# Токен для защиты _mock/* эндпоинтов (из env, опционально).
# Дублирует глобальную проверку в app.py, но оставлен для обратной совместимости
# и ясности — _mock/* исторически имели отдельную защиту.
_MOCK_TOKEN = os.getenv("MOCK_AUTH_TOKEN", "")
@bp.before_request
def _check_mock_auth():
"""Проверка X-Mock-Auth заголовка если MOCK_AUTH_TOKEN задан.
Вызывается Flask перед КАЖДЫМ обработчиком в этом blueprint'е.
Если токен не задан в env — пропускаем всех (dev-режим).
Если задан — требуем совпадения с заголовком.
Примечание: app.before_request уже проверяет Authorization: Bearer;
этот хук — дополнительная защита для _mock/* эндпоинтов."""
if _MOCK_TOKEN and request.headers.get("X-Mock-Auth") != _MOCK_TOKEN:
abort(403)
@bp.route("/reset", methods=["POST"])
def mock_reset():
"""POST /api/v1/svc/_mock/reset — полный сброс состояния.
Удаляет ВСЕ инстансы, операции и параметры из MockState.
Используется в тестах (autouse fixture) для изоляции тестов друг от друга.
"""
state.reset()
return jsonify({"reset": "ok"})
@bp.route("/state", methods=["GET"])
def mock_state_view():
"""GET /api/v1/svc/_mock/state — отладочный дамп состояния.
Возвращает {instances: {uid: {instanceUid, displayName, status, serviceId}},
operations: {uid: {instanceOperationUid, instanceUid, operation,
dtFinish, isSuccessful}}}.
Полезно для отладки тестов: «какие инстансы сейчас живы?», «завершилась ли
операция?». НЕ для production-использования.
"""
st = state
return jsonify({
"instances": {
uid: {
"instanceUid": inst["instanceUid"],
"displayName": inst["displayName"],
"status": inst["status"],
"serviceId": inst["serviceId"],
}
for uid, inst in st.instances.items()
},
"operations": {
uid: {
"instanceOperationUid": op["instanceOperationUid"],
"instanceUid": op["instanceUid"],
"operation": op["operation"],
"dtFinish": op["dtFinish"],
"isSuccessful": op["isSuccessful"],
}
for uid, op in st.operations.items()
},
})
@bp.route("/services", methods=["GET"])
def mock_services():
"""GET /api/v1/svc/_mock/services — отладочный список сервисов.
Возвращает {count: N, services: {svcId: {name, displayName, operations, params}}}.
Полезно чтобы быстро проверить что все 37+ сервисов загрузились,
не дёргая реальный /api/v1/svc/services.
"""
result = {}
for svc_id, svc_def in _cfg.SERVICES.items():
result[str(svc_id)] = {
"name": svc_def.get("name", ""),
"displayName": svc_def.get("service_display_name", ""),
"operations": len(svc_def.get("operations", [])),
"params": len(svc_def.get("cfsParams", [])),
}
return jsonify({"count": len(result), "services": result})
# Максимальная задержка — защита от блокировки /health (1 воркер).
# При DELAY > healthcheck timeout платформа рестартит под.
_MAX_DELAY = 5.0
@bp.route("/delay/<string:seconds>", methods=["POST"])
def mock_set_delay(seconds):
"""POST /api/v1/svc/_mock/delay/%s — изменить задержку операции.
Меняет config.loader.DELAY «на лету», без перезапуска сервера.
Полезно в тестах: установить delay=0 для мгновенных операций,
или delay=5 чтобы проверить таймауты поллинга.
Максимум %sс — защита от блокировки /health (1 воркер).
Минимум 0с — отрицательные значения запрещены.
Принимает строку и конвертирует в float — Flask <float:> не матчит
целые числа без десятичной точки (0, 2).
""" % (_MAX_DELAY, _MAX_DELAY)
try:
val = float(seconds)
except (ValueError, TypeError):
return jsonify({"error": f"invalid delay: {seconds}"}), 400
if val < 0:
return jsonify({"error": "delay must be >= 0"}), 400
if val > _MAX_DELAY:
return jsonify({"error": f"delay must be <= {_MAX_DELAY}s (Nubes healthcheck protection)"}), 400
_cfg.DELAY = val
return jsonify({"delay": _cfg.DELAY})
@bp.route("/fail-next", methods=["POST"])
def mock_fail_next():
"""POST /api/v1/svc/_mock/fail-next — следующая операция упадёт.
Устанавливает флаг в MockState. При следующем POST /run:
- isSuccessful = False
- errorLog = "mock failure (requested by /_mock/fail-next)"
- эффект НЕ применяется (состояние инстанса не меняется)
После использования флаг сбрасывается (one-shot).
Позволяет тестировать негативные сценарии.
"""
state.fail_next = True
return jsonify({"fail_next": True})