"""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)