"""登录方式的开关,以及用邮箱登录。 三条是这个功能的重点,也是测试的重点: 1. **短信验证码能整体关掉**。关掉后连"发码"这一步都不该通——否则等于留了个后门: 内置验证码仍然对得上,只是没人去点。 2. **邮箱是账号名,不是联系方式**:绑定之后能用它登录,改绑立刻生效、解绑立刻失效。 3. **邮箱唯一且大小写不敏感**:`Shufen@126.com` 和 `shufen@126.com` 是同一个人, 不能让第二个账号绑走。 """ from __future__ import annotations from httpx import AsyncClient from app.config import get_settings from .conftest import login, unwrap settings = get_settings() async def _set_password(client: AsyncClient, user: dict, password: str) -> None: unwrap( await client.post( "/api/v1/auth/password", json={"password": password}, headers=user["headers"] ) ) async def _bind_email(client: AsyncClient, user: dict, email: str) -> dict: return unwrap( await client.patch("/api/v1/auth/me", json={"email": email}, headers=user["headers"]) ) async def test_methods_lists_what_is_open(client: AsyncClient) -> None: data = unwrap(await client.get("/api/v1/auth/methods")) assert data["password"] is True assert data["email_password"] is True # 测试环境把短信开着(夹具要靠它造用户);线上默认是 false assert data["sms_code"] is True async def test_sms_login_can_be_closed(client: AsyncClient, monkeypatch) -> None: monkeypatch.setattr(settings, "sms_login_enabled", False) methods = unwrap(await client.get("/api/v1/auth/methods")) assert methods["sms_code"] is False sent = await client.post("/api/v1/auth/sms/send", json={"phone": "13800138000"}) assert sent.json()["code"] == 2017 # 就算手里还捏着旧验证码,登录这一步也必须关死 logged = await client.post( "/api/v1/auth/login", json={"phone": "13800138000", "code": settings.sms_dev_code} ) assert logged.json()["code"] == 2017 async def test_bind_email_and_login_with_it(client: AsyncClient) -> None: user = await login(client) await _set_password(client, user, "Dadao2026pw") data = await _bind_email(client, user, " Shufen@126.com ") # 存的是小写,避免大小写绕出第二个账号 assert data["user"]["email"] == "shufen@126.com" logged = unwrap( await client.post( "/api/v1/auth/login/password", json={"email": "SHUFEN@126.COM", "password": "Dadao2026pw"}, ) ) assert logged["user"]["id"] == user["user_id"] async def test_email_login_rejects_wrong_password(client: AsyncClient) -> None: user = await login(client) await _set_password(client, user, "Dadao2026pw") await _bind_email(client, user, "shufen@126.com") resp = await client.post( "/api/v1/auth/login/password", json={"email": "shufen@126.com", "password": "nope-nope-1"} ) assert resp.json()["code"] == 2013 async def test_email_is_validated_and_unique(client: AsyncClient) -> None: first = await login(client) bad = await client.patch( "/api/v1/auth/me", json={"email": "not-an-email"}, headers=first["headers"] ) assert bad.json()["code"] == 2018 await _bind_email(client, first, "shufen@126.com") second = await login(client) taken = await client.patch( "/api/v1/auth/me", json={"email": "SHUFEN@126.com"}, headers=second["headers"] ) assert taken.json()["code"] == 2019 async def test_unbind_email_then_login_by_email_fails(client: AsyncClient) -> None: user = await login(client) await _set_password(client, user, "Dadao2026pw") await _bind_email(client, user, "shufen@126.com") cleared = await _bind_email(client, user, "") assert cleared["user"]["email"] is None resp = await client.post( "/api/v1/auth/login/password", json={"email": "shufen@126.com", "password": "Dadao2026pw"}, ) assert resp.json()["code"] == 2013 async def test_phone_login_still_works(client: AsyncClient) -> None: """老客户端/安卓只发 phone,这条路不能因为加了邮箱就断。""" user = await login(client) await _set_password(client, user, "Dadao2026pw") logged = unwrap( await client.post( "/api/v1/auth/login/password", json={"phone": user["phone"], "password": "Dadao2026pw"}, ) ) assert logged["user"]["id"] == user["user_id"]