Files
ai-contract-platform/backend/tests/test_auth_service.py
T
2026-05-03 12:28:38 +08:00

171 lines
7.1 KiB
Python

import unittest
from types import SimpleNamespace
from fastapi import HTTPException
from app.core.security import create_access_token, get_password_hash
from app.services.auth_service import AuthService
class StubUserRepository:
def __init__(self):
self.user = SimpleNamespace(
id="user-admin",
tenant_id="tenant-demo-001",
username="admin@example.com",
display_name="管理员",
password_hash=get_password_hash("123456"),
role="admin",
status="active",
)
self.users_by_username = {self.user.username: self.user}
self.users_by_id = {self.user.id: self.user}
def get_by_username(self, username):
return self.users_by_username.get(username)
def get_by_id(self, user_id):
return self.users_by_id.get(user_id)
def create(self, user):
self.users_by_username[user.username] = user
self.users_by_id[user.id] = user
return user
def update(self, user):
self.users_by_username[user.username] = user
self.users_by_id[user.id] = user
if user.id == self.user.id:
self.user = user
return user
class StubAuditLogService:
def __init__(self):
self.events = []
def record_event(self, **kwargs):
self.events.append(kwargs)
return kwargs
class StubTenantService:
def __init__(self):
self.ensure_default_tenant_calls = 0
def ensure_default_tenant(self):
self.ensure_default_tenant_calls += 1
return {"id": "tenant-demo-001"}
class AuthServiceTestCase(unittest.TestCase):
def setUp(self):
self.service = AuthService(db=None)
self.service.repository = StubUserRepository()
self.service.audit_log_service = StubAuditLogService()
self.original_tenant_service = AuthService.register.__globals__["TenantService"]
self.stub_tenant_service = StubTenantService()
AuthService.register.__globals__["TenantService"] = lambda db: self.stub_tenant_service
def tearDown(self):
AuthService.register.__globals__["TenantService"] = self.original_tenant_service
def test_register_creates_member_user(self):
result = self.service.register("member@example.com", "123456", "测试成员")
self.assertEqual(result["username"], "member@example.com")
self.assertEqual(result["display_name"], "测试成员")
self.assertEqual(result["role"], "member")
self.assertIsNotNone(self.service.repository.get_by_username("member@example.com"))
self.assertEqual(self.stub_tenant_service.ensure_default_tenant_calls, 1)
self.assertEqual(self.service.audit_log_service.events[0]["action"], "register")
self.assertEqual(self.service.audit_log_service.events[0]["resource_type"], "user")
def test_register_rejects_blank_display_name(self):
with self.assertRaises(HTTPException) as context:
self.service.register("member@example.com", "123456", " ")
self.assertEqual(context.exception.status_code, 400)
self.assertEqual(context.exception.detail, "display name is required")
def test_register_rejects_short_password(self):
with self.assertRaises(HTTPException) as context:
self.service.register("member@example.com", "12345", "测试成员")
self.assertEqual(context.exception.status_code, 400)
self.assertEqual(context.exception.detail, "password must be at least 6 characters")
def test_register_then_login_works_with_same_credentials(self):
self.service.register("member@example.com", "123456", "测试成员")
result = self.service.login("member@example.com", "123456")
self.assertEqual(result["user"]["username"], "member@example.com")
self.assertEqual(len(self.service.audit_log_service.events), 2)
self.assertEqual(self.service.audit_log_service.events[0]["action"], "register")
self.assertEqual(self.service.audit_log_service.events[1]["action"], "login")
def test_login_records_audit_event(self):
result = self.service.login("admin@example.com", "123456")
self.assertEqual(result["user"]["id"], "user-admin")
self.assertEqual(len(self.service.audit_log_service.events), 1)
event = self.service.audit_log_service.events[0]
self.assertEqual(event["action"], "login")
self.assertEqual(event["user_id"], "user-admin")
self.assertEqual(event["resource_type"], "user")
self.assertEqual(event["resource_id"], "user-admin")
def test_login_rejects_invalid_credentials_with_chinese_message(self):
with self.assertRaises(HTTPException) as context:
self.service.login("admin@example.com", "wrong-password")
self.assertEqual(context.exception.status_code, 401)
self.assertEqual(context.exception.detail, "账号或密码错误")
def test_seed_default_admin_existing_user_does_not_return_default_password(self):
result = self.service.seed_default_admin()
self.assertEqual(result["username"], "admin@example.com")
self.assertFalse(result["created"])
self.assertIsNone(result["password"])
def test_update_current_user_updates_display_name_and_password(self):
previous_password_hash = self.service.repository.user.password_hash
authorization = f"Bearer {create_access_token('user-admin')}"
result = self.service.update_current_user(authorization, "法务负责人", "654321")
self.assertEqual(result["display_name"], "法务负责人")
self.assertEqual(self.service.repository.user.display_name, "法务负责人")
self.assertNotEqual(self.service.repository.user.password_hash, previous_password_hash)
profile_events = [event for event in self.service.audit_log_service.events if event["action"] == "update_profile"]
self.assertEqual(len(profile_events), 1)
self.assertEqual(profile_events[0]["resource_type"], "user")
self.assertEqual(profile_events[0]["detail_json"]["username"], "admin@example.com")
self.assertEqual(profile_events[0]["detail_json"]["display_name"], "法务负责人")
self.assertTrue(profile_events[0]["detail_json"]["password_updated"])
self.assertNotIn("654321", str(profile_events[0]["detail_json"]))
def test_update_current_user_rejects_blank_display_name(self):
authorization = f"Bearer {create_access_token('user-admin')}"
with self.assertRaises(HTTPException) as context:
self.service.update_current_user(authorization, " ", None)
self.assertEqual(context.exception.status_code, 400)
self.assertEqual(context.exception.detail, "display name is required")
def test_update_current_user_rejects_short_password(self):
authorization = f"Bearer {create_access_token('user-admin')}"
with self.assertRaises(HTTPException) as context:
self.service.update_current_user(authorization, "法务负责人", "12345")
self.assertEqual(context.exception.status_code, 400)
self.assertEqual(context.exception.detail, "password must be at least 6 characters")
if __name__ == "__main__":
unittest.main()