"""`/api/chart_points` (issue #114): the chart page's data tables come from existing producers.""" from __future__ import annotations import json import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / "scripts" if str(SCRIPTS) not in sys.path: sys.path.insert(0, str(SCRIPTS)) from chart_view_points.api import ChartPointsInputError, SIGNS, compute_chart_points # noqa: E402 # Real engine /api/chart output (1990-06-15 12:00 Beijing, raman), shared with the frontend tests. GOLDEN = json.loads((ROOT / "frontend" / "tests" / "fixtures" / "chart-insight-golden.json").read_text(encoding="utf-8")) BODY = { "year": 1990, "month": 6, "day": 15, "hour": 12, "minute": 0, "second": 0, "lat": 39.9042, "lon": 116.4074, "tz": 8, "ayanamsa": "raman", "planets": GOLDEN["chart"]["planets"], "ascendant": GOLDEN["chart"]["ascendant"], } def _sign_index(longitude: float) -> int: return int((longitude % 360) // 30) def test_every_block_is_computed_from_the_engine_producers(): result = compute_chart_points(BODY) assert result["success"] is True assert result["upagrahas"]["status"] == "computed" assert [row["key"] for row in result["upagrahas"]["rows"]][:5] == ["Dhuma", "Vyatipata", "Parivesha", "Indrachapa", "Upaketu"] assert len(result["upagrahas"]["rows"]) == 11 assert result["special_lagnas"]["status"] == "computed" assert len(result["special_lagnas"]["rows"]) == 8 assert [row["key"] for row in result["yogi_points"]["rows"]] == ["Yogi_Point", "Avayogi_Point"] assert [row["key"] for row in result["arudha_padas"]["rows"]] == ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "UL"] assert len(result["friendship"]["rows"]) == 9 def test_points_carry_sign_and_house_counted_from_the_ascendant(): result = compute_chart_points(BODY) asc = _sign_index(float(BODY["ascendant"]["lon"])) for block in ("upagrahas", "special_lagnas", "yogi_points"): for row in result[block]["rows"]: sign = _sign_index(row["longitude"]) assert row["sign"] == SIGNS[sign] assert row["house"] == (sign - asc) % 12 + 1 assert 0 <= row["degree_in_sign"] < 30 def test_solar_upagrahas_follow_the_sun_formula(): sun = float(BODY["planets"]["Sun"]["lon"]) rows = {row["key"]: row["longitude"] for row in compute_chart_points(BODY)["upagrahas"]["rows"]} dhuma = (sun + 133 + 20 / 60) % 360 assert rows["Dhuma"] == pytest.approx(dhuma, abs=1e-3) assert rows["Vyatipata"] == pytest.approx((360 - dhuma) % 360, abs=1e-3) assert rows["Parivesha"] == pytest.approx((rows["Vyatipata"] + 180) % 360, abs=1e-3) def test_avayogi_is_yogi_plus_186_40(): rows = {row["key"]: row["longitude"] for row in compute_chart_points(BODY)["yogi_points"]["rows"]} assert (rows["Avayogi_Point"] - rows["Yogi_Point"]) % 360 == pytest.approx(186 + 40 / 60, abs=1e-3) def test_friendship_compound_is_natural_plus_temporary(): rows = compute_chart_points(BODY)["friendship"]["rows"] sun = next(row for row in rows if row["planet"] == "Sun") for target in sun["compound"]["great_friends"]: assert target in sun["natural"]["friends"] and target in sun["temporary"]["friends"] for target in sun["compound"]["great_enemies"]: assert target in sun["natural"]["enemies"] and target in sun["temporary"]["enemies"] def test_one_failing_block_does_not_fail_the_response(monkeypatch): import gulika def broken(*_args, **_kwargs): raise RuntimeError("boom") monkeypatch.setattr(gulika, "calculate_upagrahas", broken) result = compute_chart_points(BODY) assert result["upagrahas"]["status"] == "blocked" assert result["arudha_padas"]["status"] == "computed" def test_missing_birth_or_chart_is_a_bad_request(): with pytest.raises(ChartPointsInputError): compute_chart_points({**BODY, "planets": None}) with pytest.raises(ChartPointsInputError): compute_chart_points({key: value for key, value in BODY.items() if key != "lat"}) def test_route_is_registered_thinly(): source = (SCRIPTS / "jyotish_api_server.py").read_text(encoding="utf-8") assert "elif path == '/api/chart_points':" in source assert "_load_local_module('chart_view_points.api')" in source