Skip to content

Commit ae0b4cf

Browse files
committed
add better tests
1 parent 0ebf1b3 commit ae0b4cf

File tree

7 files changed

+242
-9
lines changed

7 files changed

+242
-9
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"lockfile-manage": "synp --source-file yarn.lock && cp package-lock.json dist/src/ && rm package-lock.json",
1414
"typecheck": "tsc --noEmit",
1515
"lint": "eslint . --ext .ts --cache",
16-
"prettier": "prettier --check src/*.ts src/**/*.ts",
17-
"prettier:write": "prettier --write src/*.ts src/**/*.ts",
16+
"prettier": "prettier --check src/*.ts src/**/*.ts tests/**/*.ts",
17+
"prettier:write": "prettier --write src/*.ts src/**/*.ts tests/**/*.ts",
1818
"prepare": "node .husky/install.mjs || true",
1919
"lint-staged": "lint-staged",
2020
"test:unit": "cross-env APPLICATION_KEY=infra-events-api vitest tests/unit",

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const environmentConfig: EnvironmentConfigType = {
3535
"940e4f9e-6891-4e28-9e29-148798495cdb": [AppRoles.MANAGER], // ACM Infra Team
3636
"f8dfc4cf-456b-4da3-9053-f7fdeda5d5d6": [AppRoles.MANAGER], // Infra Leads
3737
"0": [AppRoles.MANAGER], // Dummy Group for development only
38+
"1": [AppRoles.PUBLIC], // Dummy Group for development only
3839
},
3940
AzureRoleMapping: { AutonomousWriters: [AppRoles.MANAGER] },
4041
ValidCorsOrigins: [

src/roles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export const runEnvironments = ["dev", "prod"] as const;
33
export type RunEnvironment = (typeof runEnvironments)[number];
44
export enum AppRoles {
55
MANAGER = "manage:events",
6+
PUBLIC = "public",
67
}

src/routes/events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
8989
);
9090
reply.send({
9191
id: entryUUID,
92-
resource: `/api/v1/entry/${entryUUID}`,
92+
resource: `/api/v1/event/${entryUUID}`,
9393
});
9494
} catch (e: unknown) {
9595
if (e instanceof Error) {

tests/unit/auth.test.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from "vitest";
1+
import { expect, test, vi } from "vitest";
22
import {
33
GetSecretValueCommand,
44
SecretsManagerClient,
@@ -11,11 +11,26 @@ import jwt from "jsonwebtoken";
1111
const ddbMock = mockClient(SecretsManagerClient);
1212

1313
const app = await init();
14-
15-
function createJwt() {
16-
const jwt_secret = secretObject["jwt_key"];
17-
return jwt.sign(jwtPayload, jwt_secret, { algorithm: "HS256" });
14+
const jwt_secret = secretObject["jwt_key"];
15+
export function createJwt(date?: Date, group?: string) {
16+
let modifiedPayload = jwtPayload;
17+
if (date) {
18+
const nowMs = Math.floor(date.valueOf() / 1000);
19+
const laterMs = nowMs + 3600 * 24;
20+
modifiedPayload = {
21+
...jwtPayload,
22+
iat: nowMs,
23+
nbf: nowMs,
24+
exp: laterMs,
25+
};
26+
}
27+
if (group) {
28+
modifiedPayload["groups"][0] = group;
29+
}
30+
return jwt.sign(modifiedPayload, jwt_secret, { algorithm: "HS256" });
1831
}
32+
vi.stubEnv("JwtSigningKey", jwt_secret);
33+
1934
const testJwt = createJwt();
2035

2136
test("Test happy path", async () => {

tests/unit/eventPost.test.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { afterAll, expect, test, beforeEach, vi } from "vitest";
2+
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
3+
import { mockClient } from "aws-sdk-client-mock";
4+
import init from "../../src/index.js";
5+
import { createJwt } from "./auth.test.js";
6+
import {
7+
GetSecretValueCommand,
8+
SecretsManagerClient,
9+
} from "@aws-sdk/client-secrets-manager";
10+
import { secretJson, secretObject } from "./secret.testdata.js";
11+
import supertest from "supertest";
12+
13+
const ddbMock = mockClient(DynamoDBClient);
14+
const smMock = mockClient(SecretsManagerClient);
15+
const jwt_secret = secretObject["jwt_key"];
16+
vi.stubEnv("JwtSigningKey", jwt_secret);
17+
18+
const app = await init();
19+
20+
test("Sad path: Not authenticated", async () => {
21+
await app.ready();
22+
const response = await supertest(app.server).post("/api/v1/events").send({
23+
description: "Test paid event.",
24+
end: "2024-09-25T19:00:00",
25+
featured: true,
26+
host: "Social Committee",
27+
location: "Illini Union",
28+
start: "2024-09-25T18:00:00",
29+
title: "Fall Semiformal",
30+
paidEventId: "sp24_semiformal",
31+
});
32+
33+
expect(response.statusCode).toBe(403);
34+
});
35+
36+
test("Sad path: Authenticated but not authorized", async () => {
37+
await app.ready();
38+
const testJwt = createJwt(undefined, "1");
39+
const response = await supertest(app.server)
40+
.post("/api/v1/events")
41+
.set("Authorization", `Bearer ${testJwt}`)
42+
.send({
43+
description: "Test paid event.",
44+
end: "2024-09-25T19:00:00",
45+
featured: true,
46+
host: "Social Committee",
47+
location: "Illini Union",
48+
start: "2024-09-25T18:00:00",
49+
title: "Fall Semiformal",
50+
paidEventId: "sp24_semiformal",
51+
});
52+
expect(response.statusCode).toBe(401);
53+
});
54+
test("Sad path: Prevent empty body request", async () => {
55+
await app.ready();
56+
const testJwt = createJwt(undefined, "0");
57+
const response = await supertest(app.server)
58+
.post("/api/v1/events")
59+
.set("Authorization", `Bearer ${testJwt}`)
60+
.send();
61+
expect(response.statusCode).toBe(400);
62+
expect(response.body).toStrictEqual({
63+
error: true,
64+
name: "ValidationError",
65+
id: 104,
66+
message: "Required",
67+
});
68+
});
69+
test("Sad path: Prevent specifying repeatEnds on non-repeating events", async () => {
70+
ddbMock.on(PutItemCommand).resolves({});
71+
smMock.on(GetSecretValueCommand).resolves({
72+
SecretString: secretJson,
73+
});
74+
const testJwt = createJwt();
75+
await app.ready();
76+
const response = await supertest(app.server)
77+
.post("/api/v1/events")
78+
.set("authorization", `Bearer ${testJwt}`)
79+
.send({
80+
description: "Test paid event.",
81+
end: "2024-09-25T19:00:00",
82+
featured: false,
83+
host: "Social Committee",
84+
location: "Illini Union",
85+
start: "2024-09-25T18:00:00",
86+
title: "Fall Semiformal",
87+
repeatEnds: "2024-09-25T18:00:00",
88+
paidEventId: "sp24_semiformal",
89+
});
90+
91+
expect(response.statusCode).toBe(400);
92+
expect(response.body).toStrictEqual({
93+
error: true,
94+
name: "ValidationError",
95+
id: 104,
96+
message: "repeats is required when repeatEnds is defined",
97+
});
98+
});
99+
100+
test("Sad path: Prevent specifying unknown repeat frequencies", async () => {
101+
ddbMock.on(PutItemCommand).resolves({});
102+
smMock.on(GetSecretValueCommand).resolves({
103+
SecretString: secretJson,
104+
});
105+
const testJwt = createJwt();
106+
await app.ready();
107+
const response = await supertest(app.server)
108+
.post("/api/v1/events")
109+
.set("authorization", `Bearer ${testJwt}`)
110+
.send({
111+
description: "Test paid event.",
112+
end: "2024-09-25T19:00:00",
113+
featured: false,
114+
host: "Social Committee",
115+
location: "Illini Union",
116+
start: "2024-09-25T18:00:00",
117+
title: "Fall Semiformal",
118+
repeats: "forever_and_ever",
119+
paidEventId: "sp24_semiformal",
120+
});
121+
122+
expect(response.statusCode).toBe(400);
123+
expect(response.body).toStrictEqual({
124+
error: true,
125+
name: "ValidationError",
126+
id: 104,
127+
message: `Invalid enum value. Expected 'weekly' | 'biweekly', received 'forever_and_ever' at "repeats"`,
128+
});
129+
});
130+
131+
test("Happy path: Adding a non-repeating, featured, paid event", async () => {
132+
ddbMock.on(PutItemCommand).resolves({});
133+
smMock.on(GetSecretValueCommand).resolves({
134+
SecretString: secretJson,
135+
});
136+
const testJwt = createJwt();
137+
await app.ready();
138+
const response = await supertest(app.server)
139+
.post("/api/v1/events")
140+
.set("authorization", `Bearer ${testJwt}`)
141+
.send({
142+
description: "Test paid event.",
143+
end: "2024-09-25T19:00:00",
144+
featured: true,
145+
host: "Social Committee",
146+
location: "Illini Union",
147+
start: "2024-09-25T18:00:00",
148+
title: "Fall Semiformal",
149+
paidEventId: "sp24_semiformal",
150+
});
151+
152+
expect(response.statusCode).toBe(200);
153+
const responseDataJson = response.body as { id: string; resource: string };
154+
expect(responseDataJson).toHaveProperty("id");
155+
const uuid = responseDataJson["id"];
156+
expect(responseDataJson).toEqual({
157+
id: uuid,
158+
resource: `/api/v1/event/${uuid}`,
159+
});
160+
});
161+
162+
test("Happy path: Adding a weekly repeating, non-featured, paid event", async () => {
163+
ddbMock.on(PutItemCommand).resolves({});
164+
smMock.on(GetSecretValueCommand).resolves({
165+
SecretString: secretJson,
166+
});
167+
const testJwt = createJwt();
168+
await app.ready();
169+
const response = await supertest(app.server)
170+
.post("/api/v1/events")
171+
.set("authorization", `Bearer ${testJwt}`)
172+
.send({
173+
description: "Test paid event.",
174+
end: "2024-09-25T19:00:00",
175+
featured: false,
176+
host: "Social Committee",
177+
location: "Illini Union",
178+
start: "2024-09-25T18:00:00",
179+
title: "Fall Semiformal",
180+
repeats: "weekly",
181+
paidEventId: "sp24_semiformal",
182+
});
183+
184+
expect(response.statusCode).toBe(200);
185+
const responseDataJson = response.body as { id: string; resource: string };
186+
expect(responseDataJson).toHaveProperty("id");
187+
const uuid = responseDataJson["id"];
188+
expect(responseDataJson).toEqual({
189+
id: uuid,
190+
resource: `/api/v1/event/${uuid}`,
191+
});
192+
});
193+
194+
afterAll(async () => {
195+
await app.close();
196+
vi.useRealTimers();
197+
});
198+
beforeEach(() => {
199+
ddbMock.reset();
200+
smMock.reset();
201+
vi.useFakeTimers();
202+
});

tests/unit/events.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { afterAll, expect, test, beforeEach, vi } from "vitest";
2-
import { ScanCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
2+
import {
3+
ScanCommand,
4+
DynamoDBClient,
5+
PutItemCommand,
6+
} from "@aws-sdk/client-dynamodb";
37
import { mockClient } from "aws-sdk-client-mock";
48
import init from "../../src/index.js";
59
import { EventGetResponse } from "../../src/routes/events.js";
@@ -8,8 +12,18 @@ import {
812
dynamoTableDataUnmarshalled,
913
dynamoTableDataUnmarshalledUpcomingOnly,
1014
} from "./mockEventData.testdata.js";
15+
import { createJwt } from "./auth.test.js";
16+
import {
17+
GetSecretValueCommand,
18+
SecretsManagerClient,
19+
} from "@aws-sdk/client-secrets-manager";
20+
import { secretJson, secretObject } from "./secret.testdata.js";
21+
import supertest from "supertest";
1122

1223
const ddbMock = mockClient(DynamoDBClient);
24+
const smMock = mockClient(SecretsManagerClient);
25+
const jwt_secret = secretObject["jwt_key"];
26+
vi.stubEnv("JwtSigningKey", jwt_secret);
1327

1428
const app = await init();
1529
test("Test getting events", async () => {

0 commit comments

Comments
 (0)