Skip to content

Commit b2e9f08

Browse files
committed
update tests and utils
1 parent c2a9f20 commit b2e9f08

File tree

7 files changed

+219
-39
lines changed

7 files changed

+219
-39
lines changed

generate_jwt.js

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
import jwt from 'jsonwebtoken';
1+
import jwt from "jsonwebtoken";
22
import * as dotenv from "dotenv";
33
dotenv.config();
44

5-
const username = process.env.JWTGEN_USERNAME || '[email protected]'
5+
const username = process.env.JWTGEN_USERNAME || "[email protected]"
66
const payload = {
7-
aud: "custom_jwt",
8-
iss: "custom_jwt",
9-
iat: Math.floor(Date.now() / 1000),
10-
nbf: Math.floor(Date.now() / 1000),
11-
exp: Math.floor(Date.now() / 1000) + (3600 * 24), // Token expires after 24 hour
12-
acr: "1",
13-
aio: "AXQAi/8TAAAA",
14-
amr: ["pwd"],
15-
appid: "your-app-id",
16-
appidacr: "1",
17-
email: username,
18-
groups: ["0"],
19-
idp: "https://login.microsoftonline.com",
20-
ipaddr: "192.168.1.1",
21-
name: "Doe, John",
22-
oid: "00000000-0000-0000-0000-000000000000",
23-
rh: "rh-value",
24-
scp: "user_impersonation",
25-
sub: "subject",
26-
tid: "tenant-id",
27-
unique_name: username,
28-
uti: "uti-value",
29-
ver: "1.0"
7+
aud: "custom_jwt",
8+
iss: "custom_jwt",
9+
iat: Math.floor(Date.now() / 1000),
10+
nbf: Math.floor(Date.now() / 1000),
11+
exp: Math.floor(Date.now() / 1000) + 3600 * 24, // Token expires after 24 hour
12+
acr: "1",
13+
aio: "AXQAi/8TAAAA",
14+
amr: ["pwd"],
15+
appid: "your-app-id",
16+
appidacr: "1",
17+
email: username,
18+
groups: ["0"],
19+
idp: "https://login.microsoftonline.com",
20+
ipaddr: "192.168.1.1",
21+
name: "Doe, John",
22+
oid: "00000000-0000-0000-0000-000000000000",
23+
rh: "rh-value",
24+
scp: "user_impersonation",
25+
sub: "subject",
26+
tid: "tenant-id",
27+
unique_name: username,
28+
uti: "uti-value",
29+
ver: "1.0",
3030
};
3131

3232
const secretKey = process.env.JwtSigningKey;
33-
const token = jwt.sign(payload, secretKey, { algorithm: 'HS256' });
34-
console.log(`USERNAME=${username}`)
35-
console.log('=====================')
36-
console.log(token)
33+
const token = jwt.sign(payload, secretKey, { algorithm: "HS256" });
34+
console.log(`USERNAME=${username}`);
35+
console.log("=====================");
36+
console.log(token);

