Spaces:
Sleeping
Sleeping
| //@vitest-environment node | |
| import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; | |
| // Mock server-only module | |
| vi.mock("server-only", () => ({})); | |
| // Mock dependencies | |
| vi.mock("lib/db/repository", () => ({ | |
| userRepository: { | |
| getUserById: vi.fn(), | |
| getUserStats: vi.fn(), | |
| getPreferences: vi.fn(), | |
| updateUserDetails: vi.fn(), | |
| }, | |
| })); | |
| vi.mock("auth/server", () => ({ | |
| auth: { | |
| api: { | |
| listUserAccounts: vi.fn(), | |
| listSessions: vi.fn(), | |
| }, | |
| }, | |
| getSession: vi.fn(), | |
| })); | |
| vi.mock("next/headers", () => ({ | |
| headers: vi.fn(), | |
| })); | |
| vi.mock("next/navigation", () => ({ | |
| notFound: vi.fn(), | |
| })); | |
| const { auth, getSession } = await import("auth/server"); | |
| const { headers } = await import("next/headers"); | |
| const { notFound } = await import("next/navigation"); | |
| import { | |
| getUserAccounts, | |
| getUserIdAndCheckAccess, | |
| updateUserDetails, | |
| } from "./server"; | |
| /* | |
| * Tests focus on the business logic of the user server. | |
| */ | |
| describe("User Server", () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| describe("getUserAccounts - Account Type Detection", () => { | |
| beforeEach(() => { | |
| vi.mocked(getSession).mockResolvedValue({ | |
| user: { id: "user-1" }, | |
| } as any); | |
| vi.mocked(headers).mockResolvedValue(new Headers()); | |
| }); | |
| it("should correctly identify password vs OAuth accounts", async () => { | |
| const mockAccounts = [ | |
| { providerId: "credential", id: "1" }, | |
| { providerId: "google", id: "2" }, | |
| { providerId: "github", id: "3" }, | |
| ]; | |
| vi.mocked(auth.api.listUserAccounts).mockResolvedValue( | |
| mockAccounts as any, | |
| ); | |
| const result = await getUserAccounts("user-1"); | |
| expect(result.hasPassword).toBe(true); | |
| expect(result.oauthProviders).toEqual(["google", "github"]); | |
| }); | |
| it("should handle OAuth-only accounts", async () => { | |
| const mockAccounts = [ | |
| { providerId: "google", id: "1" }, | |
| { providerId: "github", id: "2" }, | |
| ]; | |
| vi.mocked(auth.api.listUserAccounts).mockResolvedValue( | |
| mockAccounts as any, | |
| ); | |
| const result = await getUserAccounts("user-1"); | |
| expect(result.hasPassword).toBe(false); | |
| expect(result.oauthProviders).toEqual(["google", "github"]); | |
| }); | |
| it("should handle password-only accounts", async () => { | |
| const mockAccounts = [{ providerId: "credential", id: "1" }]; | |
| vi.mocked(auth.api.listUserAccounts).mockResolvedValue( | |
| mockAccounts as any, | |
| ); | |
| const result = await getUserAccounts("user-1"); | |
| expect(result.hasPassword).toBe(true); | |
| expect(result.oauthProviders).toEqual([]); | |
| }); | |
| it("should filter out credential provider from OAuth list", async () => { | |
| const mockAccounts = [ | |
| { providerId: "credential", id: "1" }, | |
| { providerId: "credential", id: "2" }, // multiple credential accounts | |
| { providerId: "google", id: "3" }, | |
| ]; | |
| vi.mocked(auth.api.listUserAccounts).mockResolvedValue( | |
| mockAccounts as any, | |
| ); | |
| const result = await getUserAccounts("user-1"); | |
| expect(result.hasPassword).toBe(true); | |
| expect(result.oauthProviders).toEqual(["google"]); // credential filtered out | |
| }); | |
| }); | |
| describe("getUserIdAndCheckAccess - Access Control Logic", () => { | |
| it("should use requested user ID when provided", async () => { | |
| vi.mocked(getSession).mockResolvedValue({ | |
| user: { id: "current-user" }, | |
| } as any); | |
| const result = await getUserIdAndCheckAccess("target-user"); | |
| expect(result).toBe("target-user"); | |
| }); | |
| it("should fall back to current user ID when none provided", async () => { | |
| vi.mocked(getSession).mockResolvedValue({ | |
| user: { id: "current-user" }, | |
| } as any); | |
| const result = await getUserIdAndCheckAccess(); | |
| expect(result).toBe("current-user"); | |
| }); | |
| it("should call notFound for falsy user IDs", async () => { | |
| vi.mocked(getSession).mockResolvedValue({ user: { id: "" } } as any); | |
| await getUserIdAndCheckAccess(); | |
| expect(notFound).toHaveBeenCalled(); | |
| }); | |
| it("should handle null/undefined gracefully", async () => { | |
| vi.mocked(getSession).mockResolvedValue({ | |
| user: { id: "fallback-user" }, | |
| } as any); | |
| const result1 = await getUserIdAndCheckAccess(null as any); | |
| const result2 = await getUserIdAndCheckAccess(undefined); | |
| expect(result1).toBe("fallback-user"); | |
| expect(result2).toBe("fallback-user"); | |
| }); | |
| }); | |
| describe("updateUserDetails - User Update Logic", () => { | |
| let userRepository: any; | |
| beforeAll(async () => { | |
| const userRepositoryModule = await import("lib/db/repository"); | |
| userRepository = userRepositoryModule.userRepository; | |
| }); | |
| beforeEach(() => { | |
| vi.mocked(getSession).mockResolvedValue({ | |
| user: { id: "current-user" }, | |
| } as any); | |
| }); | |
| it("should update user with provided fields", async () => { | |
| vi.mocked(userRepository.updateUserDetails).mockResolvedValue(undefined); | |
| await updateUserDetails( | |
| "user-1", | |
| "New Name", | |
| "new@email.com", | |
| "new-image.jpg", | |
| ); | |
| expect(userRepository.updateUserDetails).toHaveBeenCalledWith({ | |
| userId: "user-1", | |
| name: "New Name", | |
| email: "new@email.com", | |
| image: "new-image.jpg", | |
| }); | |
| }); | |
| it("should update only name when provided", async () => { | |
| vi.mocked(userRepository.updateUserDetails).mockResolvedValue(undefined); | |
| await updateUserDetails("user-1", "New Name"); | |
| expect(userRepository.updateUserDetails).toHaveBeenCalledWith({ | |
| userId: "user-1", | |
| name: "New Name", | |
| }); | |
| }); | |
| it("should update only email when provided", async () => { | |
| vi.mocked(userRepository.updateUserDetails).mockResolvedValue(undefined); | |
| await updateUserDetails("user-1", undefined, "new@email.com"); | |
| expect(userRepository.updateUserDetails).toHaveBeenCalledWith({ | |
| userId: "user-1", | |
| email: "new@email.com", | |
| }); | |
| }); | |
| it("should update only image when provided", async () => { | |
| vi.mocked(userRepository.updateUserDetails).mockResolvedValue(undefined); | |
| await updateUserDetails("user-1", undefined, undefined, "new-image.jpg"); | |
| expect(userRepository.updateUserDetails).toHaveBeenCalledWith({ | |
| userId: "user-1", | |
| image: "new-image.jpg", | |
| }); | |
| }); | |
| it("should return early when no fields provided", async () => { | |
| await updateUserDetails("user-1"); | |
| expect(userRepository.updateUserDetails).not.toHaveBeenCalled(); | |
| }); | |
| it("should handle empty string values as falsy", async () => { | |
| await updateUserDetails("user-1", "", "", ""); | |
| expect(userRepository.updateUserDetails).not.toHaveBeenCalled(); | |
| }); | |
| it("should use resolved user ID from access check", async () => { | |
| vi.mocked(userRepository.updateUserDetails).mockResolvedValue(undefined); | |
| await updateUserDetails("user-1", "New Name"); | |
| expect(userRepository.updateUserDetails).toHaveBeenCalledWith({ | |
| userId: "user-1", | |
| name: "New Name", | |
| }); | |
| }); | |
| }); | |
| }); | |