Skip to content

Commit 874a882

Browse files
authored
Reorganize Live and E2E tests (#161)
* FIx Playwright base typing issues * Create linkry exists and not-exists tests. * Use `getBaseEndpoint()` function to allow us to run live tests on different endpoints.
1 parent 2eb8e1d commit 874a882

17 files changed

+207
-25
lines changed

tests/e2e/base.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test as base } from "@playwright/test";
1+
import { test as base, Page } from "@playwright/test";
22
import {
33
SecretsManagerClient,
44
GetSecretValueCommand,
@@ -7,7 +7,9 @@ import {
77
export const getSecretValue = async (
88
secretId: string,
99
): Promise<Record<string, string | number | boolean> | null> => {
10-
const smClient = new SecretsManagerClient();
10+
const smClient = new SecretsManagerClient({
11+
region: process.env.AWS_REGION ?? "us-east-1",
12+
});
1113
const data = await smClient.send(
1214
new GetSecretValueCommand({ SecretId: secretId }),
1315
);
@@ -32,10 +34,10 @@ async function getSecrets() {
3234
}
3335
response["PLAYWRIGHT_USERNAME"] =
3436
process.env.PLAYWRIGHT_USERNAME ||
35-
(keyData ? keyData["playwright_username"] : "");
37+
((keyData ? keyData["playwright_username"] : "") as string);
3638
response["PLAYWRIGHT_PASSWORD"] =
3739
process.env.PLAYWRIGHT_PASSWORD ||
38-
(keyData ? keyData["playwright_password"] : "");
40+
((keyData ? keyData["playwright_password"] : "") as string);
3941
return response;
4042
}
4143

@@ -45,7 +47,7 @@ export function capitalizeFirstLetter(string: string) {
4547
return string.charAt(0).toUpperCase() + string.slice(1);
4648
}
4749

48-
async function becomeUser(page) {
50+
async function becomeUser(page: Page) {
4951
await page.goto("https://core.aws.qa.acmuiuc.org/login");
5052
await page
5153
.getByRole("button", { name: "Sign in with Illinois NetID" })
@@ -73,7 +75,7 @@ export async function getAllEvents() {
7375
return (await data.json()) as Record<string, string>[];
7476
}
7577

76-
export const test = base.extend<{ becomeUser: (page) => Promise<void> }>({
78+
export const test = base.extend<{ becomeUser: (page: Page) => Promise<void> }>({
7779
becomeUser: async ({}, use) => {
7880
use(becomeUser);
7981
},

tests/live/apiKey.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test, describe } from "vitest";
2+
import { getBaseEndpoint } from "./utils.js";
23

3-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
45

56
describe("API Key tests", async () => {
67
test("Test that auth is present on routes", { timeout: 10000 }, async () => {

tests/live/documentation.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test } from "vitest";
2+
import { getBaseEndpoint } from "./utils.js";
23

3-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
45

56
test("Get OpenAPI JSON", async () => {
67
const response = await fetch(`${baseEndpoint}/api/documentation/json`);

tests/live/events.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { expect, test } from "vitest";
22
import { EventsGetResponse } from "../../src/api/routes/events.js";
33
import { createJwt } from "./utils.js";
44
import { describe } from "node:test";
5+
import { getBaseEndpoint } from "./utils.js";
6+
7+
const baseEndpoint = getBaseEndpoint();
58

6-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
79
test("getting events", async () => {
810
const response = await fetch(`${baseEndpoint}/api/v1/events`);
911
expect(response.status).toBe(200);

tests/live/healthz.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, test } from "vitest";
2-
import { InternalServerError } from "../../src/common/errors/index.js";
2+
import { getBaseEndpoint } from "./utils.js";
33

4-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
55

66
test("healthz", async () => {
77
const response = await fetch(`${baseEndpoint}/api/v1/healthz`);

tests/live/iam.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import {
55
GroupMemberGetResponse,
66
} from "../../src/common/types/iam.js";
77
import { allAppRoles, AppRoles } from "../../src/common/roles.js";
8+
import { getBaseEndpoint } from "./utils.js";
89

9-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
10+
const baseEndpoint = getBaseEndpoint();
1011
test("getting members of a group", async () => {
1112
const token = await createJwt();
1213
const response = await fetch(

tests/live/ical.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { describe, expect, test } from "vitest";
22
import { CoreOrganizationList } from "@acm-uiuc/js-shared";
33
import ical from "node-ical";
4-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
import { getBaseEndpoint } from "./utils.js";
5+
const baseEndpoint = getBaseEndpoint();
56

67
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
78

tests/live/linrky.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, test } from "vitest";
2+
import { getBaseEndpoint, makeRandomString } from "./utils.js";
3+
4+
const baseEndpoint = getBaseEndpoint("go");
5+
6+
describe("Linkry live tests", async () => {
7+
test("Linkry health check", async () => {
8+
const response = await fetch(`${baseEndpoint}/healthz`);
9+
expect(response.status).toBe(200);
10+
expect(response.redirected).toBe(true);
11+
expect(response.url).toBe("https://www.google.com/");
12+
});
13+
test("Linkry 404 redirect", async () => {
14+
const response = await fetch(`${baseEndpoint}/${makeRandomString(16)}`);
15+
expect(response.status).toBe(200);
16+
expect(response.redirected).toBe(true);
17+
expect(response.url).toBe("https://www.acm.illinois.edu/404");
18+
});
19+
});

tests/live/membership.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test, describe } from "vitest";
2+
import { getBaseEndpoint } from "./utils.js";
23

3-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
45

56
describe("Membership API basic checks", async () => {
67
test("Test that getting member succeeds", { timeout: 3000 }, async () => {

tests/live/mobileWallet.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test, describe } from "vitest";
2+
import { getBaseEndpoint } from "./utils.js";
23

3-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
45

56
describe("Mobile pass issuance", async () => {
67
test(

tests/live/organizations.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, test } from "vitest";
2-
import { InternalServerError } from "../../src/common/errors/index.js";
2+
import { getBaseEndpoint } from "./utils.js";
33

4-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
4+
const baseEndpoint = getBaseEndpoint();
55

66
test("getting organizations", async () => {
77
const response = await fetch(`${baseEndpoint}/api/v1/organizations`);

tests/live/protected.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { expect, test, describe } from "vitest";
2-
import { createJwt } from "./utils";
3-
import { allAppRoles } from "../../src/common/roles";
2+
import { createJwt } from "./utils.js";
3+
import { allAppRoles } from "../../src/common/roles.js";
4+
import { getBaseEndpoint } from "./utils.js";
45

5-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
6+
const baseEndpoint = getBaseEndpoint();
67

78
describe("Role checking live API tests", async () => {
89
const token = await createJwt();

tests/live/stripe.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect, test, describe } from "vitest";
2-
import { createJwt } from "./utils";
2+
import { createJwt } from "./utils.js";
3+
import { getBaseEndpoint } from "./utils.js";
34

4-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
5+
const baseEndpoint = getBaseEndpoint();
56

67
describe("Stripe live API authentication", async () => {
78
const token = await createJwt();

tests/live/tickets.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect, test, describe } from "vitest";
2-
import { createJwt } from "./utils";
2+
import { createJwt } from "./utils.js";
3+
import { getBaseEndpoint } from "./utils.js";
34

4-
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
5+
const baseEndpoint = getBaseEndpoint();
56

67
describe("Tickets live API tests", async () => {
78
const token = await createJwt();

tests/live/utils.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async function getSecrets() {
3333
keyData = await getSecretValue("infra-core-api-config");
3434
}
3535
response["JWTKEY"] =
36-
process.env.JWT_KEY || (keyData ? keyData["jwt_key"] : "");
36+
process.env.JWT_KEY || ((keyData ? keyData["jwt_key"] : "") as string);
3737
return response;
3838
}
3939

@@ -70,3 +70,29 @@ export async function createJwt(
7070
const token = jwt.sign(payload, secretData.JWTKEY, { algorithm: "HS256" });
7171
return token;
7272
}
73+
74+
type Service = "core" | "go" | "ical";
75+
76+
export function getBaseEndpoint(service?: Service) {
77+
const base = process.env.CORE_BASE_URL ?? "https://core.aws.qa.acmuiuc.org";
78+
if (
79+
base.includes("localhost") ||
80+
base.includes("127.0.0.1") ||
81+
base.includes("::1") ||
82+
!service
83+
) {
84+
return base;
85+
}
86+
return base.replace("core", service);
87+
}
88+
89+
export function makeRandomString(length: number) {
90+
var result = "";
91+
var characters =
92+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
93+
var charactersLength = characters.length;
94+
for (var i = 0; i < length; i++) {
95+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
96+
}
97+
return result;
98+
}

tests/unit/logs.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { afterAll, expect, test, beforeEach, vi } from "vitest";
2+
import init from "../../src/api/index.js";
3+
import { describe } from "node:test";
4+
import { mockClient } from "aws-sdk-client-mock";
5+
import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
6+
import { secretObject } from "./secret.testdata.js";
7+
import supertest from "supertest";
8+
import { createJwt } from "./auth.test.js";
9+
import { genericConfig } from "../../src/common/config.js";
10+
import { marshall } from "@aws-sdk/util-dynamodb";
11+
import { Modules } from "../../src/common/modules.js";
12+
13+
const ddbMock = mockClient(DynamoDBClient);
14+
const jwt_secret = secretObject["jwt_key"];
15+
vi.stubEnv("JwtSigningKey", jwt_secret);
16+
17+
const app = await init();
18+
describe("Audit Log tests", async () => {
19+
test("Sad path: Not authenticated", async () => {
20+
await app.ready();
21+
const response = await supertest(app.server)
22+
.get("/api/v1/logs/events")
23+
.send();
24+
expect(response.statusCode).toBe(403);
25+
});
26+
test("Sad path: Authenticated but not authorized", async () => {
27+
await app.ready();
28+
const testJwt = createJwt(undefined, ["1"]);
29+
const response = await supertest(app.server)
30+
.get("/api/v1/logs/events?start=0&end=1")
31+
.set("Authorization", `Bearer ${testJwt}`)
32+
.send();
33+
expect(response.statusCode).toBe(401);
34+
});
35+
test("Sad path: No start and end provided", async () => {
36+
await app.ready();
37+
const testJwt = createJwt(undefined, ["0"]);
38+
const response = await supertest(app.server)
39+
.get("/api/v1/logs/events")
40+
.set("Authorization", `Bearer ${testJwt}`)
41+
.send();
42+
expect(response.statusCode).toBe(400);
43+
expect(response.body).toStrictEqual({
44+
error: true,
45+
name: "ValidationError",
46+
id: 104,
47+
message:
48+
"querystring/start Expected number, received nan, querystring/end Expected number, received nan",
49+
});
50+
});
51+
test("Sad path: Items is undefined", async () => {
52+
const logEntry = {
53+
module: Modules.EVENTS,
54+
actor: "me",
55+
target: "you",
56+
requestId: "c03ddefa-11d7-4b7c-a6d5-771460e1b45f",
57+
message: "no!",
58+
};
59+
ddbMock
60+
.on(QueryCommand, {
61+
TableName: genericConfig.AuditLogTable,
62+
KeyConditionExpression: "#pk = :module AND #sk BETWEEN :start AND :end",
63+
ExpressionAttributeNames: {
64+
"#pk": "module",
65+
"#sk": "createdAt",
66+
},
67+
ExpressionAttributeValues: {
68+
":module": { S: "events" },
69+
":start": { N: "1750349770" },
70+
":end": { N: "1750436176" },
71+
},
72+
})
73+
.resolvesOnce({
74+
Items: [marshall(logEntry)],
75+
});
76+
await app.ready();
77+
const testJwt = createJwt(undefined, ["0"]);
78+
const response = await supertest(app.server)
79+
.get("/api/v1/logs/events?start=1750349770&end=1750436176")
80+
.set("Authorization", `Bearer ${testJwt}`)
81+
.send();
82+
expect(response.statusCode).toBe(200);
83+
expect(response.body).toStrictEqual([logEntry]);
84+
});
85+
ddbMock
86+
.on(QueryCommand, {
87+
TableName: genericConfig.AuditLogTable,
88+
KeyConditionExpression: "#pk = :module AND #sk BETWEEN :start AND :end",
89+
ExpressionAttributeNames: {
90+
"#pk": "module",
91+
"#sk": "createdAt",
92+
},
93+
ExpressionAttributeValues: {
94+
":module": { S: "events" },
95+
":start": { N: "1750349770" },
96+
":end": { N: "1750436176" },
97+
},
98+
})
99+
.resolvesOnce({
100+
Items: undefined,
101+
});
102+
await app.ready();
103+
const testJwt = createJwt(undefined, ["0"]);
104+
const response = await supertest(app.server)
105+
.get("/api/v1/logs/events?start=1750349770&end=1750436176")
106+
.set("Authorization", `Bearer ${testJwt}`)
107+
.send();
108+
expect(response.statusCode).toBe(500);
109+
});
110+
afterAll(async () => {
111+
await app.close();
112+
});
113+
beforeEach(() => {
114+
(app as any).nodeCache.flushAll();
115+
(app as any).redisClient.flushdb();
116+
ddbMock.reset();
117+
vi.clearAllMocks();
118+
vi.useFakeTimers();
119+
});

tests/unit/vitest.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export default defineConfig({
1212
provider: "istanbul",
1313
include: ["src/api/**/*.ts", "src/common/**/*.ts"],
1414
exclude: ["src/api/lambda.ts"],
15+
thresholds: {
16+
statements: 54,
17+
functions: 65,
18+
lines: 54,
19+
},
1520
},
1621
},
1722
resolve: {

0 commit comments

Comments
 (0)