Files
embeddevio c7f2ea128e deploy: гейт покрытия стенда и тестируемая health-заглушка (#56)
- deploy/api_stub.py: make_server() выделен из main(), main(argv) — тесты поднимают
  настоящий сервер на порту 0 (покрытие api_stub.py 0 % -> 98 %).
- Makefile: make test-deploy (гейт ≥70 % через tests/deploy/pytest.ini),
  test-netns-ci прокидывает AWG_NS_EXEC_MODE.

Refs #21, Refs #15, Refs #56
2026-10-28 21:30:33 +03:00

96 lines
3.7 KiB
Python
Raw Permalink 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.
#!/usr/bin/env python3
"""api_stub.py — DEV-ONLY заглушка health-эндпоинта (задача #21).
Существует ровно для одной цели: до реализации FastAPI-каркаса (#31) проверить
плумбинг dev-стенда — маппинг порта, healthcheck контейнера, тома /config и
/data, переживание `docker compose restart`.
Отдаёт:
GET /api/v1/health -> 200 {"status":"ok","stub":true,...}
GET /healthz -> 200 (алиас для отладки)
всё остальное -> 404 {"error":"not_implemented","issue":"#31"}
Чего НЕ делает: никакой логики шлюза, никакого доступа к awg/iptables.
В staging/prod запускать запрещено (см. deploy/dev-stand.md).
"""
from __future__ import annotations
import argparse
import json
import os
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
STARTED_AT = time.time()
STUB_ENV = os.environ.get("AWG_API_STUB", "0")
HEALTH_PATHS = ("/api/v1/health", "/healthz")
class Handler(BaseHTTPRequestHandler):
server_version = "awg-dash-stub/0.1"
protocol_version = "HTTP/1.1"
def _json(self, code: int, payload: dict) -> None:
body = (json.dumps(payload, sort_keys=True) + "\n").encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None: # noqa: N802 (HTTP-метод)
path = self.path.split("?", 1)[0]
if path in HEALTH_PATHS:
self._json(
200,
{
"status": "ok",
"stub": True,
"issue": "#31",
"note": "заглушка dev-стенда: реальный API появится в #31",
"uptime_s": round(time.time() - STARTED_AT, 3),
"stub_env": STUB_ENV,
},
)
return
self._json(404, {"error": "not_implemented", "issue": "#31", "path": path})
def do_HEAD(self) -> None: # noqa: N802
self.send_response(200 if self.path.split("?", 1)[0] in HEALTH_PATHS else 404)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, fmt: str, *args) -> None: # лог в stdout контейнера
print(f"[api-stub] {self.address_string()} {fmt % args}", flush=True)
def make_server(host: str, port: int) -> ThreadingHTTPServer:
"""HTTP-сервер заглушки.
Вынесен из main(), чтобы тесты (tests/deploy/test_api_stub.py) поднимали
настоящий HTTP-сервер на свободном порту (port=0) и проверяли коды ответов.
"""
return ThreadingHTTPServer((host, port), Handler)
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description="DEV-ONLY заглушка health для dev-стенда (#21)")
ap.add_argument("--host", default="0.0.0.0")
ap.add_argument("--port", type=int, default=8080)
ap.add_argument("--domain", default="/config", help="каталог, доступность которого проверяем (для лога)")
args = ap.parse_args(argv)
print(
f"[api-stub] DEV-ONLY заглушка на {args.host}:{args.port}; каталог {args.domain} "
f"{'доступен' if os.path.isdir(args.domain) else 'НЕ доступен'}",
flush=True,
)
make_server(args.host, args.port).serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(main())