nextjs-hackathon-stack 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/index.js +7 -0
  2. package/package.json +1 -1
  3. package/template/.husky/pre-commit +1 -0
  4. package/template/_env.example +7 -5
  5. package/template/package.json.tmpl +8 -2
  6. package/template/src/app/(protected)/page.tsx +9 -1
  7. package/template/src/app/__tests__/auth-callback.test.ts +72 -0
  8. package/template/src/app/__tests__/error.test.tsx +71 -0
  9. package/template/src/app/__tests__/layout.test.tsx +22 -0
  10. package/template/src/app/__tests__/login-page.test.tsx +34 -0
  11. package/template/src/app/__tests__/protected-layout.test.tsx +43 -0
  12. package/template/src/app/__tests__/protected-page.test.tsx +41 -0
  13. package/template/src/app/api/chat/route.ts +3 -1
  14. package/template/src/features/auth/__tests__/login-form.test.tsx +53 -2
  15. package/template/src/features/auth/__tests__/login.action.test.ts +82 -0
  16. package/template/src/features/auth/__tests__/logout.action.test.ts +32 -0
  17. package/template/src/features/auth/actions/login.action.ts +1 -1
  18. package/template/src/features/auth/components/login-form.tsx +4 -3
  19. package/template/src/features/chat/__tests__/chat-ui.test.tsx +97 -7
  20. package/template/src/features/chat/__tests__/route.test.ts +66 -3
  21. package/template/src/features/chat/__tests__/use-chat.test.ts +15 -0
  22. package/template/src/features/chat/components/chat-ui.tsx +1 -1
  23. package/template/src/features/tts/__tests__/route.test.ts +78 -3
  24. package/template/src/features/tts/__tests__/tts-player.test.tsx +65 -3
  25. package/template/src/features/video/__tests__/route.test.ts +78 -3
  26. package/template/src/features/video/__tests__/video-generator.test.tsx +65 -3
  27. package/template/src/shared/__tests__/middleware.test.ts +34 -0
  28. package/template/src/shared/__tests__/schema.test.ts +16 -3
  29. package/template/src/shared/__tests__/supabase-middleware.test.ts +162 -0
  30. package/template/src/shared/__tests__/supabase-server.test.ts +76 -3
  31. package/template/src/shared/__tests__/ui-button.test.tsx +52 -0
  32. package/template/src/shared/__tests__/ui-card.test.tsx +78 -0
  33. package/template/src/shared/__tests__/utils.test.ts +30 -0
  34. package/template/src/shared/lib/ai.ts +4 -4
  35. package/template/vitest.config.ts +5 -0
@@ -3,29 +3,119 @@ import { describe, it, expect, vi } from "vitest";
3
3
 
4
4
  import { ChatUi } from "../components/chat-ui";
5
5
 
6
+ const mockUseChat = vi.fn(() => ({
7
+ messages: [] as { id: string; role: string; content: string }[],
8
+ input: "",
9
+ handleInputChange: vi.fn(),
10
+ handleSubmit: vi.fn(),
11
+ status: "idle",
12
+ }));
13
+
6
14
  vi.mock("@ai-sdk/react", () => ({
7
- useChat: vi.fn(() => ({
8
- messages: [],
9
- input: "",
10
- handleInputChange: vi.fn(),
11
- handleSubmit: vi.fn(),
12
- isLoading: false,
13
- })),
15
+ useChat: (...args: unknown[]) => mockUseChat(...args),
14
16
  }));
15
17
 
