fix(qizheng): copy vendored stem-branch files from local upstream checkout
Independent Staging Quality Gate / validate (push) Canceled after 4m51s
Independent Staging Quality Gate / publish (push) Canceled after 0s

Read G:/Ferti/yinduzhanxing-codex-add-birth-time-rectification-skill (a911c890) and copy the listed vendor files by content. Rebuild the adapter from the 168-line CLI wrapper, then apply school parameters, boundary rewrite, and sanitized errors.
This commit is contained in:
jesse-ux
2026-09-15 16:21:04 +08:00
parent 25851dd338
commit e776cf9d83
8 changed files with 225 additions and 137 deletions
+1 -1
View File
@@ -118,5 +118,5 @@ def test_qizheng_node_unavailable_returns_structured_error(api_env, monkeypatch)
payload = handler.payload()
assert payload["success"] is False
assert payload["error_code"] == "ERR_BAD_REQUEST"
assert "node" in payload["error"]
assert "unavailable" in payload["error"].lower()
assert "Traceback" not in payload["error"]
+15 -14
View File
@@ -11,6 +11,7 @@ from types import SimpleNamespace
import pytest
from scripts import qizheng_chart_engine as engine
from scripts.qizheng_chart_engine import calculate_qizheng_chart
ROOT = Path(__file__).resolve().parents[1]
GOLDEN = ROOT / "tests" / "golden" / "qizheng_stem_branch_19900409.json"
@@ -49,7 +50,7 @@ def test_golden_fixture_is_real_engine_output() -> None:
def test_live_chart_matches_golden_and_product_contract() -> None:
_require_node()
golden = json.loads(GOLDEN.read_text(encoding="utf-8"))
result = engine.build_qizheng_natal_chart(SAMPLE)
result = calculate_qizheng_chart(SAMPLE)
assert result["coordinate_system"] == "qizheng_mansion_degrees_from_jiao"
assert set(result["bodies"]) == set(engine.REQUIRED_BODIES)
assert len(result["palaces"]) == 12
@@ -70,7 +71,7 @@ def test_live_chart_matches_golden_and_product_contract() -> None:
def test_ketu_mode_request_overrides_and_is_echoed() -> None:
_require_node()
result = engine.build_qizheng_natal_chart({**SAMPLE, "ketu_mode": "descending-node"})
result = calculate_qizheng_chart({**SAMPLE, "ketu_mode": "descending-node"})
assert result["calculation"]["ketu_mode"] == "descending-node"
assert result["calculation"]["engine_ketu_mode"] == "descending-node"
rahu = result["bodies"]["rahu"]["siderealLon"]
@@ -81,23 +82,23 @@ def test_ketu_mode_request_overrides_and_is_echoed() -> None:
def test_missing_lat_is_structured_input_error() -> None:
with pytest.raises(engine.QizhengChartError, match="lat is required") as caught:
engine.build_qizheng_natal_chart({k: v for k, v in SAMPLE.items() if k != "lat"})
calculate_qizheng_chart({k: v for k, v in SAMPLE.items() if k != "lat"})
assert caught.value.error_code == "ERR_QIZHENG_INPUT"
def test_node_missing_error(monkeypatch) -> None:
monkeypatch.setattr(engine.shutil, "which", lambda name: None)
with pytest.raises(engine.QizhengChartError, match="node runtime is not available") as caught:
engine.build_qizheng_natal_chart(SAMPLE)
with pytest.raises(engine.QizhengChartError, match="unavailable") as caught:
calculate_qizheng_chart(SAMPLE)
assert caught.value.error_code == "ERR_QIZHENG_NODE_MISSING"
def test_cli_missing_error(monkeypatch, tmp_path: Path) -> None:
monkeypatch.setattr(engine, "CLI_PATH", tmp_path / "cli.cjs")
monkeypatch.setattr(engine, "LIB_PATH", tmp_path / "index.cjs")
monkeypatch.setattr(engine, "VENDORED_CLI", tmp_path / "cli.cjs")
monkeypatch.setattr(engine, "VENDORED_LIB", tmp_path / "index.cjs")
monkeypatch.setattr(engine.shutil, "which", lambda name: "node")
with pytest.raises(engine.QizhengChartError, match="missing") as caught:
engine.build_qizheng_natal_chart(SAMPLE)
with pytest.raises(engine.QizhengChartError, match="unavailable") as caught:
calculate_qizheng_chart(SAMPLE)
assert caught.value.error_code == "ERR_QIZHENG_CLI_MISSING"
@@ -108,8 +109,8 @@ def test_timeout_error(monkeypatch) -> None:
raise subprocess.TimeoutExpired(cmd="node", timeout=20)
monkeypatch.setattr(engine.subprocess, "run", _boom)
with pytest.raises(engine.QizhengChartError, match="timed out") as caught:
engine.build_qizheng_natal_chart(SAMPLE)
with pytest.raises(engine.QizhengChartError, match="超时") as caught:
calculate_qizheng_chart(SAMPLE)
assert caught.value.error_code == "ERR_QIZHENG_TIMEOUT"
@@ -123,7 +124,7 @@ def test_nonzero_exit_does_not_leak_stderr(monkeypatch) -> None:
),
)
with pytest.raises(engine.QizhengChartError, match="exited with an error") as caught:
engine.build_qizheng_natal_chart(SAMPLE)
calculate_qizheng_chart(SAMPLE)
assert caught.value.error_code == "ERR_QIZHENG_ENGINE_EXIT"
assert "secret" not in str(caught.value)
assert "cli.cjs" not in str(caught.value)
@@ -136,8 +137,8 @@ def test_non_json_stdout_error(monkeypatch) -> None:
"run",
lambda *args, **kwargs: SimpleNamespace(returncode=0, stdout="not-json", stderr=""),
)
with pytest.raises(engine.QizhengChartError, match="non-JSON") as caught:
engine.build_qizheng_natal_chart(SAMPLE)
with pytest.raises(engine.QizhengChartError, match="JSON") as caught:
calculate_qizheng_chart(SAMPLE)
assert caught.value.error_code == "ERR_QIZHENG_BAD_JSON"