[Closes #8] Added authentication
Some checks failed
Production Build and Deploy / Build (push) Failing after 44s
Production Build and Deploy / Deploy (push) Has been skipped

This commit is contained in:
Liviu Burcusel 2026-01-07 11:11:35 +01:00
parent 6eefa137bb
commit 97211cdccd
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
65 changed files with 5831 additions and 440 deletions

View file

@ -0,0 +1,366 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import type { H3Event } from "h3";
// Mock the auth utility
const mocks = vi.hoisted(() => ({
authHandler: vi.fn(),
defineEventHandler: vi.fn((handler) => handler),
toWebRequest: vi.fn((event: H3Event) => {
// Create a mock Request object
const url = event.node.req.url || "/";
const method = event.node.req.method || "GET";
return new Request(`http://localhost${url}`, {
method,
headers: event.node.req.headers as HeadersInit,
});
}),
}));
vi.mock("~~/shared/utils/auth", () => ({
auth: {
handler: mocks.authHandler,
},
}));
// Mock H3 utilities
vi.mock("h3", async () => {
const actual = await vi.importActual<typeof import("h3")>("h3");
return {
...actual,
defineEventHandler: mocks.defineEventHandler,
toWebRequest: mocks.toWebRequest,
};
});
describe("Auth API Handler", () => {
let handler: any;
beforeEach(async () => {
vi.clearAllMocks();
// Set up global functions for Nuxt auto-imports
(globalThis as any).defineEventHandler = mocks.defineEventHandler;
(globalThis as any).toWebRequest = mocks.toWebRequest;
// Dynamically import the handler after mocks are set up
const module = await import("../../../server/api/[...auth]");
handler = module.default;
});
it("should be defined", () => {
expect(handler).toBeDefined();
expect(typeof handler).toBe("function");
});
it("should call auth.handler with converted web request", async () => {
// Mock H3Event
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-in",
headers: {
"content-type": "application/json",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
// Mock the response from auth.handler
const mockResponse = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
// Call the handler
const result = await handler(mockEvent);
// Verify auth.handler was called
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
// Verify the result
expect(result).toBe(mockResponse);
});
it("should handle GET requests", async () => {
const mockEvent = {
node: {
req: {
method: "GET",
url: "/api/auth/session",
headers: {},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ user: null }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
});
it("should handle POST requests for sign-in", async () => {
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-in/email",
headers: {
"content-type": "application/json",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(
JSON.stringify({
user: {
id: "123",
email: "test@example.com",
name: "Test User",
},
session: { token: "abc123" },
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
});
it("should handle POST requests for sign-up", async () => {
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-up/email",
headers: {
"content-type": "application/json",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(
JSON.stringify({
user: {
id: "456",
email: "newuser@example.com",
name: "New User",
},
session: { token: "xyz789" },
}),
{
status: 201,
headers: { "content-type": "application/json" },
}
);
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
});
it("should handle POST requests for sign-out", async () => {
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-out",
headers: {
"content-type": "application/json",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
});
it("should handle error responses from auth.handler", async () => {
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-in/email",
headers: {
"content-type": "application/json",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockErrorResponse = new Response(
JSON.stringify({
error: "Invalid credentials",
}),
{
status: 401,
headers: { "content-type": "application/json" },
}
);
mocks.authHandler.mockResolvedValue(mockErrorResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockErrorResponse);
});
it("should handle different HTTP methods", async () => {
const methods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
for (const method of methods) {
vi.clearAllMocks();
const mockEvent = {
node: {
req: {
method,
url: "/api/auth/test",
headers: {},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ method }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
}
});
it("should convert H3Event to Web Request correctly", async () => {
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/test",
headers: {
"content-type": "application/json",
authorization: "Bearer token123",
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
await handler(mockEvent);
// Verify that auth.handler was called with a Request object
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
const callArg = mocks.authHandler.mock.calls[0][0];
// The argument should be a Request object (from toWebRequest conversion)
expect(callArg).toBeDefined();
});
it("should handle requests with query parameters", async () => {
const mockEvent = {
node: {
req: {
method: "GET",
url: "/api/auth/session?redirect=/dashboard",
headers: {},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ user: null }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
});
it("should handle requests with different content types", async () => {
const contentTypes = ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"];
for (const contentType of contentTypes) {
vi.clearAllMocks();
const mockEvent = {
node: {
req: {
method: "POST",
url: "/api/auth/sign-in",
headers: {
"content-type": contentType,
},
},
res: {},
},
context: {},
} as unknown as H3Event;
const mockResponse = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
mocks.authHandler.mockResolvedValue(mockResponse);
const result = await handler(mockEvent);
expect(mocks.authHandler).toHaveBeenCalledTimes(1);
expect(result).toBe(mockResponse);
}
});
});