All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
5.5 KiB
Python
127 lines
5.5 KiB
Python
"""
|
|
Integration tests for the PySwiss chart calculation API.
|
|
|
|
These tests drive the TDD implementation of GET /api/chart/.
|
|
They verify the HTTP contract using Django's test client.
|
|
|
|
Run:
|
|
pyswiss/.venv/Scripts/python pyswiss/manage.py test pyswiss/apps/charts
|
|
"""
|
|
from django.test import TestCase
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# J2000.0 — a well-known reference point: Sun at ~280.37° (Capricorn 10°22')
|
|
J2000 = '2000-01-01T12:00:00Z'
|
|
LONDON = {'lat': 51.5074, 'lon': -0.1278}
|
|
|
|
|
|
class ChartApiTest(TestCase):
|
|
"""GET /api/chart/ — calculate a natal chart from datetime + coordinates."""
|
|
|
|
def _get(self, params):
|
|
return self.client.get('/api/chart/', params)
|
|
|
|
# ── guards ────────────────────────────────────────────────────────────
|
|
|
|
def test_chart_returns_400_if_dt_missing(self):
|
|
response = self._get({'lat': 51.5074, 'lon': -0.1278})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_chart_returns_400_if_lat_missing(self):
|
|
response = self._get({'dt': J2000, 'lon': -0.1278})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_chart_returns_400_if_lon_missing(self):
|
|
response = self._get({'dt': J2000, 'lat': 51.5074})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_chart_returns_400_for_invalid_dt_format(self):
|
|
response = self._get({'dt': 'not-a-date', **LONDON})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_chart_returns_400_for_out_of_range_lat(self):
|
|
response = self._get({'dt': J2000, 'lat': 999, 'lon': -0.1278})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
# ── response shape ────────────────────────────────────────────────────
|
|
|
|
def test_chart_returns_200_for_valid_params(self):
|
|
response = self._get({'dt': J2000, **LONDON})
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_chart_response_is_json(self):
|
|
response = self._get({'dt': J2000, **LONDON})
|
|
self.assertIn('application/json', response['Content-Type'])
|
|
|
|
def test_chart_returns_all_ten_planets(self):
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
expected = {
|
|
'Sun', 'Moon', 'Mercury', 'Venus', 'Mars',
|
|
'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto',
|
|
}
|
|
self.assertEqual(set(data['planets'].keys()), expected)
|
|
|
|
def test_each_planet_has_sign_degree_and_retrograde(self):
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
for name, planet in data['planets'].items():
|
|
with self.subTest(planet=name):
|
|
self.assertIn('sign', planet)
|
|
self.assertIn('degree', planet)
|
|
self.assertIn('retrograde', planet)
|
|
|
|
def test_chart_returns_houses(self):
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
houses = data['houses']
|
|
self.assertEqual(len(houses['cusps']), 12)
|
|
self.assertIn('asc', houses)
|
|
self.assertIn('mc', houses)
|
|
|
|
def test_chart_returns_six_element_counts(self):
|
|
"""Fire/Water/Earth/Air are sign-based counts; Time/Space are emergent."""
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
for key in ('Fire', 'Water', 'Earth', 'Air', 'Time', 'Space'):
|
|
with self.subTest(element=key):
|
|
self.assertIn(key, data['elements'])
|
|
|
|
def test_chart_reports_active_house_system(self):
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
self.assertIn('house_system', data)
|
|
|
|
# ── calculation correctness ───────────────────────────────────────────
|
|
|
|
def test_sun_is_in_capricorn_at_j2000(self):
|
|
"""Regression: Sun at J2000.0 is ~280.37° — Capricorn."""
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
sun = data['planets']['Sun']
|
|
self.assertEqual(sun['sign'], 'Capricorn')
|
|
self.assertAlmostEqual(sun['degree'], 280.37, delta=0.1)
|
|
|
|
def test_sun_is_not_retrograde(self):
|
|
"""The Sun never goes retrograde."""
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
self.assertFalse(data['planets']['Sun']['retrograde'])
|
|
|
|
def test_element_counts_sum_to_ten(self):
|
|
"""All 10 planets are assigned to exactly one classical element."""
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
classical = sum(
|
|
data['elements'][e] for e in ('Fire', 'Water', 'Earth', 'Air')
|
|
)
|
|
self.assertEqual(classical, 10)
|
|
|
|
# ── house system ──────────────────────────────────────────────────────
|
|
|
|
def test_default_house_system_is_porphyry(self):
|
|
"""Porphyry ('O') is the project default — no param needed."""
|
|
data = self._get({'dt': J2000, **LONDON}).json()
|
|
self.assertEqual(data['house_system'], 'O')
|
|
|
|
def test_non_superuser_cannot_override_house_system(self):
|
|
"""House system override is superuser-only; plain requests get 403."""
|
|
response = self._get({'dt': J2000, **LONDON, 'house_system': 'P'})
|
|
self.assertEqual(response.status_code, 403)
|