Files
xiaoxia-saas/apps/web/src/test/api/auth.test.ts
T
xiaoxia 91ef044e9c
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 8m2s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Failing after 8m14s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 11m2s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
test: 前端单测覆盖率Phase4,行覆盖率51%→62% (#589)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-19 17:17:11 +08:00

267 lines
7.7 KiB
TypeScript

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()
})
})
})