5256bd0d8b
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 46s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m41s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m58s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m33s
CI/CD Pipeline / Unit Tests (push) Successful in 9m54s
CI/CD Pipeline / Integration Tests (push) Successful in 2m17s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m46s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 34s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m39s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m40s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1165h29m51s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1165h40m4s
CI/CD Pipeline / Deploy Production (push) Failing after 1165h40m5s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1165h40m6s
CI/CD Pipeline / CI Gate (push) Failing after 1165h40m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 1165h40m6s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1165h49m59s
CI/CD Pipeline / PR Build API Image (push) Failing after 1165h50m1s
CI/CD Pipeline / Frontend Lint (push) Failing after 1165h50m1s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1165h50m3s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1166h13m10s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1166h23m3s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
270 lines
7.7 KiB
TypeScript
270 lines
7.7 KiB
TypeScript
/**
|
|
* Auth API 测试
|
|
* 对应 api/auth/ 目录化后的模块
|
|
*/
|
|
import { describe, expect, it, vi, beforeEach } from "vitest"
|
|
import {
|
|
normalizeUser,
|
|
login,
|
|
register,
|
|
logout,
|
|
getCurrentUser,
|
|
refreshAccessToken,
|
|
requestPasswordReset,
|
|
resetPassword,
|
|
verifyEmail,
|
|
} from "@/api/auth"
|
|
const mockPost = vi.fn()
|
|
const mockGet = vi.fn()
|
|
const mockAxiosPost = vi.fn()
|
|
|
|
vi.mock("@/api/client", () => ({
|
|
default: {
|
|
post: (...args: unknown[]) => mockPost(...args),
|
|
get: (...args: unknown[]) => mockGet(...args),
|
|
defaults: { baseURL: "/api/v1" },
|
|
},
|
|
}))
|
|
|
|
vi.mock("axios", () => ({
|
|
default: {
|
|
post: (...args: unknown[]) => mockAxiosPost(...args),
|
|
},
|
|
post: (...args: unknown[]) => mockAxiosPost(...args),
|
|
}))
|
|
|
|
vi.mock("antd", () => ({ message: { error: vi.fn(), success: vi.fn() } }))
|
|
|
|
describe("normalizeUser", () => {
|
|
it("normalizes canonical API current-user fields", () => {
|
|
expect(
|
|
normalizeUser({
|
|
user_id: "user-1",
|
|
email: "user@example.com",
|
|
username: "user",
|
|
display_name: "User",
|
|
email_verified: true,
|
|
}),
|
|
).toEqual({
|
|
id: "user-1",
|
|
user_id: "user-1",
|
|
email: "user@example.com",
|
|
username: "user",
|
|
display_name: "User",
|
|
is_email_verified: true,
|
|
email_verified: true,
|
|
created_at: undefined,
|
|
})
|
|
})
|
|
|
|
it("keeps compatibility with legacy UI-shaped user fields", () => {
|
|
expect(
|
|
normalizeUser({
|
|
id: "user-2",
|
|
email: "legacy@example.com",
|
|
username: "legacy",
|
|
display_name: "Legacy",
|
|
is_email_verified: false,
|
|
created_at: "2026-06-22T00:00:00Z",
|
|
}),
|
|
).toEqual({
|
|
id: "user-2",
|
|
user_id: "user-2",
|
|
email: "legacy@example.com",
|
|
username: "legacy",
|
|
display_name: "Legacy",
|
|
is_email_verified: false,
|
|
email_verified: false,
|
|
created_at: "2026-06-22T00:00:00Z",
|
|
})
|
|
})
|
|
|
|
it("prefers id over user_id when both present", () => {
|
|
const result = normalizeUser({
|
|
id: "id-first",
|
|
user_id: "userid-second",
|
|
email: "test@test.com",
|
|
username: "test",
|
|
display_name: "Test",
|
|
})
|
|
expect(result.id).toBe("id-first")
|
|
expect(result.user_id).toBe("id-first")
|
|
})
|
|
|
|
it("prefers is_email_verified over email_verified", () => {
|
|
const result = normalizeUser({
|
|
email: "test@test.com",
|
|
username: "test",
|
|
display_name: "Test",
|
|
is_email_verified: true,
|
|
email_verified: false,
|
|
})
|
|
expect(result.is_email_verified).toBe(true)
|
|
expect(result.email_verified).toBe(true)
|
|
})
|
|
|
|
it("defaults email verified to false when both missing", () => {
|
|
const result = normalizeUser({
|
|
email: "test@test.com",
|
|
username: "test",
|
|
display_name: "Test",
|
|
})
|
|
expect(result.is_email_verified).toBe(false)
|
|
expect(result.email_verified).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("auth API functions", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockPost.mockResolvedValue({ data: { success: true } })
|
|
mockGet.mockResolvedValue({ data: {} })
|
|
mockAxiosPost.mockResolvedValue({ data: { access_token: "tok" } })
|
|
})
|
|
|
|
describe("login", () => {
|
|
it("calls login API with correct params", async () => {
|
|
mockPost.mockResolvedValue({
|
|
data: { access_token: "acc", refresh_token: "ref", user_id: "1" },
|
|
})
|
|
const result = await login({ email: "test@test.com", password: "pass" })
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/login", {
|
|
email: "test@test.com",
|
|
password: "pass",
|
|
})
|
|
expect(result.access_token).toBe("acc")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("login failed"))
|
|
await expect(login({ email: "t", password: "p" })).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("register", () => {
|
|
it("calls register API", async () => {
|
|
mockPost.mockResolvedValue({ data: { message: "ok" } })
|
|
const result = await register({
|
|
email: "test@test.com",
|
|
password: "pass",
|
|
username: "testuser",
|
|
})
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/register", {
|
|
email: "test@test.com",
|
|
password: "pass",
|
|
username: "testuser",
|
|
})
|
|
expect(result.message).toBe("ok")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("register failed"))
|
|
await expect(register({ email: "t", password: "p", username: "u" })).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("logout", () => {
|
|
it("calls logout API", async () => {
|
|
mockPost.mockResolvedValue({ data: {} })
|
|
await logout()
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/logout")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("logout failed"))
|
|
await expect(logout()).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("getCurrentUser", () => {
|
|
it("fetches and normalizes user", async () => {
|
|
mockGet.mockResolvedValue({
|
|
data: {
|
|
user_id: "u1",
|
|
email: "user@test.com",
|
|
username: "user1",
|
|
display_name: "User One",
|
|
email_verified: true,
|
|
},
|
|
})
|
|
const result = await getCurrentUser()
|
|
expect(mockGet).toHaveBeenCalledWith("/auth/me")
|
|
expect(result.id).toBe("u1")
|
|
expect(result.email).toBe("user@test.com")
|
|
expect(result.is_email_verified).toBe(true)
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockGet.mockRejectedValue(new Error("fetch failed"))
|
|
await expect(getCurrentUser()).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("refreshAccessToken", () => {
|
|
it("calls refresh endpoint with raw axios", async () => {
|
|
mockAxiosPost.mockResolvedValue({
|
|
data: { access_token: "new-acc", refresh_token: "new-ref" },
|
|
})
|
|
const result = await refreshAccessToken("old-refresh")
|
|
expect(mockAxiosPost).toHaveBeenCalledWith("/api/v1/auth/refresh", {
|
|
refresh_token: "old-refresh",
|
|
})
|
|
expect(result.access_token).toBe("new-acc")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockAxiosPost.mockRejectedValue(new Error("refresh failed"))
|
|
await expect(refreshAccessToken("tok")).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("requestPasswordReset", () => {
|
|
it("calls forgot-password API", async () => {
|
|
mockPost.mockResolvedValue({ data: { message: "sent" } })
|
|
const result = await requestPasswordReset("test@test.com")
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/forgot-password", {
|
|
email: "test@test.com",
|
|
})
|
|
expect(result.message).toBe("sent")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("failed"))
|
|
await expect(requestPasswordReset("e")).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("resetPassword", () => {
|
|
it("calls reset-password API", async () => {
|
|
mockPost.mockResolvedValue({ data: { message: "reset ok" } })
|
|
const result = await resetPassword("token123", "newpass")
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/reset-password", {
|
|
token: "token123",
|
|
new_password: "newpass",
|
|
})
|
|
expect(result.message).toBe("reset ok")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("failed"))
|
|
await expect(resetPassword("t", "p")).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("verifyEmail", () => {
|
|
it("calls verify-email API", async () => {
|
|
mockPost.mockResolvedValue({ data: { message: "verified" } })
|
|
const result = await verifyEmail("verify-token")
|
|
expect(mockPost).toHaveBeenCalledWith("/auth/verify-email", {
|
|
token: "verify-token",
|
|
})
|
|
expect(result.message).toBe("verified")
|
|
})
|
|
|
|
it("rejects on error", async () => {
|
|
mockPost.mockRejectedValue(new Error("verify failed"))
|
|
await expect(verifyEmail("t")).rejects.toThrow()
|
|
})
|
|
})
|
|
})
|