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