api/tests/test_dev_stack.py
CC Worker f3da9f3b59
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
fix: explicit apikey header + resilient dev-stack seed-count baselines
- client.py: set apikey explicitly in _create_base_client headers (Kong needs it
  on every request; for per-user clients apikey stays anon while Authorization
  carries the user JWT). Fixes the 2 stale header unit tests that asserted apikey
  in options.headers, and is robust against supabase-py default-header changes.
- test_dev_stack: exact == seed counts → >= baselines. The greenfield seed sets a
  floor; additive exam-marker fixtures (S4-4 cohort) legitimately push live .94
  counts above the old snapshot. >= still catches a broken/missing seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:25:39 +00:00

87 lines
3.3 KiB
Python

import os
import requests
def _supabase_headers():
key = os.getenv('SERVICE_ROLE_KEY') or os.getenv('ANON_KEY')
assert key, 'SERVICE_ROLE_KEY or ANON_KEY must be set for Supabase integration tests'
return {
'apikey': key,
'Authorization': f'Bearer {key}',
'Prefer': 'count=exact',
}
def _rest_count(table: str) -> int:
supabase_url = os.getenv('SUPABASE_URL')
assert supabase_url, 'SUPABASE_URL must be set'
response = requests.get(
f'{supabase_url.rstrip("/")}/rest/v1/{table}',
headers=_supabase_headers(),
params={'select': 'id'},
timeout=15,
)
assert response.status_code in (200, 206), response.text[:500]
content_range = response.headers.get('content-range', '')
assert '/' in content_range, f'missing exact content-range count for {table}: {content_range!r}'
return int(content_range.rsplit('/', 1)[1])
def test_dev_environment_points_at_dev_supabase():
assert os.getenv('SUPABASE_URL') == 'http://192.168.0.94:8000'
def test_dev_api_health_endpoint_is_healthy():
health_url = os.getenv('API_HEALTH_URL', 'http://192.168.0.64:18000/health')
response = requests.get(health_url, timeout=15)
assert response.status_code == 200
payload = response.json()
assert payload['status'] == 'healthy'
runtime = payload['runtime']
assert runtime['api_runtime_role'] == 'dev'
assert runtime['start_mode'] == 'dev'
assert runtime['app_environment'] == 'development'
assert runtime['environment'] == 'development'
assert runtime['backend_dev_mode'] is True
assert runtime['compose_project'] == 'api-dev'
assert runtime['supabase_url_host'] == '192.168.0.94'
assert payload['services']['supabase']['status'] == 'healthy'
assert payload['services']['supabase']['url_host'] == '192.168.0.94'
assert payload['services']['redis']['status'] == 'healthy'
assert payload['services']['redis']['environment'] == 'dev'
assert payload['services']['redis']['database'] == 0
# NOTE: these are >= baselines, not exact counts. The greenfield seed produces this floor;
# additive exam-marker fixtures (S4-4 cohort adds ~10 students/memberships; ad-hoc classes) push
# the live .94 counts above it. Exact == froze a snapshot that any new fixture breaks, while >=
# still catches a broken or missing seed.
def test_supabase_dev_seed_core_counts():
assert _rest_count('profiles') >= 21
assert _rest_count('institute_memberships') >= 21
assert _rest_count('institutes') >= 2
def test_supabase_dev_seed_timetable_counts():
assert _rest_count('classes') >= 17
assert _rest_count('taught_lessons') >= 1462
def test_runtime_identity_does_not_expose_secret_values():
health_url = os.getenv('API_HEALTH_URL', 'http://192.168.0.64:18000/health')
response = requests.get(health_url, timeout=15)
assert response.status_code == 200
payload_text = response.text
for secret_name in ('SERVICE_ROLE_KEY', 'ANON_KEY', 'SUPABASE_JWT_SECRET', 'REDIS_PASSWORD'):
secret_value = os.getenv(secret_name)
if secret_value:
assert secret_value not in payload_text
runtime = response.json()['runtime']
assert 'supabase_url' not in runtime
assert 'service_role_key' not in runtime
assert 'anon_key' not in runtime