16
18
  describe("ChatUi", () => {
17
19
  it("renders chat input", () => {
20
+ // Arrange + Act
18
21
  render(<ChatUi />);
22
+
23
+ // Assert
19
24
  expect(screen.getByTestId("chat-input")).toBeInTheDocument();
20
25
  });
21
26
 
22
27
  it("renders send button", () => {
28
+ // Arrange + Act
23
29
  render(<ChatUi />);
30
+
31
+ // Assert
24
32
  expect(screen.getByRole("button", { name: /send/i })).toBeInTheDocument();
25
33
  });
26
34
 
27
35
  it("renders messages list area", () => {
36
+ // Arrange + Act
28
37
  render(<ChatUi />);
38
+
39
+ // Assert
29
40
  expect(screen.getByTestId("chat-ui")).toBeInTheDocument();
30
41
  });
42
+
43
+ it("renders user and assistant messages", () => {
44
+ // Arrange
45
+ mockUseChat.mockReturnValueOnce({
46
+ messages: [
47
+ { id: "1", role: "user", content: "Hello" },
48
+ { id: "2", role: "assistant", content: "Hi there!" },
49
+ ],
50
+ input: "",
51
+ handleInputChange: vi.fn(),
52
+ handleSubmit: vi.fn(),
53
+ status: "idle",
54
+ });
55
+
56
+ // Act
57
+ render(<ChatUi />);
58
+
59
+ // Assert
60
+ expect(screen.getByText("Hello")).toBeInTheDocument();
61
+ expect(screen.getByText("Hi there!")).toBeInTheDocument();
62
+ });
63
+
64
+ it("shows loading indicator when status is streaming", () => {
65
+ // Arrange
66
+ mockUseChat.mockReturnValueOnce({
67
+ messages: [],
68
+ input: "",
69
+ handleInputChange: vi.fn(),
70
+ handleSubmit: vi.fn(),
71
+ status: "streaming",
72
+ });
73
+
74
+ // Act
75
+ render(<ChatUi />);
76
+
77
+ // Assert
78
+ expect(screen.getByText(/thinking/i)).toBeInTheDocument();
79
+ });
80
+
81
+ it("shows loading indicator when status is submitted", () => {
82
+ // Arrange
83
+ mockUseChat.mockReturnValueOnce({
84
+ messages: [],
85
+ input: "",
86
+ handleInputChange: vi.fn(),
87
+ handleSubmit: vi.fn(),
88
+ status: "submitted",
89
+ });
90
+
91
+ // Act
92
+ render(<ChatUi />);
93
+
94
+ // Assert
95
+ expect(screen.getByText(/thinking/i)).toBeInTheDocument();
96
+ });
97
+
98
+ it("strips <think> tags from message content", () => {
99
+ // Arrange
100
+ mockUseChat.mockReturnValueOnce({
101
+ messages: [
102
+ {
103
+ id: "1",
104
+ role: "assistant",
105
+ content: "<think>internal reasoning</think>Visible answer",
106
+ },
107
+ ],
108
+ input: "",
109
+ handleInputChange: vi.fn(),
110
+ handleSubmit: vi.fn(),
111
+ status: "idle",
112
+ });
113
+
114
+ // Act
115
+ render(<ChatUi />);
116
+
117
+ // Assert
118
+ expect(screen.getByText("Visible answer")).toBeInTheDocument();
119
+ expect(screen.queryByText(/internal reasoning/)).not.toBeInTheDocument();
120
+ });
31
121
  });
@@ -1,8 +1,13 @@
1
1
  import { describe, it, expect, vi } from "vitest";
2
2
 
3
+ let capturedGetErrorMessage: ((error: unknown) => string) | undefined;
4
+
3
5
  vi.mock("ai", () => ({
4
6
  streamText: vi.fn(() => ({
5
- toDataStreamResponse: vi.fn(() => new Response("data: done\n\n")),
7
+ toDataStreamResponse: vi.fn((opts?: { getErrorMessage?: (e: unknown) => string }) => {
8
+ capturedGetErrorMessage = opts?.getErrorMessage;
9
+ return new Response("data: done\n\n");
10
+ }),
6
11
  })),
7
12
  }));
8
13
 
@@ -11,9 +16,67 @@ vi.mock("@/shared/lib/ai", () => ({
11
16
  }));
12
17
 
