메일 알림 시스템 추가

This commit is contained in:
Yun Chan 2026-06-29 17:07:01 +09:00
parent ddf12a851c
commit 3bf38c50df
22 changed files with 1769 additions and 31 deletions

View file

@ -8,7 +8,7 @@ from contextlib import contextmanager
from typing import Any
from urllib.parse import parse_qs, urlencode, urlsplit
from fastapi import Response
from fastapi import HTTPException, Response
from starlette.requests import Request
from unittest.mock import AsyncMock, patch
@ -17,6 +17,7 @@ from .config import Settings, settings
from .deps import Principal, Role
from .routes import admin as admin_routes
from .routes import auth as auth_routes
from .routes import sessions as session_routes
from .saml import inflate_redirect_request
@ -355,6 +356,38 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase):
hoonjung_role = auth_routes._role_for_email("hoonjungkoo@hs.ac.kr")
self.assertEqual(hoonjung_role, Role.ADMIN)
async def test_pending_provider_user_enqueues_account_approval_notification(self) -> None:
with (
patched_settings(
environment="dev",
auth_new_user_default_status="pending",
auth_super_admin_emails=[],
auth_admin_emails=[],
auth_teacher_emails=[],
auth_approved_emails=[],
),
patch.object(auth_sessions, "get_pool", side_effect=RuntimeError("no db")),
patch.object(
auth_sessions.notifications,
"enqueue_account_pending_approval",
AsyncMock(),
) as enqueue_approval,
):
_, user = await auth_sessions.create_session(
email="new-approval@hs.ac.kr",
display_name="New Approval",
role="learner",
external_id="google:new-approval",
)
self.assertEqual(user.account_status, "pending")
enqueue_approval.assert_awaited_once_with(
user_id=user.user_id,
email="new-approval@hs.ac.kr",
display_name="New Approval",
role="learner",
)
async def test_super_admin_can_enter_teacher_and_learner_role_guards(self) -> None:
principal = Principal(
user_id="00000000-0000-0000-0000-000000000601",
@ -373,6 +406,41 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase):
self.assertTrue(principal.can_access_role(Role.TEACHER))
self.assertTrue(principal.can_access_role(Role.ADMIN))
async def test_admin_role_can_enter_all_role_spaces_without_super_admin(self) -> None:
principal = Principal(
user_id="00000000-0000-0000-0000-000000000602",
role=Role.ADMIN,
email="operator@twentyoz.kr",
display_name="Operator Admin",
admin_access=True,
super_admin=False,
)
teacher_checker = deps.require_role(Role.TEACHER)
teacher_view = await teacher_checker(principal)
learner_view = session_routes._ensure_learner(principal)
self.assertEqual(teacher_view.role, Role.TEACHER)
self.assertEqual(learner_view.role, Role.LEARNER)
self.assertTrue(principal.can_access_role(Role.LEARNER))
self.assertTrue(principal.can_access_role(Role.TEACHER))
self.assertTrue(principal.can_access_role(Role.ADMIN))
async def test_admin_access_flag_without_admin_role_does_not_enter_learner_space(self) -> None:
principal = Principal(
user_id="00000000-0000-0000-0000-000000000603",
role=Role.TEACHER,
email="teacher-admin@hs.ac.kr",
display_name="Teacher Admin",
admin_access=True,
super_admin=False,
)
self.assertTrue(principal.can_access_role(Role.ADMIN))
self.assertFalse(principal.can_access_role(Role.LEARNER))
with self.assertRaises(HTTPException):
session_routes._ensure_learner(principal)
async def test_only_super_admin_can_grant_admin_access(self) -> None:
with (
patched_settings(