"""HTTP contract for POST /api/qizheng.""" from __future__ import annotations import json from io import BytesIO import pytest from scripts.api_heavy_compute_gate import reset_heavy_compute_gate from scripts.jyotish_api_server import ( DEFAULT_ALLOWED_HOSTS, DEFAULT_ALLOWED_ORIGINS, JyotishAPIHandler, ) SAMPLE = { "year": 1990, "month": 4, "day": 9, "hour": 13, "minute": 24, "lat": 31.19, "lon": 121.44, "tz": 8, } class _FakeHeaders(dict): def get(self, key, default=None): return super().get(key, default) class _FakeServer: allowed_origins = DEFAULT_ALLOWED_ORIGINS allowed_hosts = DEFAULT_ALLOWED_HOSTS class _PostCaptureHandler(JyotishAPIHandler): def __init__(self, path: str, payload: dict) -> None: raw = json.dumps(payload).encode("utf-8") self.headers = _FakeHeaders( { "Content-Length": str(len(raw)), "Content-Type": "application/json", } ) self.server = _FakeServer() self.path = path self.rfile = BytesIO(raw) self.wfile = BytesIO() self.status_code = None self.response_headers = [] self.client_address = ("test-qizheng", 0) def send_response(self, code, message=None): # noqa: ANN001 self.status_code = code def send_header(self, key, value): # noqa: ANN001 self.response_headers.append((key, value)) def end_headers(self): return None def payload(self) -> dict: return json.loads(self.wfile.getvalue().decode("utf-8")) @pytest.fixture def api_env(monkeypatch): monkeypatch.setenv("JYOTISH_API_RATE_LIMIT_PER_MINUTE", "0") reset_heavy_compute_gate() yield reset_heavy_compute_gate() def test_qizheng_endpoint_returns_eleven_bodies_and_twelve_palaces(api_env) -> None: handler = _PostCaptureHandler("/api/qizheng", SAMPLE) handler.do_POST() assert handler.status_code == 200 payload = handler.payload() assert payload["success"] is True assert payload["coordinate_system"] == "qizheng_mansion_degrees_from_jiao" assert len(payload["bodies"]) == 11 assert len(payload["palaces"]) == 12 assert payload["ascendant"]["palace"] == "午宮" assert payload["dignities"]["status"] == "unclosed" assert "神煞" not in payload["boundary"] def test_qizheng_missing_lat_returns_400(api_env) -> None: body = {key: value for key, value in SAMPLE.items() if key != "lat"} handler = _PostCaptureHandler("/api/qizheng", body) handler.do_POST() assert handler.status_code == 400 payload = handler.payload() assert payload["success"] is False assert payload["error_code"] == "ERR_BAD_REQUEST" assert "lat" in payload["error"] def test_qizheng_missing_lon_returns_400(api_env) -> None: body = {key: value for key, value in SAMPLE.items() if key != "lon"} handler = _PostCaptureHandler("/api/qizheng", body) handler.do_POST() assert handler.status_code == 400 assert handler.payload()["success"] is False def test_qizheng_node_unavailable_returns_structured_error(api_env, monkeypatch) -> None: from scripts.jyotish_api_server import _load_local_module module = _load_local_module("qizheng_chart_engine") monkeypatch.setattr(module.shutil, "which", lambda name: None) handler = _PostCaptureHandler("/api/qizheng", SAMPLE) handler.do_POST() assert handler.status_code == 400 payload = handler.payload() assert payload["success"] is False assert payload["error_code"] == "ERR_BAD_REQUEST" assert "unavailable" in payload["error"].lower() assert "Traceback" not in payload["error"]