""" 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)