63d4048354
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
160 lines
5.0 KiB
Python
Executable File
160 lines
5.0 KiB
Python
Executable File
"""Auth ports (ABC接口) 单元测试.
|
|
|
|
验证抽象接口定义正确:不能直接实例化,子类必须实现所有抽象方法。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC
|
|
|
|
import pytest
|
|
from domain.auth.email_service import EmailServicePort
|
|
from domain.auth.jwt_service import JWTServicePort
|
|
from domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
|
|
from domain.auth.session_store import SessionStorePort
|
|
from domain.auth.sms_service import SmsService
|
|
|
|
|
|
class TestSessionStorePort:
|
|
"""SessionStorePort 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(SessionStorePort, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
SessionStorePort() # type: ignore[misc]
|
|
|
|
def test_has_abstract_methods(self):
|
|
abstract_methods = SessionStorePort.__abstractmethods__
|
|
expected = {
|
|
"save_session",
|
|
"get_session",
|
|
"get_session_by_refresh_token",
|
|
"get_refresh_token",
|
|
"update_last_active",
|
|
"delete_session",
|
|
"get_user_sessions",
|
|
"delete_all_user_sessions",
|
|
"session_exists",
|
|
}
|
|
assert expected.issubset(abstract_methods)
|
|
|
|
def test_concrete_subclass_works(self):
|
|
class ConcreteStore(SessionStorePort):
|
|
def save_session(self, **kwargs): # type: ignore[override]
|
|
return True
|
|
|
|
def get_session(self, session_id): # type: ignore[override]
|
|
return None
|
|
|
|
def get_session_by_refresh_token(self, token): # type: ignore[override]
|
|
return None
|
|
|
|
def get_refresh_token(self, session_id): # type: ignore[override]
|
|
return None
|
|
|
|
def update_last_active(self, session_id): # type: ignore[override]
|
|
return True
|
|
|
|
def delete_session(self, session_id): # type: ignore[override]
|
|
return True
|
|
|
|
def get_user_sessions(self, user_id): # type: ignore[override]
|
|
return []
|
|
|
|
def delete_all_user_sessions(self, user_id): # type: ignore[override]
|
|
return 0
|
|
|
|
def session_exists(self, session_id): # type: ignore[override]
|
|
return False
|
|
|
|
store = ConcreteStore()
|
|
assert isinstance(store, SessionStorePort)
|
|
assert store.session_exists("s1") is False
|
|
assert store.delete_session("s1") is True
|
|
|
|
|
|
class TestEmailServicePort:
|
|
"""EmailServicePort 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(EmailServicePort, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
EmailServicePort() # type: ignore[misc]
|
|
|
|
def test_has_abstract_methods(self):
|
|
abstract_methods = EmailServicePort.__abstractmethods__
|
|
expected = {"send_email", "send_verification_email", "send_password_reset_email"}
|
|
assert expected.issubset(abstract_methods)
|
|
|
|
|
|
class TestJWTServicePort:
|
|
"""JWTServicePort 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(JWTServicePort, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
JWTServicePort() # type: ignore[misc]
|
|
|
|
def test_has_abstract_methods(self):
|
|
abstract_methods = JWTServicePort.__abstractmethods__
|
|
expected = {
|
|
"create_access_token",
|
|
"create_refresh_token",
|
|
"verify_token",
|
|
"verify_access_token",
|
|
"verify_refresh_token",
|
|
}
|
|
assert expected.issubset(abstract_methods)
|
|
|
|
|
|
class TestPasswordHasherPort:
|
|
"""PasswordHasherPort 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(PasswordHasherPort, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
PasswordHasherPort() # type: ignore[misc]
|
|
|
|
def test_has_abstract_methods(self):
|
|
abstract_methods = PasswordHasherPort.__abstractmethods__
|
|
expected = {"hash_password", "verify_password", "needs_rehash"}
|
|
assert expected.issubset(abstract_methods)
|
|
|
|
|
|
class TestPasswordValidatorPort:
|
|
"""PasswordValidatorPort 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(PasswordValidatorPort, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
PasswordValidatorPort() # type: ignore[misc]
|
|
|
|
def test_has_validate_method(self):
|
|
assert "validate" in PasswordValidatorPort.__abstractmethods__
|
|
|
|
|
|
class TestSmsService:
|
|
"""SmsService 接口测试."""
|
|
|
|
def test_is_abstract(self):
|
|
assert issubclass(SmsService, ABC)
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
SmsService() # type: ignore[misc]
|
|
|
|
def test_has_abstract_methods(self):
|
|
abstract_methods = SmsService.__abstractmethods__
|
|
expected = {"send_verification_code", "send_template_sms"}
|
|
assert expected.issubset(abstract_methods)
|