13
18
  describe("chat route", () => {
14
- it("module is importable", async () => {
15
- const mod = await import("../api/route");
19
+ it("exports runtime as edge and POST handler", async () => {
20
+ // Arrange + Act
21
+ const mod = await import("../../../app/api/chat/route");
22
+
23
+ // Assert
16
24
  expect(mod.runtime).toBe("edge");
17
25
  expect(typeof mod.POST).toBe("function");
18
26
  });
27
+
28
+ it("calls streamText and returns a response", async () => {
29
+ // Arrange
30
+ const mod = await import("../../../app/api/chat/route");
31
+ const req = new Request("http://localhost/api/chat", {
32
+ method: "POST",
33
+ body: JSON.stringify({ messages: [{ role: "user", content: "hello" }] }),
34
+ });
35
+
36
+ // Act
37
+ const response = await mod.POST(req);
38
+
39
+ // Assert
40
+ expect(response).toBeInstanceOf(Response);
41
+ });
42
+
43
+ it("getErrorMessage returns error.message for Error instances", async () => {
44
+ // Arrange
45
+ const mod = await import("../../../app/api/chat/route");
46
+ const req = new Request("http://localhost/api/chat", {
47
+ method: "POST",
48
+ body: JSON.stringify({ messages: [] }),
49
+ });
50
+ await mod.POST(req);
51
+
52
+ // Act + Assert
53
+ expect(capturedGetErrorMessage).toBeDefined();
54
+ expect(capturedGetErrorMessage!(new Error("boom"))).toBe("boom");
55
+ });
56
+
57
+ it("getErrorMessage returns JSON for plain objects", async () => {
58
+ // Arrange
59
+ const mod = await import("../../../app/api/chat/route");
60
+ const req = new Request("http://localhost/api/chat", {
61
+ method: "POST",
62
+ body: JSON.stringify({ messages: [] }),
63
+ });
64
+ await mod.POST(req);
65
+
66
+ // Act + Assert
67
+ expect(capturedGetErrorMessage!({ code: 42 })).toBe('{"code":42}');
68
+ });
69
+
70
+ it("getErrorMessage returns String() for primitives", async () => {
71
+ // Arrange
72
+ const mod = await import("../../../app/api/chat/route");
73
+ const req = new Request("http://localhost/api/chat", {
74
+ method: "POST",
75
+ body: JSON.stringify({ messages: [] }),
76
+ });
77
+ await mod.POST(req);
78
+
79
+ // Act + Assert
80
+ expect(capturedGetErrorMessage!("plain string")).toBe("plain string");
81
+ });
19
82
  });
@@ -0,0 +1,15 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+
3
+ vi.mock("@ai-sdk/react", () => ({
4
+ useChat: vi.fn(() => ({ messages: [], input: "" })),
5
+ }));
6
+
7
+ describe("use-chat re-export", () => {
8
+ it("re-exports useChat from @ai-sdk/react", async () => {
9
+ // Arrange + Act
10
+ const mod = await import("../hooks/use-chat");
11
+
12
+ // Assert
13
+ expect(typeof mod.useChat).toBe("function");
14
+ });
15
+ });
@@ -18,7 +18,7 @@ export function ChatUi() {
18
18
  message.role === "user" ? "bg-primary/10 ml-8" : "bg-muted mr-8"
19
19
  }`}
20
20
  >
21
- <p className="text-sm">{message.content}</p>
21
+ <p className="text-sm">{message.content.replace(/<think>[\s\S]*?<\/think>\s*/g, "")}</p>
22
22
  </div>
23
23
  ))}
24
24
  {isLoading && (
@@ -1,13 +1,88 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ const mockSynthesizeSpeech = vi.fn();
2
4
 
3
5
  vi.mock("@/shared/lib/minimax-media", () => ({
4
- synthesizeSpeech: vi.fn().mockResolvedValue("https://example.com/audio.mp3"),
6
+ synthesizeSpeech: (...args: unknown[]) => mockSynthesizeSpeech(...args),
5
7
  }));
6
8
 
7
9
  describe("tts route", () => {
8
- it("module is importable", async () => {
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ });
13
+
14
+ it("exports runtime as edge and POST handler", async () => {
15
+ // Arrange + Act
9
16
  const mod = await import("../api/route");
17
+
18
+ // Assert
10
19
  expect(mod.runtime).toBe("edge");
11
20
  expect(typeof mod.POST).toBe("function");
12
21
  });
22
+
23
+ it("returns 400 when text is missing", async () => {
24
+ // Arrange
25
+ const mod = await import("../api/route");
26
+ const req = new Request("http://localhost/api/tts", {
27
+ method: "POST",
28
+ body: JSON.stringify({}),
29
+ });
30
+
31
+ // Act
32
+ const response = await mod.POST(req);
33
+
34
+ // Assert
35
+ expect(response.status).toBe(400);
36
+ const body = await response.json() as { error: string };
37
+ expect(body.error).toBe("text is required");
38
+ });
39
+
40
+ it("returns 400 when text is not a string", async () => {
41
+ // Arrange
42
+ const mod = await import("../api/route");
43
+ const req = new Request("http://localhost/api/tts", {
44
+ method: "POST",
45
+ body: JSON.stringify({ text: 123 }),
46
+ });
47
+
48
+ // Act
49
+ const response = await mod.POST(req);
50
+
51
+ // Assert
52
+ expect(response.status).toBe(400);
53
+ });
54
+
55
+ it("returns audioFile on success", async () => {
56
+ // Arrange
57
+ mockSynthesizeSpeech.mockResolvedValue("base64audiodata");
58
+ const mod = await import("../api/route");
59
+ const req = new Request("http://localhost/api/tts", {
60
+ method: "POST",
61
+ body: JSON.stringify({ text: "Hello world" }),
62
+ });
63
+
64
+ // Act
65
+ const response = await mod.POST(req);
66
+
67
+ // Assert
68
+ expect(response.status).toBe(200);
69
+ const body = await response.json() as { audioFile: string };
70
+ expect(body.audioFile).toBe("base64audiodata");
71
+ });
72
+
73
+ it("passes voiceId to synthesizeSpeech when provided", async () => {
74
+ // Arrange
75
+ mockSynthesizeSpeech.mockResolvedValue("audio");
76
+ const mod = await import("../api/route");
77
+ const req = new Request("http://localhost/api/tts", {
78
+ method: "POST",
79
+ body: JSON.stringify({ text: "Hello", voiceId: "custom-voice" }),
80
+ });
81
+
82
+ // Act
83
+ await mod.POST(req);
84
+
85
+ // Assert
86
+ expect(mockSynthesizeSpeech).toHaveBeenCalledWith("Hello", "custom-voice");
87
+ });
13
88
  });
@@ -1,21 +1,83 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
1
+ import { render, screen, waitFor } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
4
 
4
5
  import { TtsPlayer } from "../components/tts-player";
5
6
 
6
7
  describe("TtsPlayer", () => {
8
+ beforeEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+
7
12
  it("renders text input", () => {
13
+ // Arrange + Act
8
14
  render(<TtsPlayer />);
15
+
16
+ // Assert
9
17
  expect(screen.getByPlaceholderText(/enter text to speak/i)).toBeInTheDocument();
10
18
  });
11
19
 
12
20
  it("renders speak button", () => {
21
+ // Arrange + Act
13
22
  render(<TtsPlayer />);
23
+
24
+ // Assert
14
25
  expect(screen.getByRole("button", { name: /speak/i })).toBeInTheDocument();
15
26
  });
16
27
 
17
- it("button disabled when input empty", () => {
28
+ it("button is disabled when input is empty", () => {
29
+ // Arrange + Act
18
30
  render(<TtsPlayer />);
31
+
32
+ // Assert
19
33
  expect(screen.getByRole("button", { name: /speak/i })).toBeDisabled();
20
34
  });
35
+
36
+ it("button is enabled when text has content", async () => {
37
+ // Arrange
38
+ const user = userEvent.setup();
39
+ render(<TtsPlayer />);
40
+
41
+ // Act
42
+ await user.type(screen.getByPlaceholderText(/enter text to speak/i), "Hello world");
43
+
44
+ // Assert
45
+ expect(screen.getByRole("button", { name: /speak/i })).not.toBeDisabled();
46
+ });
47
+
48
+ it("shows audio player after successful synthesis", async () => {
49
+ // Arrange
50
+ vi.spyOn(global, "fetch").mockResolvedValue(
51
+ new Response(JSON.stringify({ audioFile: "data:audio/mp3;base64,abc" }), { status: 200 })
52
+ );
53
+ const user = userEvent.setup();
54
+ render(<TtsPlayer />);
55
+
56
+ // Act
57
+ await user.type(screen.getByPlaceholderText(/enter text to speak/i), "Say something");
58
+ await user.click(screen.getByRole("button", { name: /speak/i }));
59
+
60
+ // Assert
61
+ await waitFor(() => {
62
+ expect(screen.getByTestId("tts-player").querySelector("audio")).toBeInTheDocument();
63
+ });
64
+ });
65
+
66
+ it("shows error message when synthesis fails", async () => {
67
+ // Arrange
68
+ vi.spyOn(global, "fetch").mockResolvedValue(
69
+ new Response(null, { status: 500 })
70
+ );
71
+ const user = userEvent.setup();
72
+ render(<TtsPlayer />);
73
+
74
+ // Act
75
+ await user.type(screen.getByPlaceholderText(/enter text to speak/i), "Say something");
76
+ await user.click(screen.getByRole("button", { name: /speak/i }));
77
+
78
+ // Assert
79
+ await waitFor(() => {
80
+ expect(screen.getByText(/failed to synthesize speech/i)).toBeInTheDocument();
81
+ });
82
+ });
21
83
  });
@@ -1,13 +1,88 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ const mockGenerateVideo = vi.fn();
2
4
 
3
5
  vi.mock("@/shared/lib/minimax-media", () => ({
4
- generateVideo: vi.fn().mockResolvedValue("task-abc"),
6
+ generateVideo: (...args: unknown[]) => mockGenerateVideo(...args),
5
7
  }));
6
8
 
7
9
  describe("video route", () => {
8
- it("module is importable", async () => {
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ });
13
+
14
+ it("exports runtime as edge and POST handler", async () => {
15
+ // Arrange + Act
9
16
  const mod = await import("../api/route");
17
+
18
+ // Assert
10
19
  expect(mod.runtime).toBe("edge");
11
20
  expect(typeof mod.POST).toBe("function");
12
21
  });
22
+
23
+ it("returns 400 when prompt is missing", async () => {
24
+ // Arrange
25
+ const mod = await import("../api/route");
26
+ const req = new Request("http://localhost/api/video", {
27
+ method: "POST",
28
+ body: JSON.stringify({}),
29
+ });
30
+
31
+ // Act
32
+ const response = await mod.POST(req);
33
+
34
+ // Assert
35
+ expect(response.status).toBe(400);
36
+ const body = await response.json() as { error: string };
37
+ expect(body.error).toBe("prompt is required");
38
+ });
39
+
40
+ it("returns 400 when prompt is not a string", async () => {
41
+ // Arrange
42
+ const mod = await import("../api/route");
43
+ const req = new Request("http://localhost/api/video", {
44
+ method: "POST",
45
+ body: JSON.stringify({ prompt: 42 }),
46
+ });
47
+
48
+ // Act
49
+ const response = await mod.POST(req);
50
+
51
+ // Assert
52
+ expect(response.status).toBe(400);
53
+ });
54
+
55
+ it("returns taskId on success", async () => {
56
+ // Arrange
57
+ mockGenerateVideo.mockResolvedValue("task-123");
58
+ const mod = await import("../api/route");
59
+ const req = new Request("http://localhost/api/video", {
60
+ method: "POST",
61
+ body: JSON.stringify({ prompt: "A sunset over the ocean" }),
62
+ });
63
+
64
+ // Act
65
+ const response = await mod.POST(req);
66
+
67
+ // Assert
68
+ expect(response.status).toBe(200);
69
+ const body = await response.json() as { taskId: string };
70
+ expect(body.taskId).toBe("task-123");
71
+ });
72
+
73
+ it("passes prompt to generateVideo", async () => {
74
+ // Arrange
75
+ mockGenerateVideo.mockResolvedValue("task-abc");
76
+ const mod = await import("../api/route");
77
+ const req = new Request("http://localhost/api/video", {
78
+ method: "POST",
79
+ body: JSON.stringify({ prompt: "Dancing robots" }),
80
+ });
81
+
82
+ // Act
83
+ await mod.POST(req);
84
+
85
+ // Assert
86
+ expect(mockGenerateVideo).toHaveBeenCalledWith("Dancing robots");
87
+ });
13
88
  });
@@ -1,21 +1,83 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
1
+ import { render, screen, waitFor } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
4
 
4
5
  import { VideoGenerator } from "../components/video-generator";
5
6
 
6
7
  describe("VideoGenerator", () => {
8
+ beforeEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+
7
12
  it("renders prompt input", () => {
13
+ // Arrange + Act
8
14
  render(<VideoGenerator />);
15
+
16
+ // Assert
9
17
  expect(screen.getByPlaceholderText(/describe a video/i)).toBeInTheDocument();
10
18
  });
11
19
 
12
20
  it("renders generate button", () => {
21
+ // Arrange + Act
13
22
  render(<VideoGenerator />);
23
+
24
+ // Assert
14
25
  expect(screen.getByRole("button", { name: /generate/i })).toBeInTheDocument();
15
26
  });
16
27
 
17
- it("button disabled when input empty", () => {
28
+ it("button is disabled when input is empty", () => {
29
+ // Arrange + Act
18
30
  render(<VideoGenerator />);
31
+
32
+ // Assert
19
33
  expect(screen.getByRole("button", { name: /generate/i })).toBeDisabled();
20
34
  });
35
+
36
+ it("button is enabled when prompt has content", async () => {
37
+ // Arrange
38
+ const user = userEvent.setup();
39
+ render(<VideoGenerator />);
40
+
41
+ // Act
42
+ await user.type(screen.getByPlaceholderText(/describe a video/i), "A sunset");
43
+
44
+ // Assert
45
+ expect(screen.getByRole("button", { name: /generate/i })).not.toBeDisabled();
46
+ });
47
+
48
+ it("shows task ID after successful generation", async () => {
49
+ // Arrange
50
+ vi.spyOn(global, "fetch").mockResolvedValue(
51
+ new Response(JSON.stringify({ taskId: "task-xyz" }), { status: 200 })
52
+ );
53
+ const user = userEvent.setup();
54
+ render(<VideoGenerator />);
55
+
56
+ // Act
57
+ await user.type(screen.getByPlaceholderText(/describe a video/i), "A dancing robot");
58
+ await user.click(screen.getByRole("button", { name: /generate/i }));
59
+
60
+ // Assert
61
+ await waitFor(() => {
62
+ expect(screen.getByText(/task id: task-xyz/i)).toBeInTheDocument();
63
+ });
64
+ });
65
+
66
+ it("shows error message when fetch fails", async () => {
67
+ // Arrange
68
+ vi.spyOn(global, "fetch").mockResolvedValue(
69
+ new Response(null, { status: 500 })
70
+ );
71
+ const user = userEvent.setup();
72
+ render(<VideoGenerator />);
73
+
74
+ // Act
75
+ await user.type(screen.getByPlaceholderText(/describe a video/i), "Something");
76
+ await user.click(screen.getByRole("button", { name: /generate/i }));
77
+
78
+ // Assert
79
+ await waitFor(() => {
80
+ expect(screen.getByText(/failed to generate video/i)).toBeInTheDocument();
81
+ });
82
+ });
21
83
  });
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { NextRequest } from "next/server";
3
+
4
+ const mockUpdateSession = vi.fn();
5
+
6
+ vi.mock("@/shared/lib/supabase/middleware", () => ({
7
+ updateSession: (...args: unknown[]) => mockUpdateSession(...args),
8
+ }));
9
+
10
+ describe("middleware", () => {
11
+ it("delegates to updateSession", async () => {
12
+ // Arrange
13
+ const mockResponse = new Response(null, { status: 200 });
14
+ mockUpdateSession.mockResolvedValue(mockResponse);
15
+ const { middleware } = await import("../../middleware");
16
+ const req = new NextRequest("http://localhost/dashboard");
17
+
18
+ // Act
19
+ const response = await middleware(req);
20
+
21
+ // Assert
22
+ expect(mockUpdateSession).toHaveBeenCalledWith(req);
23
+ expect(response).toBe(mockResponse);
24
+ });
25
+
26
+ it("exports a matcher config", async () => {
27
+ // Arrange + Act
28
+ const { config } = await import("../../middleware");
29
+
30
+ // Assert
31
+ expect(config.matcher).toBeDefined();
32
+ expect(Array.isArray(config.matcher)).toBe(true);
33
+ });
34
+ });
@@ -4,16 +4,29 @@ import { insertProfileSchema, selectProfileSchema } from "../db/schema";
4
4
 
5
5
  describe("profiles schema", () => {
6
6
  it("insertProfileSchema validates required fields", () => {
7
- const result = insertProfileSchema.safeParse({ id: "uuid", email: "test@example.com" });
7
+ // Arrange + Act
8
+ const result = insertProfileSchema.safeParse({
9
+ id: "123e4567-e89b-12d3-a456-426614174000",
10
+ email: "test@example.com",
11
+ });
12
+
13
+ // Assert
8
14
  expect(result.success).toBe(true);
9
15
  });
10
16
 
11
- it("insertProfileSchema rejects invalid email", () => {
12
- const result = insertProfileSchema.safeParse({ id: "uuid", email: "not-email" });
17
+ it("insertProfileSchema rejects invalid uuid", () => {
18
+ // Arrange + Act
19
+ const result = insertProfileSchema.safeParse({
20
+ id: "not-a-uuid",
21
+ email: "test@example.com",
22
+ });
23
+
24
+ // Assert
13
25
  expect(result.success).toBe(false);
14
26
  });
15
27
 
16
28
  it("selectProfileSchema is defined", () => {
29
+ // Arrange + Act + Assert
17
30
  expect(selectProfileSchema).toBeDefined();
18
31
  });
19
32
  });