from __future__ import annotations import tempfile import threading import unittest from pathlib import Path import server from backend.application import DashboardService, RequestHandler from backend.bootstrap.config import APP_DIR, STATIC_DIR from backend.features.accounts.repository import AccountRepositoryMixin from backend.features.accounts.security import SecretVault, token_hash from backend.features.accounts.service import AccountService from backend.http.handler import HttpTransportMixin from database import ReviewDatabase class AccountSliceStructureTests(unittest.TestCase): def test_original_entrypoint_exports_canonical_runtime(self) -> None: self.assertIs(server.DashboardService, DashboardService) self.assertIs(server.RequestHandler, RequestHandler) self.assertIs(server.SERVICE, RequestHandler.application_service) def test_runtime_paths_still_point_at_app_root(self) -> None: self.assertEqual(APP_DIR, Path(__file__).resolve().parents[1]) self.assertEqual(STATIC_DIR, APP_DIR / "frontend") def test_account_persistence_and_http_transport_have_single_owners(self) -> None: for method in ( "create_user", "session_user", "update_membership", "save_user_birth_profile", ): self.assertNotIn(method, ReviewDatabase.__dict__) self.assertIn(method, AccountRepositoryMixin.__dict__) for method in ( "require_auth", "require_csrf", "require_access", "serve_static", "send_json", "_write_stream_event", "send_ndjson_stream", ): self.assertNotIn(method, RequestHandler.__dict__) self.assertIn(method, HttpTransportMixin.__dict__) class AccountSliceBehaviorTests(unittest.TestCase): def setUp(self) -> None: self.temporary = tempfile.TemporaryDirectory() self.database = ReviewDatabase(Path(self.temporary.name) / "review.db") self.vault = SecretVault(SecretVault.generate_key()) self.context: dict[str, object] = {"user_id": 0, "access": {}} def bind_user(user_id: int) -> None: self.context["user_id"] = user_id self.context["access"] = self.database.user_access(user_id) or {} self.service = AccountService( database=self.database, vault=self.vault, current_user_supplier=lambda: int(self.context["user_id"]), access_supplier=lambda: dict(self.context["access"]), bind_user=bind_user, personal_field_builder=lambda *args: { "birth": "private", "day_master": "甲木", "current": {"trade_date": args[2]}, "notice": "test", }, auth_lock=threading.Lock(), ) def tearDown(self) -> None: self.temporary.cleanup() def test_register_login_session_and_password_contract(self) -> None: registered = self.service.register("owner_01", "Password123") self.assertEqual(registered["user"]["role"], "admin") self.assertTrue(registered["user"]["membership"]["active"]) self.assertIsNotNone( self.database.session_user(token_hash(registered["session_token"])) ) with self.assertRaisesRegex(ValueError, "账号名或密码不正确"): self.service.login("owner_01", "wrong-password") self.service.change_password("Password123", "NewPassword456") logged_in = self.service.login("owner_01", "NewPassword456") self.assertEqual(logged_in["user"]["id"], registered["user"]["id"]) def test_membership_and_birth_profile_remain_account_scoped(self) -> None: owner = self.service.register("owner_02", "Password123") other = self.database.create_user("other_02", "salt", "hash") self.service.update_membership( {"user_id": other["id"], "status": "active", "duration": "3_months"} ) other_access = self.database.user_access(other["id"]) self.assertEqual(other_access["membership_plan"], "3个月") self.assertTrue(AccountService.membership_for_access(other_access)["subscribed"]) personal = self.service.save_birth_profile( { "birth_datetime": "1990-01-01 08:30", "gender": "male", "trade_date": "2026-07-30", } ) self.assertNotIn("birth", personal) self.assertEqual(personal["day_master"], "甲木") self.assertTrue(self.database.get_user_birth_profile(owner["user"]["id"])) self.assertEqual(self.database.get_user_birth_profile(other["id"]), "") if __name__ == "__main__": unittest.main()