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/... → стенд по умолчанию
This commit is contained in:
@@ -340,6 +340,67 @@ def test_delay_negative():
|
||||
t("POST /_mock/delay/-1 → 400", test_delay_negative)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════
|
||||
# 8. MULTI-STAND (v0.5.0+)
|
||||
# ═══════════════════════════════════════
|
||||
print("\n── 8. Multi-Stand (stand1/stand2/stand3) ──")
|
||||
|
||||
STANDS = ["stand1", "stand2", "stand3"]
|
||||
stand_instances = {}
|
||||
|
||||
def test_stands_services():
|
||||
for sid in STANDS:
|
||||
r = requests.get(f"{BASE}/{sid}/api/v1/svc/services")
|
||||
st(r, 200, f"{sid} services")
|
||||
t("GET /{stand}/api/v1/svc/services → 200 для всех стендов", test_stands_services)
|
||||
|
||||
def test_stands_isolated():
|
||||
"""Каждый стенд — свои инстансы."""
|
||||
for sid in STANDS:
|
||||
requests.post(f"{BASE}/{sid}/api/v1/svc/_mock/reset", headers=MH)
|
||||
# Создаём по 1 инстансу на стенд
|
||||
for i, sid in enumerate(STANDS):
|
||||
r = requests.post(f"{BASE}/{sid}/api/v1/svc/instances",
|
||||
json={"serviceId": 1, "displayName": f"inst-on-{sid}"}, headers=H)
|
||||
st(r, 201)
|
||||
stand_instances[sid] = js(r)["instanceUid"]
|
||||
# Проверяем что на каждом стенде ровно 1 инстанс
|
||||
for sid in STANDS:
|
||||
d = js(requests.get(f"{BASE}/{sid}/api/v1/svc/instances"))
|
||||
assert d["total"] == 1, f"{sid} has {d['total']} instances, expected 1"
|
||||
t("Изоляция: каждый стенд — свои инстансы", test_stands_isolated)
|
||||
|
||||
def test_stands_openapi():
|
||||
r = requests.get(f"{BASE}/api/v1/svc/openapi.json")
|
||||
spec = js(r)
|
||||
servers = [s["url"] for s in spec["servers"]]
|
||||
assert len(servers) == 3, f"expected 3 servers, got {len(servers)}"
|
||||
for sid in STANDS:
|
||||
assert any(f"/{sid}" in u for u in servers), f"{sid} not in servers"
|
||||
t("GET /openapi.json → 3 servers (по одному на стенд)", test_stands_openapi)
|
||||
|
||||
def test_stands_fail_next_isolated():
|
||||
"""fail-next на одном стенде не влияет на другой."""
|
||||
requests.post(f"{BASE}/stand1/api/v1/svc/_mock/reset", headers=MH)
|
||||
requests.post(f"{BASE}/stand2/api/v1/svc/_mock/reset", headers=MH)
|
||||
# fail-next только на stand1
|
||||
requests.post(f"{BASE}/stand1/api/v1/svc/_mock/fail-next", headers=MH)
|
||||
# Создаём операции на обоих стендах
|
||||
for sid, label in [("stand1", "fail"), ("stand2", "ok")]:
|
||||
r = requests.post(f"{BASE}/{sid}/api/v1/svc/instances",
|
||||
json={"serviceId": 1, "displayName": label}, headers=H)
|
||||
inst_uid = js(r)["instanceUid"]
|
||||
r = requests.post(f"{BASE}/{sid}/api/v1/svc/instanceOperations",
|
||||
json={"instanceUid": inst_uid, "operation": "create", "svcOperationId": 18}, headers=H)
|
||||
op_uid = js(r)["instanceOperationUid"]
|
||||
d = js(requests.post(f"{BASE}/{sid}/api/v1/svc/instanceOperations/{op_uid}/run", headers=H))
|
||||
if sid == "stand1":
|
||||
assert d["ok"] is False, f"stand1 should fail, got ok={d['ok']}"
|
||||
else:
|
||||
assert d["ok"] is True, f"stand2 should succeed, got ok={d['ok']}"
|
||||
t("Изоляция fail-next: stand1 падает, stand2 работает", test_stands_fail_next_isolated)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════
|
||||
# ИТОГ
|
||||
# ═══════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user