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