fix: harden accuracy and api guardrails
This commit is contained in:
@@ -4,8 +4,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -27,6 +29,40 @@ def _handler() -> JyotishAPIHandler:
|
||||
return JyotishAPIHandler.__new__(JyotishAPIHandler)
|
||||
|
||||
|
||||
class _FakeHeaders(dict):
|
||||
def get(self, key, default=None):
|
||||
return super().get(key, default)
|
||||
|
||||
|
||||
class _FakeServer:
|
||||
allowed_origins = DEFAULT_ALLOWED_ORIGINS
|
||||
|
||||
|
||||
class _ResponseCaptureHandler(JyotishAPIHandler):
|
||||
def __init__(self) -> None:
|
||||
self.headers = _FakeHeaders()
|
||||
self.server = _FakeServer()
|
||||
self.path = '/api/capability_audit'
|
||||
self.wfile = BytesIO()
|
||||
self.status_code = None
|
||||
self.response_headers = []
|
||||
|
||||
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 _capability_audit(self):
|
||||
raise RuntimeError('simulated internal failure')
|
||||
|
||||
def payload(self) -> dict:
|
||||
return json.loads(self.wfile.getvalue().decode('utf-8'))
|
||||
|
||||
|
||||
def test_default_cors_origins_are_local_only() -> None:
|
||||
assert 'http://localhost:3456' in DEFAULT_ALLOWED_ORIGINS
|
||||
assert '*' not in DEFAULT_ALLOWED_ORIGINS
|
||||
@@ -40,6 +76,19 @@ def test_env_cors_parser_ignores_empty_entries() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_get_internal_errors_are_json_wrapped() -> None:
|
||||
handler = _ResponseCaptureHandler()
|
||||
|
||||
handler.do_GET()
|
||||
|
||||
assert handler.status_code == 500
|
||||
assert ('Content-Type', 'application/json; charset=utf-8') in handler.response_headers
|
||||
payload = handler.payload()
|
||||
assert payload['success'] is False
|
||||
assert payload['error'] == 'Internal server error'
|
||||
assert payload['error_code'] == 'ERR_INTERNAL'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('key', 'value', 'minimum', 'maximum'),
|
||||
[
|
||||
|
||||
@@ -891,11 +891,14 @@ def test_quality_gate_declares_fast_browser_release_profiles() -> None:
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
for token in [
|
||||
"--profile",
|
||||
"choices=[\"quick\", \"browser\", \"release\"]",
|
||||
"choices=[\"quick\", \"browser\", \"release\", \"accuracy\"]",
|
||||
"QUALITY_GATE_PROFILES",
|
||||
"quick",
|
||||
"browser",
|
||||
"release",
|
||||
"accuracy",
|
||||
"skip_local_accuracy_report",
|
||||
"scripts/local_accuracy_report.py",
|
||||
"skip_slow",
|
||||
"skip_yoga_logic",
|
||||
"skip_frontend_click",
|
||||
@@ -913,9 +916,11 @@ def test_quality_gate_declares_fast_browser_release_profiles() -> None:
|
||||
"quick:快速开发守门",
|
||||
"browser:完整浏览器守门",
|
||||
"release:发布前守门",
|
||||
"accuracy:本地准确率守门",
|
||||
"python3 scripts/run_quality_gate.py --profile quick",
|
||||
"python3 scripts/run_quality_gate.py --profile browser",
|
||||
"python3 scripts/run_quality_gate.py --profile release",
|
||||
"python3 scripts/run_quality_gate.py --profile accuracy",
|
||||
]:
|
||||
assert token in readme
|
||||
|
||||
@@ -938,6 +943,19 @@ def test_release_quality_gate_tracks_untracked_product_files() -> None:
|
||||
assert path in quality_gate.RELEASE_CRITICAL_UNTRACKED_PATHS
|
||||
|
||||
|
||||
def test_accuracy_quality_gate_runs_local_accuracy_report_without_frontend_click() -> None:
|
||||
quality_gate = load_quality_gate_module()
|
||||
profile = quality_gate.QUALITY_GATE_PROFILES["accuracy"]
|
||||
|
||||
assert profile["skip_frontend_click"] is True
|
||||
assert profile["skip_frontend_runtime"] is True
|
||||
assert profile["skip_real_cases"] is False
|
||||
assert profile["skip_dasha_audit"] is False
|
||||
assert profile["skip_oracle_audit"] is False
|
||||
assert profile["skip_yoga_logic"] is False
|
||||
assert profile["skip_local_accuracy_report"] is False
|
||||
|
||||
|
||||
def test_github_release_quality_gate_runs_browser_release_profile() -> None:
|
||||
workflow = (ROOT / ".github" / "workflows" / "release-quality-gate.yml").read_text(encoding="utf-8")
|
||||
for token in [
|
||||
|
||||
@@ -15,13 +15,15 @@ SHADBALA_COMPONENTS = ["sthana", "dig", "kala", "chesta", "naisargika", "drik"]
|
||||
|
||||
|
||||
def complete_shadbala_components() -> dict[str, dict[str, float]]:
|
||||
return {
|
||||
planet: {
|
||||
component: float(40 + planet_index * 3 + component_index)
|
||||
result = {}
|
||||
for planet_index, planet in enumerate(SHADBALA_PLANETS):
|
||||
row = {
|
||||
component: round(0.1 + planet_index * 0.01 + component_index * 0.02, 4)
|
||||
for component_index, component in enumerate(SHADBALA_COMPONENTS)
|
||||
}
|
||||
for planet_index, planet in enumerate(SHADBALA_PLANETS)
|
||||
}
|
||||
row["total_rupa"] = round(sum(row[component] for component in SHADBALA_COMPONENTS), 4)
|
||||
result[planet] = row
|
||||
return result
|
||||
|
||||
|
||||
def build_queue() -> dict:
|
||||
@@ -209,6 +211,106 @@ def test_validator_rejects_non_numeric_or_negative_shadbala_components(tmp_path:
|
||||
assert "invalid_shadbala_component_negative:Moon.dig" in first["problems"]
|
||||
|
||||
|
||||
def test_validator_rejects_missing_shadbala_total_rupa(tmp_path: Path) -> None:
|
||||
queue = build_queue()
|
||||
packet = queue["tasks"][0]["evidence_packet"]
|
||||
shadbala = complete_shadbala_components()
|
||||
del shadbala["Sun"]["total_rupa"]
|
||||
packet["metadata"] = {
|
||||
"tool_name": "JHora",
|
||||
"tool_version_or_url": "manual-screenshot-v1",
|
||||
"capture_date": "2026-06-25",
|
||||
"source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png",
|
||||
"ayanamsa": "lahiri",
|
||||
"node_mode": "mean",
|
||||
"timezone": "UTC+08:00",
|
||||
"operator_note": "Manual external screenshot; values typed from JHora screen.",
|
||||
}
|
||||
packet["target_placeholders"] = {
|
||||
"target.moon_sidereal_longitude_deg": 311.7897,
|
||||
"target.vimshottari_start_date": "1986-05-18",
|
||||
"target.shadbala_components": shadbala,
|
||||
}
|
||||
packet["status"] = "external_verified"
|
||||
queue_path = tmp_path / "queue.json"
|
||||
queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
completed = run_validator(queue_path)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr or completed.stdout
|
||||
report = json.loads(completed.stdout)
|
||||
first = report["packets"][0]
|
||||
assert first["valid"] is False
|
||||
assert "missing_shadbala_total_rupa:Sun" in first["problems"]
|
||||
|
||||
|
||||
def test_validator_rejects_shadbala_component_values_above_20_rupas(tmp_path: Path) -> None:
|
||||
queue = build_queue()
|
||||
packet = queue["tasks"][0]["evidence_packet"]
|
||||
shadbala = complete_shadbala_components()
|
||||
shadbala["Sun"]["sthana"] = 20.01
|
||||
shadbala["Sun"]["total_rupa"] = round(sum(shadbala["Sun"][component] for component in SHADBALA_COMPONENTS), 4)
|
||||
packet["metadata"] = {
|
||||
"tool_name": "JHora",
|
||||
"tool_version_or_url": "manual-screenshot-v1",
|
||||
"capture_date": "2026-06-25",
|
||||
"source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png",
|
||||
"ayanamsa": "lahiri",
|
||||
"node_mode": "mean",
|
||||
"timezone": "UTC+08:00",
|
||||
"operator_note": "Manual external screenshot; values typed from JHora screen.",
|
||||
}
|
||||
packet["target_placeholders"] = {
|
||||
"target.moon_sidereal_longitude_deg": 311.7897,
|
||||
"target.vimshottari_start_date": "1986-05-18",
|
||||
"target.shadbala_components": shadbala,
|
||||
}
|
||||
packet["status"] = "external_verified"
|
||||
queue_path = tmp_path / "queue.json"
|
||||
queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
completed = run_validator(queue_path)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr or completed.stdout
|
||||
report = json.loads(completed.stdout)
|
||||
first = report["packets"][0]
|
||||
assert first["valid"] is False
|
||||
assert "invalid_shadbala_component_range:Sun.sthana" in first["problems"]
|
||||
|
||||
|
||||
def test_validator_rejects_shadbala_total_rupa_sum_mismatch(tmp_path: Path) -> None:
|
||||
queue = build_queue()
|
||||
packet = queue["tasks"][0]["evidence_packet"]
|
||||
shadbala = complete_shadbala_components()
|
||||
shadbala["Sun"]["total_rupa"] = round(shadbala["Sun"]["total_rupa"] + 0.06, 4)
|
||||
packet["metadata"] = {
|
||||
"tool_name": "JHora",
|
||||
"tool_version_or_url": "manual-screenshot-v1",
|
||||
"capture_date": "2026-06-25",
|
||||
"source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png",
|
||||
"ayanamsa": "lahiri",
|
||||
"node_mode": "mean",
|
||||
"timezone": "UTC+08:00",
|
||||
"operator_note": "Manual external screenshot; values typed from JHora screen.",
|
||||
}
|
||||
packet["target_placeholders"] = {
|
||||
"target.moon_sidereal_longitude_deg": 311.7897,
|
||||
"target.vimshottari_start_date": "1986-05-18",
|
||||
"target.shadbala_components": shadbala,
|
||||
}
|
||||
packet["status"] = "external_verified"
|
||||
queue_path = tmp_path / "queue.json"
|
||||
queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
completed = run_validator(queue_path)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr or completed.stdout
|
||||
report = json.loads(completed.stdout)
|
||||
first = report["packets"][0]
|
||||
assert first["valid"] is False
|
||||
assert "shadbala_total_rupa_sum_mismatch:Sun" in first["problems"]
|
||||
|
||||
|
||||
def test_validator_rejects_invalid_ashtakoot_score_ranges(tmp_path: Path) -> None:
|
||||
queue = build_queue_from_file(ROOT / "references/oracle/ashtakoot_oracle_cases.json")
|
||||
packet = queue["tasks"][0]["evidence_packet"]
|
||||
|
||||
Reference in New Issue
Block a user