Skip to content

Commit 596140b

Browse files
Merge pull request #58 from prisma/DC-4635-add-tests
DC-4635 Add Tests
2 parents f3ae481 + 90b0f22 commit 596140b

File tree

14 files changed

+6154
-8244
lines changed

14 files changed

+6154
-8244
lines changed

.github/workflows/tests.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Tests
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pull-requests: read
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup pnpm
20+
uses: pnpm/action-setup@v4
21+
with:
22+
version: 9
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: 20
28+
cache: "pnpm"
29+
30+
- name: Install Dependencies
31+
run: pnpm install --frozen-lockfile
32+
working-directory: ./claim-db-worker
33+
34+
- name: Run claim-db-worker tests
35+
run: pnpm test
36+
working-directory: ./claim-db-worker
37+
env:
38+
NODE_ENV: test
39+
40+
- name: Install create-db dependencies
41+
run: pnpm install --frozen-lockfile
42+
working-directory: ./create-db
43+
44+
- name: Run create-db tests
45+
run: pnpm test
46+
working-directory: ./create-db
47+
env:
48+
NODE_ENV: test
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// __tests__/callback-api.test.ts
2+
import { describe, it, expect, vi, beforeEach } from "vitest";
3+
import { GET } from "../app/api/auth/callback/route";
4+
import { NextRequest } from "next/server";
5+
6+
vi.mock("@/lib/env", () => ({
7+
getEnv: vi.fn(() => ({
8+
CLAIM_DB_RATE_LIMITER: {
9+
limit: vi.fn(() => Promise.resolve({ success: true })),
10+
},
11+
POSTHOG_API_KEY: "test-key",
12+
POSTHOG_API_HOST: "https://app.posthog.com",
13+
})),
14+
}));
15+
16+
vi.mock("@/lib/auth-utils", () => ({
17+
exchangeCodeForToken: vi.fn(),
18+
validateProject: vi.fn(),
19+
}));
20+
21+
vi.mock("@/lib/response-utils", () => ({
22+
redirectToError: vi.fn(),
23+
redirectToSuccess: vi.fn(),
24+
getBaseUrl: vi.fn(() => "http://localhost:3000"),
25+
}));
26+
27+
vi.mock("@/lib/project-transfer", () => ({
28+
transferProject: vi.fn(),
29+
}));
30+
31+
const mockFetch = vi.fn();
32+
global.fetch = mockFetch;
33+
34+
describe("auth callback API", () => {
35+
beforeEach(() => {
36+
vi.clearAllMocks();
37+
});
38+
39+
describe("successful claim flow", () => {
40+
it("completes full OAuth callback and project transfer", async () => {
41+
const { exchangeCodeForToken, validateProject } = await import(
42+
"@/lib/auth-utils"
43+
);
44+
const { redirectToError, redirectToSuccess } = await import(
45+
"@/lib/response-utils"
46+
);
47+
const { transferProject } = await import("@/lib/project-transfer");
48+
49+
vi.mocked(exchangeCodeForToken).mockResolvedValue({
50+
access_token: "test-token",
51+
});
52+
53+
vi.mocked(validateProject).mockResolvedValue(undefined);
54+
55+
vi.mocked(transferProject).mockResolvedValue({
56+
success: true,
57+
status: 200,
58+
});
59+
60+
vi.mocked(redirectToSuccess).mockReturnValue(
61+
new Response(null, {
62+
status: 302,
63+
headers: { Location: "/success?projectID=test-project-123" },
64+
})
65+
);
66+
67+
mockFetch.mockResolvedValue({
68+
ok: true,
69+
json: async () => ({}),
70+
});
71+
72+
const request = new NextRequest(
73+
"http://localhost:3000/api/auth/callback?code=test-code&state=test-state&projectID=test-project-123"
74+
);
75+
76+
const response = await GET(request);
77+
78+
expect(exchangeCodeForToken).toHaveBeenCalledWith(
79+
"test-code",
80+
expect.stringContaining("test-project-123")
81+
);
82+
expect(validateProject).toHaveBeenCalledWith("test-project-123");
83+
expect(transferProject).toHaveBeenCalledWith(
84+
"test-project-123",
85+
"test-token"
86+
);
87+
expect(redirectToSuccess).toHaveBeenCalledWith(
88+
request,
89+
"test-project-123"
90+
);
91+
92+
expect(mockFetch).toHaveBeenCalledWith(
93+
expect.stringContaining("posthog.com"),
94+
expect.objectContaining({
95+
method: "POST",
96+
body: expect.stringContaining("create_db:claim_successful"),
97+
})
98+
);
99+
});
100+
});
101+
102+
describe("error handling", () => {
103+
it("handles missing parameters", async () => {
104+
const { redirectToError } = await import("@/lib/response-utils");
105+
106+
vi.mocked(redirectToError).mockReturnValue(
107+
new Response(null, {
108+
status: 302,
109+
headers: { Location: "/error" },
110+
})
111+
);
112+
113+
const request = new NextRequest(
114+
"http://localhost:3000/api/auth/callback?code=test-code"
115+
);
116+
117+
await GET(request);
118+
119+
expect(redirectToError).toHaveBeenCalledWith(
120+
request,
121+
"Missing State Parameter",
122+
"Please try again.",
123+
"The state parameter is required for security purposes."
124+
);
125+
});
126+
127+
it("handles auth token exchange failure", async () => {
128+
const { exchangeCodeForToken } = await import("@/lib/auth-utils");
129+
const { redirectToError } = await import("@/lib/response-utils");
130+
131+
vi.mocked(exchangeCodeForToken).mockRejectedValue(
132+
new Error("Invalid authorization code")
133+
);
134+
135+
vi.mocked(redirectToError).mockReturnValue(
136+
new Response(null, {
137+
status: 302,
138+
headers: { Location: "/error" },
139+
})
140+
);
141+
142+
const request = new NextRequest(
143+
"http://localhost:3000/api/auth/callback?code=invalid-code&state=test-state&projectID=test-project-123"
144+
);
145+
146+
await GET(request);
147+
148+
expect(redirectToError).toHaveBeenCalledWith(
149+
request,
150+
"Authentication Failed",
151+
"Failed to authenticate with Prisma. Please try again.",
152+
"Invalid authorization code"
153+
);
154+
});
155+
156+
it("handles project transfer failure", async () => {
157+
const { exchangeCodeForToken, validateProject } = await import(
158+
"@/lib/auth-utils"
159+
);
160+
const { redirectToError } = await import("@/lib/response-utils");
161+
const { transferProject } = await import("@/lib/project-transfer");
162+
163+
vi.mocked(exchangeCodeForToken).mockResolvedValue({
164+
access_token: "test-token",
165+
});
166+
167+
vi.mocked(validateProject).mockResolvedValue(undefined);
168+
169+
vi.mocked(transferProject).mockResolvedValue({
170+
success: false,
171+
status: 403,
172+
error: "Insufficient permissions",
173+
});
174+
175+
vi.mocked(redirectToError).mockReturnValue(
176+
new Response(null, {
177+
status: 302,
178+
headers: { Location: "/error" },
179+
})
180+
);
181+
182+
const request = new NextRequest(
183+
"http://localhost:3000/api/auth/callback?code=test-code&state=test-state&projectID=test-project-123"
184+
);
185+
186+
await GET(request);
187+
188+
expect(redirectToError).toHaveBeenCalledWith(
189+
request,
190+
"Transfer Failed",
191+
"Failed to transfer the project. Please try again.",
192+
expect.stringContaining("Insufficient permissions")
193+
);
194+
});
195+
});
196+
});

0 commit comments

Comments
 (0)