Skip to content

Commit 304c408

Browse files
chore: add Jest test setup
1 parent dd01f8b commit 304c408

5 files changed

Lines changed: 7277 additions & 2530 deletions

File tree

__tests__/lib/auth.test.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**
2+
* Unit tests for lib/auth.ts
3+
* Tests input validation and error handling without hitting Supabase.
4+
*/
5+
6+
// Mock the Supabase client before importing auth functions
7+
jest.mock("@/lib/supabase", () => ({
8+
supabase: {
9+
auth: {
10+
signUp: jest.fn(),
11+
signInWithPassword: jest.fn(),
12+
getUser: jest.fn(),
13+
getSession: jest.fn(),
14+
signOut: jest.fn(),
15+
onAuthStateChange: jest.fn(() => ({
16+
data: { subscription: { unsubscribe: jest.fn() } },
17+
})),
18+
},
19+
from: jest.fn(() => ({
20+
select: jest.fn().mockReturnThis(),
21+
eq: jest.fn().mockReturnThis(),
22+
single: jest.fn().mockResolvedValue({ data: null, error: { message: "not found" } }),
23+
})),
24+
},
25+
}));
26+
27+
import { registerUser, authenticateUser } from "@/lib/auth";
28+
import { supabase } from "@/lib/supabase";
29+
30+
const mockAuth = supabase.auth as jest.Mocked<typeof supabase.auth>;
31+
32+
// ─── registerUser ────────────────────────────────────────────────────────────
33+
34+
describe("registerUser – input validation", () => {
35+
it("rejects empty email and password", async () => {
36+
const result = await registerUser("", "");
37+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
38+
});
39+
40+
it("rejects missing email", async () => {
41+
const result = await registerUser("", "validpassword");
42+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
43+
});
44+
45+
it("rejects missing password", async () => {
46+
const result = await registerUser("user@example.com", "");
47+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
48+
});
49+
50+
it("rejects password shorter than 6 characters", async () => {
51+
const result = await registerUser("user@example.com", "abc");
52+
expect(result).toEqual({ ok: false, error: "Password must be at least 6 characters" });
53+
});
54+
55+
it("accepts password of exactly 6 characters", async () => {
56+
(mockAuth.signUp as jest.Mock).mockResolvedValueOnce({
57+
data: { user: { id: "123", identities: [{}] }, session: { access_token: "tok" } },
58+
error: null,
59+
});
60+
const result = await registerUser("user@example.com", "abc123");
61+
expect(result.ok).toBe(true);
62+
});
63+
});
64+
65+
describe("registerUser – Supabase error handling", () => {
66+
it("returns error message from Supabase on failure", async () => {
67+
(mockAuth.signUp as jest.Mock).mockResolvedValueOnce({
68+
data: { user: null, session: null },
69+
error: { message: "User already registered" },
70+
});
71+
const result = await registerUser("user@example.com", "password123");
72+
expect(result.ok).toBe(false);
73+
if (!result.ok) {
74+
expect(result.error).toContain("already registered");
75+
}
76+
});
77+
78+
it("flags needsEmailConfirmation when no session returned", async () => {
79+
(mockAuth.signUp as jest.Mock).mockResolvedValueOnce({
80+
data: { user: { id: "abc", identities: [{}] }, session: null },
81+
error: null,
82+
});
83+
const result = await registerUser("user@example.com", "password123");
84+
expect(result).toMatchObject({ ok: true, needsEmailConfirmation: true });
85+
});
86+
87+
it("returns network error message on thrown exception", async () => {
88+
(mockAuth.signUp as jest.Mock).mockRejectedValueOnce(new Error("fetch failed"));
89+
const result = await registerUser("user@example.com", "password123");
90+
expect(result.ok).toBe(false);
91+
if (!result.ok) expect(result.error).toMatch(/fetch failed/i);
92+
});
93+
});
94+
95+
// ─── authenticateUser ────────────────────────────────────────────────────────
96+
97+
describe("authenticateUser – input validation", () => {
98+
it("rejects empty email and password", async () => {
99+
const result = await authenticateUser("", "");
100+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
101+
});
102+
103+
it("rejects missing email", async () => {
104+
const result = await authenticateUser("", "password123");
105+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
106+
});
107+
108+
it("rejects missing password", async () => {
109+
const result = await authenticateUser("user@example.com", "");
110+
expect(result).toEqual({ ok: false, error: "Email and password are required" });
111+
});
112+
});
113+
114+
describe("authenticateUser – Supabase error handling", () => {
115+
it("maps invalid credentials to a friendly message", async () => {
116+
(mockAuth.signInWithPassword as jest.Mock).mockResolvedValueOnce({
117+
data: { user: null, session: null },
118+
error: { message: "Invalid login credentials" },
119+
});
120+
const result = await authenticateUser("user@example.com", "wrongpassword");
121+
expect(result).toEqual({ ok: false, error: "Invalid email or password" });
122+
});
123+
124+
it("maps unconfirmed email to a friendly message", async () => {
125+
(mockAuth.signInWithPassword as jest.Mock).mockResolvedValueOnce({
126+
data: { user: null, session: null },
127+
error: { message: "Email not confirmed" },
128+
});
129+
const result = await authenticateUser("user@example.com", "password123");
130+
expect(result).toEqual({ ok: false, error: "Please verify your email first" });
131+
});
132+
133+
it("returns ok:true on successful login", async () => {
134+
(mockAuth.signInWithPassword as jest.Mock).mockResolvedValueOnce({
135+
data: { user: { id: "user-1" }, session: { access_token: "tok" } },
136+
error: null,
137+
});
138+
const result = await authenticateUser("user@example.com", "password123");
139+
expect(result).toEqual({ ok: true });
140+
});
141+
142+
it("returns network error message on thrown exception", async () => {
143+
(mockAuth.signInWithPassword as jest.Mock).mockRejectedValueOnce(
144+
new Error("Network request failed")
145+
);
146+
const result = await authenticateUser("user@example.com", "password123");
147+
expect(result.ok).toBe(false);
148+
if (!result.ok) expect(result.error).toMatch(/network request failed/i);
149+
});
150+
});

jest.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const nextJest = require("next/jest");
2+
3+
const createJestConfig = nextJest({ dir: "./" });
4+
5+
const customJestConfig = {
6+
testMatch: ["**/__tests__/**/*.test.ts", "**/__tests__/**/*.test.tsx"],
7+
testEnvironment: "jest-environment-jsdom",
8+
moduleNameMapper: {
9+
"^@/(.*)$": "<rootDir>/$1",
10+
},
11+
};
12+
13+
module.exports = createJestConfig(customJestConfig);

jest.setup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "@testing-library/jest-dom";

0 commit comments

Comments
 (0)