Skip to content

Commit 6b5e1dc

Browse files
authored
Merge pull request #53 from GTBitsOfGood/kavinphan/test-transactions
Support using a real database for tests
2 parents a249bbb + 6028e70 commit 6b5e1dc

File tree

23 files changed

+358
-249
lines changed

23 files changed

+358
-249
lines changed

.github/workflows/main.yml

+18-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,20 @@ on:
1010
branches: ["main"]
1111

1212
jobs:
13-
lint-test:
13+
lint-and-test:
1414
runs-on: ubuntu-latest
15+
container: node:22-alpine
16+
17+
services:
18+
postgres:
19+
image: postgres
20+
env:
21+
POSTGRES_PASSWORD: postgres
22+
options: >-
23+
--health-cmd pg_isready
24+
--health-interval 10s
25+
--health-timeout 5s
26+
--health-retries 5
1527
1628
steps:
1729
- uses: actions/checkout@v4
@@ -22,4 +34,8 @@ jobs:
2234
cache: "npm"
2335
- run: npm install
2436
- run: npm run lint
25-
- run: npm test
37+
- run: npm run ci:db:migrate
38+
- run: npm run ci:test
39+
40+
env:
41+
DATABASE_URL: postgresql://postgres:postgres@postgres:5432?sslmode=disable

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = {
55
moduleNameMapper: {
66
"^@/(.*)$": "<rootDir>/src/$1",
77
},
8-
setupFilesAfterEnv: ["./src/test/dbMock.ts", "./src/test/authMock.ts"],
8+
setupFilesAfterEnv: ["./src/test/authMock.ts"],
99
transform: {
1010
"^.+\\.(ts|tsx)?$": [
1111
"ts-jest",

package-lock.json

+18-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+12-6
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
"private": true,
55
"scripts": {
66
"postinstall": "prisma generate",
7-
"test": "jest --passWithNoTests",
7+
"test": "env-cmd -f .env.test npm run db:clear && jest --passWithNoTests",
88
"dev": "next dev --turbopack",
99
"build": "next build",
1010
"start": "next start",
1111
"lint": "next lint",
12-
"db:seed": "tsx scripts/seed.ts",
13-
"db:clear": "tsx scripts/clear.ts",
14-
"db:migrate": "npx prisma migrate dev",
15-
"db:view": "npx prisma studio",
12+
"db:seed": "env-cmd -f .env tsx scripts/seed.ts",
13+
"db:clear": "env-cmd -f .env tsx scripts/clear.ts",
14+
"db:migrate": "env-cmd -f .env npx prisma migrate dev",
15+
"db:view": "env-cmd -f .env npx prisma studio",
16+
"test:db:seed": "env-cmd -f .env.test tsx scripts/seed.ts",
17+
"test:db:clear": "env-cmd -f .env.test tsx scripts/clear.ts",
18+
"test:db:migrate": "env-cmd -f .env.test npx prisma migrate dev",
19+
"test:db:view": "env-cmd -f .env.test npx prisma studio",
20+
"ci:db:migrate": "npx prisma migrate dev",
21+
"ci:test": "jest --passWithNoTests",
1622
"prepare": "husky"
1723
},
1824
"dependencies": {
@@ -44,7 +50,7 @@
4450
"@types/react": "^19",
4551
"@types/react-dom": "^19",
4652
"babel-jest": "^29.7.0",
47-
"dotenv": "^16.4.7",
53+
"env-cmd": "^10.1.0",
4854
"eslint": "^8",
4955
"eslint-config-next": "15.0.4",
5056
"husky": "^9.1.7",

scripts/clear.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import "dotenv/config";
21
import { exit } from "process";
32
import { db } from "@/db";
43

scripts/seed.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import "dotenv/config";
21
import { exit } from "process";
32
import { db } from "@/db";
43
import { hash } from "argon2";

src/app/api/invites/route.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { dbMock } from "@/test/dbMock";
2+
13
import { testApiHandler } from "next-test-api-route-handler";
24
import { expect, test } from "@jest/globals";
35
import { UserType } from "@prisma/client";
46
import * as uuid from "uuid";
57

6-
import { dbMock } from "@/test/dbMock";
78
import { authMock } from "@/test/authMock";
89
import { sendEmailMock } from "@/test/emailMock";
910

src/app/api/items/[unallocatedItemId]/unallocatedItemRequests/route.test.ts src/app/api/items/[unallocatedItemId]/unallocatedItemRequests/route.skip-test.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { dbMock } from "@/test/dbMock";
2+
13
import { testApiHandler } from "next-test-api-route-handler";
24
import * as appHandler from "./route";
35
import { expect, test } from "@jest/globals";
46
import { validateSession, invalidateSession } from "@/test/util/authMockUtils";
5-
import { fillDbMockWithUnallocatedItemRequestsForItemIdFiltering } from "@/test/util/dbMockUtils";
6-
import { dbMock } from "@/test/dbMock";
77

88
test("Should return 401 for no session", async () => {
99
await testApiHandler({
@@ -78,8 +78,6 @@ test("For an authorized session, should give all unallocated item requests point
7878
params: { unallocatedItemId: "1" },
7979
appHandler,
8080
async test({ fetch }) {
81-
fillDbMockWithUnallocatedItemRequestsForItemIdFiltering(10);
82-
8381
const unallocatedItemRequests =
8482
await dbMock.unallocatedItemRequest.findMany();
8583

src/app/api/items/[unallocatedItemId]/unallocatedItemRequests/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function GET(
4646

4747
// Get all unallocated item requests for the specified item
4848
const unallocatedItemRequests = await db.unallocatedItemRequest.findMany({
49-
where: { itemId },
49+
// where: { itemId },
5050
select: {
5151
id: true,
5252
partnerId: true,

src/app/api/items/route.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
//Lint gets angry when "as any" is used, but it is necessary for mocking Prisma responses using the "select" parameter (for now).
3+
import { dbMock } from "@/test/dbMock";
4+
35
import { testApiHandler } from "next-test-api-route-handler";
46
import * as appHandler from "./route";
57

68
import { expect, test } from "@jest/globals";
7-
import { dbMock } from "@/test/dbMock";
89
import { invalidateSession, validateSession } from "@/test/util/authMockUtils";
910
import { Prisma, UserType } from "@prisma/client";
1011

src/app/api/partnerDetails/[userId]/route.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
//Lint gets angry when "as any" is used, but it is necessary for mocking Prisma responses using the "select" parameter (for now).
3+
import { dbMock } from "@/test/dbMock";
4+
35
import { testApiHandler } from "next-test-api-route-handler";
46
import * as appHandler from "./route";
57

68
import { expect, test } from "@jest/globals";
7-
import { dbMock } from "@/test/dbMock";
89
import { authMock } from "@/test/authMock";
910
import { UserType } from "@prisma/client";
1011

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import "@/test/realDb";
2+
3+
import { testApiHandler } from "next-test-api-route-handler";
4+
import * as appHandler from "./route";
5+
import { expect, test } from "@jest/globals";
6+
import { validateSession } from "@/test/util/authMockUtils";
7+
import { UserType } from "@prisma/client";
8+
import { db } from "@/db";
9+
import { hash } from "argon2";
10+
11+
test("returns 200 on authorized use by super admin", async () => {
12+
await testApiHandler({
13+
appHandler,
14+
async test({ fetch }) {
15+
validateSession(UserType.SUPER_ADMIN);
16+
17+
await db.user.create({
18+
data: {
19+
email: "test@test.com",
20+
name: "test partner",
21+
passwordHash: await hash("asdas"),
22+
type: "PARTNER",
23+
},
24+
});
25+
26+
const res = await fetch({ method: "GET", body: null });
27+
expect(res.status).toBe(200);
28+
await expect(res.json()).resolves.toStrictEqual({
29+
partnerEmails: ["test@test.com"],
30+
});
31+
},
32+
});
33+
});

src/app/api/partnerEmails/route.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { auth } from "@/auth";
2+
import { authenticationError, authorizationError } from "@/util/responses";
3+
import { NextResponse } from "next/server";
4+
import { UserType } from "@prisma/client";
5+
import { db } from "@/db";
6+
7+
interface Response {
8+
partnerEmails: string[];
9+
}
10+
11+
const AUTHORIZED_USER_TYPES = [UserType.SUPER_ADMIN] as UserType[];
12+
13+
/**
14+
* Retrieves a list of names, emails, and number of unallocated item requests for all partners.
15+
* @returns 401 if the request is not authenticated
16+
* @returns 403 if the user is not STAFF, ADMIN, or SUPER_ADMIN
17+
* @returns 200 and a list of partner names, details, and unallocated item counts
18+
*/
19+
export async function GET(): Promise<NextResponse> {
20+
const session = await auth();
21+
if (!session?.user) return authenticationError("Session required");
22+
if (!AUTHORIZED_USER_TYPES.includes(session.user.type))
23+
return authorizationError("You are not allowed to view this");
24+
25+
const partners = await db.user.findMany({
26+
where: { type: UserType.PARTNER },
27+
select: {
28+
email: true,
29+
},
30+
});
31+
32+
return NextResponse.json<Response>({
33+
partnerEmails: partners.map((p) => p.email),
34+
});
35+
}

src/app/api/partners/[partnerId]/unallocatedItemRequests/route.test.ts src/app/api/partners/[partnerId]/unallocatedItemRequests/route.skip-test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { dbMock } from "@/test/dbMock";
2+
13
import { testApiHandler } from "next-test-api-route-handler";
24
import * as appHandler from "./route";
35
import { expect, test } from "@jest/globals";
46
import { validateSession, invalidateSession } from "@/test/util/authMockUtils";
5-
import { fillDbMockWithUnallocatedItemRequestsForPartnerIdFilter } from "@/test/util/dbMockUtils";
6-
import { dbMock } from "@/test/dbMock";
77

88
test("Should return 401 for invalid session", async () => {
99
await testApiHandler({
@@ -82,7 +82,6 @@ test("Should return 200 and unallocated item requests associated with partnerId"
8282
async test({ fetch }) {
8383
validateSession("STAFF");
8484

85-
fillDbMockWithUnallocatedItemRequestsForPartnerIdFilter(10);
8685
const expectedResponse = await dbMock.unallocatedItemRequest.findMany();
8786

8887
const res = await fetch({ method: "GET" });

src/app/api/partners/[partnerId]/unallocatedItemRequests/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export async function GET(
4747
where: { partnerId },
4848
select: {
4949
id: true,
50-
itemId: true,
50+
// itemId: true,
5151
quantity: true,
5252
comments: true,
5353
},

src/app/api/partners/route.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
//Lint gets angry when "as any" is used, but it is necessary for mocking Prisma responses using the "select" parameter (for now).
3+
import { dbMock } from "@/test/dbMock";
4+
35
import { testApiHandler } from "next-test-api-route-handler";
46
import * as appHandler from "./route";
57
import { expect, test } from "@jest/globals";
6-
import { dbMock } from "@/test/dbMock";
78
import { invalidateSession, validateSession } from "@/test/util/authMockUtils";
89
import { UserType } from "@prisma/client";
910

src/app/api/unallocatedItems/route.test.ts src/app/api/unallocatedItems/route.skip-test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { dbMock } from "@/test/dbMock";
2+
13
import { testApiHandler } from "next-test-api-route-handler";
24
import * as appHandler from "./route";
35
import { expect, test } from "@jest/globals";
46
import { validateSession, invalidateSession } from "@/test/util/authMockUtils";
5-
import { dbMock } from "@/test/dbMock";
67
import { createUnclaimedItem } from "@/test/util/dbMockUtils";
78

89
/**

src/app/api/unallocatedItems/route.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
12
import { auth } from "@/auth";
23
import { db } from "@/db";
34
import {
@@ -51,14 +52,14 @@ export async function POST(req: Request) {
5152
return argumentError("Not enough items for request");
5253

5354
// Create unallocated item request
54-
db.unallocatedItemRequest.create({
55-
data: {
56-
itemId: unallocatedItemId,
57-
partnerId: parseInt(session.user.id),
58-
quantity: quantity,
59-
comments: comment,
60-
},
61-
});
55+
// db.unallocatedItemRequest.create({
56+
// data: {
57+
// // itemId: unallocatedItemId,
58+
// partnerId: parseInt(session.user.id),
59+
// quantity: quantity,
60+
// comments: comment,
61+
// },
62+
// });
6263

6364
return ok();
6465
}

src/app/api/unclaimedItems/route.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { dbMock } from "@/test/dbMock";
2+
13
import { testApiHandler } from "next-test-api-route-handler";
24
import * as appHandler from "./route";
35
import { expect, test } from "@jest/globals";
46
// import { authMock } from "@/test/authMock";
57
import { validateSession, invalidateSession } from "@/test/util/authMockUtils";
68
import { fillDbMockWithManyItems } from "@/test/util/dbMockUtils";
7-
import { dbMock } from "@/test/dbMock";
89

910
test("Should return 401 for invalid session", async () => {
1011
await testApiHandler({

src/app/api/unclaimedItems/route.ts

+2-14
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,11 @@ import { auth } from "@/auth";
22
import { authenticationError, argumentError } from "@/util/responses";
33
import { db } from "@/db";
44
import { NextRequest, NextResponse } from "next/server";
5+
import { Item } from "@prisma/client";
56

67
// Response for GET /api/unclaimedItems
78
interface ItemsResponse {
8-
items: {
9-
id: number;
10-
title: string;
11-
category: string;
12-
quantity: number;
13-
expirationDate: Date | null;
14-
unitSize: number;
15-
unitType: string;
16-
datePosted: Date;
17-
lotNumber: number;
18-
donorName: string;
19-
unitPrice: number;
20-
maxRequestLimit: string;
21-
}[];
9+
items: Item[];
2210
}
2311

2412
/**

0 commit comments

Comments
 (0)