Files
xiaobai-review/tests/test_account_switch_grants.py
T
f0a1adf52f 施工(HEL-226): 实现登录门户与本机免密切换账号
用设备 Cookie 和授权表记住本机已验证账号,登录页按确认样图做成门户,不再把密码写进浏览器。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-08-29 11:24:39 +08:00

133 lines
6.0 KiB
Python

from __future__ import annotations
import threading
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from tempfile import TemporaryDirectory
from backend.features.accounts.security import SecretVault, token_hash
from backend.features.accounts.service import AccountService
from database import ReviewDatabase
class AccountSwitchGrantTests(unittest.TestCase):
def setUp(self) -> None:
self.temp = TemporaryDirectory()
self.database = ReviewDatabase(Path(self.temp.name) / "review.db")
self.bound_user_id = 0
self.service = AccountService(
database=self.database,
vault=SecretVault(SecretVault.generate_key()),
current_user_supplier=lambda: self.bound_user_id,
access_supplier=lambda: self.database.user_access(self.bound_user_id) or {},
bind_user=self._bind,
personal_field_builder=lambda *args, **kwargs: {},
auth_lock=threading.Lock(),
)
self.device_a = token_hash("device-a-token")
self.device_b = token_hash("device-b-token")
def tearDown(self) -> None:
self.temp.cleanup()
def _bind(self, user_id: int) -> None:
self.bound_user_id = int(user_id)
def _register(self, username: str, device_hash: str = "") -> dict:
return self.service.register(username, "Password123", device_hash or self.device_a)
def test_login_records_accounts_for_the_current_device_only(self) -> None:
first = self._register("alpha_user")
second = self._register("beta_user")
self.service.login("alpha_user", "Password123", self.device_b)
listed = self.service.list_device_accounts(self.device_a)
names = [item["username"] for item in listed["accounts"]]
self.assertEqual(names, ["beta_user", "alpha_user"])
self.assertEqual(
self.service.list_device_accounts(self.device_b)["accounts"][0]["username"],
"alpha_user",
)
self.assertEqual(self.service.list_device_accounts("")["accounts"], [])
self.assertEqual(first["user"]["username"], "alpha_user")
self.assertEqual(second["user"]["username"], "beta_user")
def test_switch_uses_device_grant_and_keeps_the_original_authorization(self) -> None:
first = self._register("alpha_user")
self._register("beta_user")
switched = self.service.switch_account(self.device_a, int(first["user"]["id"]))
self.assertEqual(switched["user"]["username"], "alpha_user")
remaining = {
item["username"]
for item in self.service.list_device_accounts(self.device_a)["accounts"]
}
self.assertEqual(remaining, {"alpha_user", "beta_user"})
def test_switch_without_a_valid_grant_requires_reauthentication(self) -> None:
user = self._register("alpha_user")
with self.assertRaisesRegex(PermissionError, "该账号需重新验证"):
self.service.switch_account(self.device_b, int(user["user"]["id"]))
with self.assertRaisesRegex(PermissionError, "该账号需重新验证"):
self.service.switch_account("", int(user["user"]["id"]))
def test_forget_only_removes_the_current_device_grant(self) -> None:
user = self._register("alpha_user")
self.service.login("alpha_user", "Password123", self.device_b)
self.service.forget_account(self.device_a, int(user["user"]["id"]))
self.service.forget_account(self.device_a, int(user["user"]["id"]))
self.assertEqual(self.service.list_device_accounts(self.device_a)["accounts"], [])
self.assertEqual(
self.service.list_device_accounts(self.device_b)["accounts"][0]["username"],
"alpha_user",
)
def test_logout_revokes_only_the_current_account_on_this_device(self) -> None:
first = self._register("alpha_user")
second = self._register("beta_user")
self.service.revoke_current_device_grant(self.device_a, int(second["user"]["id"]))
names = {
item["username"]
for item in self.service.list_device_accounts(self.device_a)["accounts"]
}
self.assertEqual(names, {"alpha_user"})
switched = self.service.switch_account(self.device_a, int(first["user"]["id"]))
self.assertEqual(switched["user"]["id"], first["user"]["id"])
def test_password_change_revokes_grants_on_every_device(self) -> None:
user = self._register("alpha_user")
self.service.login("alpha_user", "Password123", self.device_b)
self._bind(int(user["user"]["id"]))
self.service.change_password("Password123", "Password456")
self.assertEqual(self.service.list_device_accounts(self.device_a)["accounts"], [])
self.assertEqual(self.service.list_device_accounts(self.device_b)["accounts"], [])
with self.assertRaisesRegex(PermissionError, "该账号需重新验证"):
self.service.switch_account(self.device_a, int(user["user"]["id"]))
def test_device_keeps_at_most_five_accounts(self) -> None:
usernames = [f"user_{index}" for index in range(6)]
ids = [self._register(name)["user"]["id"] for name in usernames]
listed = self.service.list_device_accounts(self.device_a)["accounts"]
self.assertEqual(len(listed), 5)
kept = {item["user_id"] for item in listed}
self.assertNotIn(ids[0], kept)
self.assertTrue(set(ids[1:]).issubset(kept))
def test_expired_grants_are_removed_lazily(self) -> None:
user = self._register("alpha_user")
past = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat(timespec="seconds")
self.database.upsert_switch_grant(
self.device_a,
int(user["user"]["id"]),
past,
past,
past,
)
self.assertEqual(self.service.list_device_accounts(self.device_a)["accounts"], [])
with self.assertRaisesRegex(PermissionError, "该账号需重新验证"):
self.service.switch_account(self.device_a, int(user["user"]["id"]))
if __name__ == "__main__":
unittest.main()