api/tests/test_p0_api_security.py
CC Worker 93972a62f7
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
fix: revert explicit apikey header (caused Kong duplicate-apikey 401)
The previous commit added apikey to _create_base_client headers, but supabase-py
already sets apikey from the key arg → two apikey headers → Kong rejected every
as-user call with 401 'Duplicate API key found' (exam API 502'd on auth). Revert
to Authorization-only; fix the two header unit tests to assert the real contract
(apikey via the key arg; options.headers carries only the user Authorization).

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

110 lines
3.7 KiB
Python

import pytest
from fastapi import HTTPException
def test_classes_school_students_route_registered_before_dynamic_class_id():
from routers.database.tools.classes_router import router
paths = [route.path for route in router.routes]
assert paths.index('/school/students') < paths.index('/{class_id}')
def test_supabase_anon_for_user_sets_user_authorization_header(monkeypatch):
from modules.database.supabase.utils import client as client_module
captured = {}
def fake_create_client(url, key, options=None):
captured['url'] = url
captured['key'] = key
captured['options'] = options
return object()
monkeypatch.setenv('SUPABASE_URL', 'http://supabase.test')
monkeypatch.setenv('ANON_KEY', 'anon-key')
monkeypatch.setattr(client_module, 'create_client', fake_create_client)
client_module.SupabaseAnonClient.for_user('Bearer user-jwt')
# apikey comes from the `key` arg (supabase-py sets the apikey header); options.headers must
# carry only the user Authorization override. A second apikey here → Kong "Duplicate API key".
assert captured['key'] == 'anon-key'
assert 'apikey' not in captured['options'].headers
assert captured['options'].headers['Authorization'] == 'Bearer user-jwt'
@pytest.mark.parametrize('token', ['', ' '])
def test_supabase_anon_for_user_requires_token(token):
from modules.database.supabase.utils.client import SupabaseAnonClient
with pytest.raises(ValueError):
SupabaseAnonClient.for_user(token)
def test_tldraw_malformed_snapshot_falls_back_to_default():
from routers.database.tools import tldraw_supabase_storage as storage
assert not storage._is_valid_tldraw_snapshot({'document': {}, 'session': {}})
default = storage.create_default_tldraw_content()
assert storage._is_valid_tldraw_snapshot(default)
assert default['document']['schema']['schemaVersion'] == 2
def test_tldraw_rejects_cross_tenant_snapshot_db(monkeypatch):
from routers.database.tools import tldraw_supabase_storage as storage
monkeypatch.setattr(storage, '_user_scope', lambda user_id: {
'user_id': user_id,
'teacher_db': f"cc.users.teacher.{user_id.replace('-', '')}",
'institute_id': 'school-1',
'institute_db': 'cc.institutes.allowed',
'curriculum_db': 'cc.institutes.allowed.curriculum',
})
with pytest.raises(HTTPException) as exc:
storage._authorize_snapshot_path(
'cc.public.snapshots/School/other-school',
'cc.institutes.other',
{'sub': 'user-1'},
write=False,
)
assert exc.value.status_code == 403
def test_graph_node_children_rejects_unscoped_db(monkeypatch):
from routers.database.tools import graph_tree_router
monkeypatch.setattr(graph_tree_router, '_allowed_neo4j_dbs', lambda user_id, email: {'cc.users.teacher.user1'})
with pytest.raises(HTTPException) as exc:
graph_tree_router._require_allowed_neo4j_db(
'cc.institutes.not-mine',
'SubjectClass',
'classes',
'user-1',
'teacher@example.test',
)
assert exc.value.status_code == 403
def test_graph_node_children_allows_global_calendar_only():
from routers.database.tools import graph_tree_router
graph_tree_router._require_allowed_neo4j_db(
'classroomcopilot',
'CalendarYear',
'',
'user-1',
'teacher@example.test',
)
with pytest.raises(HTTPException) as exc:
graph_tree_router._require_allowed_neo4j_db(
'classroomcopilot',
'School',
'school',
'user-1',
'teacher@example.test',
)
assert exc.value.status_code == 403