# -*- coding: utf-8 -*- """认证接口端到端测试(使用临时 SQLite 数据库,不污染 data/)。""" from __future__ import annotations DEMO_USERNAME = "pine" DEMO_PASSWORD = "123456" def login(client, username=DEMO_USERNAME, password=DEMO_PASSWORD) -> dict: res = client.post("/auth/login", json={"username": username, "password": password}) assert res.status_code == 200, res.text return res.json() def auth(token: str) -> dict: return {"Authorization": f"Bearer {token}"} # --------------------------------------------------------------------------- # 基础 / 状态 # --------------------------------------------------------------------------- def test_health(client): res = client.get("/health") assert res.status_code == 200 assert res.json()["status"] == "ok" def test_status(client): res = client.get("/auth/status") assert res.status_code == 200 assert res.json() == {"enabled": True, "has_users": True} # --------------------------------------------------------------------------- # 登录 / 注册 # --------------------------------------------------------------------------- def test_login_success_returns_profile_and_token(client): data = login(client) assert data["token"] assert data["username"] == DEMO_USERNAME # 演示资料字段齐全 for field in ("nickname", "account", "company", "room", "avatar", "company_avatar"): assert field in data def test_login_wrong_password(client): res = client.post( "/auth/login", json={"username": DEMO_USERNAME, "password": "wrong"}, ) assert res.status_code == 401 assert res.json()["detail"] == "Invalid username or password" def test_register_denied_when_users_exist(client): res = client.post( "/auth/register", json={"username": "13800001234", "password": "whatever1"}, ) assert res.status_code == 403 assert res.json()["detail"] == "User already registered" # --------------------------------------------------------------------------- # 令牌校验 / 当前用户 # --------------------------------------------------------------------------- def test_verify_with_token(client): token = login(client)["token"] res = client.get("/auth/verify", headers=auth(token)) assert res.status_code == 200 assert res.json() == {"valid": True, "username": DEMO_USERNAME} def test_verify_without_token(client): res = client.get("/auth/verify") assert res.status_code == 401 def test_me_returns_profile(client): token = login(client)["token"] res = client.get("/auth/me", headers=auth(token)) assert res.status_code == 200 assert res.json()["username"] == DEMO_USERNAME assert res.json()["nickname"] # --------------------------------------------------------------------------- # 资料 / 凭据更新 # --------------------------------------------------------------------------- def test_update_profile_fields(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"nickname": "新昵称", "account": "新账号", "room": "2001"}, ) assert res.status_code == 200, res.text body = res.json() assert body["nickname"] == "新昵称" assert body["account"] == "新账号" assert body["room"] == "2001" # 仅改资料不重签令牌 assert body["token"] == "" def test_update_profile_nothing_to_update(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"current_password": DEMO_PASSWORD}, ) assert res.status_code == 400 assert res.json()["detail"] == "Nothing to update" def test_update_profile_wrong_current_password(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"current_password": "wrong", "new_password": "newpass123"}, ) assert res.status_code == 401 assert res.json()["detail"] == "Current password is incorrect" def test_update_profile_changes_password_and_reissues_token(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"current_password": DEMO_PASSWORD, "new_password": "newpass123"}, ) assert res.status_code == 200, res.text body = res.json() assert body["token"] # 新令牌 # 旧令牌失效,新令牌可用 assert client.get("/auth/verify", headers=auth(token)).status_code == 401 assert ( client.get("/auth/verify", headers=auth(body["token"])).status_code == 200 ) # 新密码可登录,旧密码不可 assert login(client, password="newpass123")["token"] assert ( client.post( "/auth/login", json={"username": DEMO_USERNAME, "password": DEMO_PASSWORD}, ).status_code == 401 ) def test_update_profile_changes_username(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"current_password": DEMO_PASSWORD, "new_username": "pine2"}, ) assert res.status_code == 200, res.text assert res.json()["username"] == "pine2" assert res.json()["token"] # 新用户名可登录 res = client.post( "/auth/login", json={"username": "pine2", "password": DEMO_PASSWORD}, ) assert res.status_code == 200 def test_update_profile_empty_new_password_rejected(client): token = login(client)["token"] res = client.post( "/auth/update-profile", headers=auth(token), json={"current_password": DEMO_PASSWORD, "new_password": " "}, ) assert res.status_code == 400 assert "cannot be empty" in res.json()["detail"] # --------------------------------------------------------------------------- # 令牌吊销 # --------------------------------------------------------------------------- def test_revoke_current_token(client): token = login(client)["token"] res = client.post("/auth/revoke-token", headers=auth(token), json={}) assert res.status_code == 200 assert res.json()["revoked"] is True assert res.json()["revoked_current_token"] is True assert client.get("/auth/verify", headers=auth(token)).status_code == 401 def test_revoke_specific_token(client): token_a = login(client)["token"] token_b = login(client)["token"] res = client.post( "/auth/revoke-token", headers=auth(token_a), json={"token": token_b}, ) assert res.status_code == 200 assert res.json()["revoked_current_token"] is False assert client.get("/auth/verify", headers=auth(token_b)).status_code == 401 assert client.get("/auth/verify", headers=auth(token_a)).status_code == 200 def test_revoke_all_tokens(client): token_a = login(client)["token"] token_b = login(client)["token"] res = client.post("/auth/revoke-all-tokens", headers=auth(token_a), json={}) assert res.status_code == 200 assert res.json()["revoked"] is True assert client.get("/auth/verify", headers=auth(token_a)).status_code == 401 assert client.get("/auth/verify", headers=auth(token_b)).status_code == 401 # --------------------------------------------------------------------------- # 落盘格式 # --------------------------------------------------------------------------- def test_persisted_user_has_no_plaintext_password(client, tmp_path): login(client) # 从 SQLite 读取用户记录,断言不落明文密码 import sqlite3 conn = sqlite3.connect(tmp_path / "test.db") row = conn.execute( "SELECT username, password_hash, password_salt FROM users WHERE username=?", (DEMO_USERNAME,), ).fetchone() conn.close() assert row is not None username, password_hash, password_salt = row assert username == DEMO_USERNAME assert password_hash assert password_salt # users 表没有 password 明文列 conn = sqlite3.connect(tmp_path / "test.db") cols = [r[1] for r in conn.execute("PRAGMA table_info(users)")] conn.close() assert "password" not in cols