src/api/routes/events.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
DatabaseFetchError,
1818
DatabaseInsertError,
1919
DiscordEventError,
20+
NotFoundError,
2021
ValidationError,
2122
} from "../../common/errors/index.js";
2223
import { randomUUID } from "crypto";
@@ -85,7 +86,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
8586
"/:id?",
8687
{
8788
schema: {
88-
response: { 200: responseJsonSchema },
89+
response: { 201: responseJsonSchema },
8990
},
9091
preValidation: async (request, reply) => {
9192
await fastify.zodValidateBody(request, reply, postRequestSchema);
@@ -168,7 +169,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
168169
}
169170
throw new DiscordEventError({});
170171
}
171-
reply.send({
172+
reply.status(201).send({
172173
id: entryUUID,
173174
resource: `/api/v1/events/${entryUUID}`,
174175
});
@@ -211,10 +212,15 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
211212
);
212213
const items = response.Items?.map((item) => unmarshall(item));
213214
if (items?.length !== 1) {
214-
throw new Error("Event not found");
215+
throw new NotFoundError({
216+
endpointName: request.url,
217+
});
215218
}
216219
reply.send(items[0]);
217220
} catch (e: unknown) {
221+
if (e instanceof BaseError) {
222+
throw e;
223+
}
218224
if (e instanceof Error) {
219225
request.log.error("Failed to get from DynamoDB: " + e.toString());
220226
}
@@ -233,7 +239,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
233239
"/:id",
234240
{
235241
schema: {
236-
response: { 200: responseJsonSchema },
242+
response: { 201: responseJsonSchema },
237243
},
238244
onRequest: async (request, reply) => {
239245
await fastify.authorize(request, reply, [AppRoles.EVENTS_MANAGER]);
@@ -254,7 +260,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
254260
true,
255261
request.log,
256262
);
257-
reply.send({
263+
reply.status(201).send({
258264
id,
259265
resource: `/api/v1/events/${id}`,
260266
});

tests/live/events.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,67 @@
11
import { expect, test } from "vitest";
22
import { EventsGetResponse } from "../../src/api/routes/events.js";
3+
import { createJwt } from "./utils.js";
34

45
const baseEndpoint = `https://infra-core-api.aws.qa.acmuiuc.org`;
5-
6+
let createdEventUuid;
67
test("getting events", async () => {
78
const response = await fetch(`${baseEndpoint}/api/v1/events`);
89
expect(response.status).toBe(200);
910
const responseJson = (await response.json()) as EventsGetResponse;
1011
expect(responseJson.length).greaterThan(0);
1112
});
13+
14+
test("creating an event", async () => {
15+
const token = await createJwt();
16+
const response = await fetch(`${baseEndpoint}/api/v1/events`, {
17+
method: "POST",
18+
headers: {
19+
Authorization: `Bearer ${token}`,
20+
"Content-Type": "application/json",
21+
},
22+
body: JSON.stringify({
23+
title: "Testing Event",
24+
description: "An event of all time",
25+
start: "2024-12-31T02:00:00",
26+
end: "2024-12-31T03:30:00",
27+
location: "ACM Room (Siebel 1104)",
28+
host: "ACM",
29+
featured: true,
30+
}),
31+
});
32+
expect(response.status).toBe(200);
33+
const responseJson = await response.json();
34+
expect(responseJson).toHaveProperty("id");
35+
expect(responseJson).toHaveProperty("resource");
36+
createdEventUuid = responseJson.id;
37+
});
38+
39+
test.runIf(createdEventUuid)(
40+
"deleting a previously-created event",
41+
async () => {
42+
const token = await createJwt();
43+
const response = await fetch(
44+
`${baseEndpoint}/api/v1/events/${createdEventUuid}`,
45+
{
46+
method: "DELETE",
47+
headers: {
48+
Authorization: `Bearer ${token}`,
49+
},
50+
},
51+
);
52+
expect(response.status).toBe(201);
53+
},
54+
);
55+
56+
test.runIf(createdEventUuid)(
57+
"check that deleted events cannot be found",
58+
async () => {
59+
const response = await fetch(
60+
`${baseEndpoint}/api/v1/events/${createdEventUuid}`,
61+
{
62+
method: "GET",
63+
},
64+
);
65+
expect(response.status).toBe(404);
66+
},
67+
);

tests/live/stripe.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { expect, test, describe } from "vitest";
2+
import { createJwt } from "./utils";
3+
4+
const baseEndpoint = `https://infra-core-api.aws.qa.acmuiuc.org`;
5+
6+
describe("Stripe live API authentication", async () => {
7+
const token = await createJwt();
8+
test(
9+
"Test that auth is present on the GET route",
10+
{ timeout: 10000 },
11+
async () => {
12+
const response = await fetch(
13+
`${baseEndpoint}/api/v1/stripe/paymentLinks`,
14+
{ method: "GET" },
15+
);
16+
expect(response.status).toBe(403);
17+
},
18+
);
19+
test(
20+
"Test that auth is present on the POST route",
21+
{ timeout: 10000 },
22+
async () => {
23+
const response = await fetch(
24+
`${baseEndpoint}/api/v1/stripe/paymentLinks`,
25+
{ method: "POST" },
26+
);
27+
expect(response.status).toBe(403);
28+
},
29+
);
30+
test(
31+
"Test that getting existing links succeeds",
32+
{ timeout: 10000 },
33+
async () => {
34+
const response = await fetch(
35+
`${baseEndpoint}/api/v1/stripe/paymentLinks`,
36+
{
37+
method: "GET",
38+
headers: {
39+
Authorization: `Bearer ${token}`,
40+
},
41+
},
42+
);
43+
expect(response.status).toBe(200);
44+
},
45+
);
46+
});

tests/live/utils.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import jwt from "jsonwebtoken";
2+
import {
3+
SecretsManagerClient,
4+
GetSecretValueCommand,
5+
} from "@aws-sdk/client-secrets-manager";
6+
7+
export const getSecretValue = async (
8+
secretId: string,
9+
): Promise<Record<string, string | number | boolean> | null> => {
10+
const smClient = new SecretsManagerClient({
11+
region: process.env.AWS_REGION || "us-east-1",
12+
});
13+
const data = await smClient.send(
14+
new GetSecretValueCommand({ SecretId: secretId }),
15+
);
16+
if (!data.SecretString) {
17+
return null;
18+
}
19+
try {
20+
return JSON.parse(data.SecretString) as Record<
21+
string,
22+
string | number | boolean
23+
>;
24+
} catch {
25+
return null;
26+
}
27+
};
28+
29+
async function getSecrets() {
30+
const response = { JWTKEY: "" };
31+
let keyData;
32+
if (!process.env.JWT_KEY) {
33+
keyData = await getSecretValue("infra-core-api-config");
34+
}
35+
response["JWTKEY"] =
36+
process.env.JWT_KEY || (keyData ? keyData["jwt_key"] : "");
37+
return response;
38+
}
39+
40+
export async function createJwt(
41+
username: string = "[email protected]",
42+
groups: string[] = ["0"],
43+
) {
44+
const secretData = await getSecrets();
45+
const payload = {
46+
aud: "custom_jwt",
47+
iss: "custom_jwt",
48+
iat: Math.floor(Date.now() / 1000),
49+
nbf: Math.floor(Date.now() / 1000),
50+
exp: Math.floor(Date.now() / 1000) + 3600 * 24, // Token expires after 24 hour
51+
acr: "1",
52+
aio: "AXQAi/8TAAAA",
53+
amr: ["pwd"],
54+
appid: "your-app-id",
55+
appidacr: "1",
56+
email: username,
57+
groups,
58+
idp: "https://login.microsoftonline.com",
59+
ipaddr: "192.168.1.1",
60+
name: "Doe, John",
61+
oid: "00000000-0000-0000-0000-000000000000",
62+
rh: "rh-value",
63+
scp: "user_impersonation",
64+
sub: "subject",
65+
tid: "tenant-id",
66+
unique_name: username,
67+
uti: "uti-value",
68+
ver: "1.0",
69+
};
70+
const token = jwt.sign(payload, secretData.JWTKEY, { algorithm: "HS256" });
71+
return token;
72+
}

tests/unit/discordEvent.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe("Test Events <-> Discord integration", () => {
5151
title: "Fall Semiformal",
5252
paidEventId: "sp24_semiformal",
5353
});
54-
expect(response.statusCode).toBe(200);
54+
expect(response.statusCode).toBe(201);
5555
expect((updateDiscord as Mock).mock.calls.length).toBe(1);
5656
});
5757

@@ -76,7 +76,7 @@ describe("Test Events <-> Discord integration", () => {
7676
repeats: "weekly",
7777
paidEventId: "sp24_semiformal",
7878
});
79-
expect(response.statusCode).toBe(200);
79+
expect(response.statusCode).toBe(201);
8080
expect((updateDiscord as Mock).mock.calls.length).toBe(0);
8181
});
8282

tests/unit/eventPost.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ test("Happy path: Adding a non-repeating, featured, paid event", async () => {
150150
paidEventId: "sp24_semiformal",
151151
});
152152

153-
expect(response.statusCode).toBe(200);
153+
expect(response.statusCode).toBe(201);
154154
const responseDataJson = response.body as { id: string; resource: string };
155155
expect(responseDataJson).toHaveProperty("id");
156156
const uuid = responseDataJson["id"];
@@ -182,7 +182,7 @@ test("Happy path: Adding a weekly repeating, non-featured, paid event", async ()
182182
paidEventId: "sp24_semiformal",
183183
});
184184

185-
expect(response.statusCode).toBe(200);
185+
expect(response.statusCode).toBe(201);
186186
const responseDataJson = response.body as { id: string; resource: string };
187187
expect(responseDataJson).toHaveProperty("id");
188188
const uuid = responseDataJson["id"];

0 commit comments

Comments
 (0)