Skip to content

Commit c62dc27

Browse files
committed
test(backend): add tests for waitlist controller to support v2 answer schema
1 parent 2483e2f commit c62dc27

File tree

2 files changed

+113
-58
lines changed

2 files changed

+113
-58
lines changed

packages/backend/src/waitlist/controller/waitlist.controller-add.test.ts

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
1+
import type { Express } from "express";
12
import request from "supertest";
2-
import type { Answers_v1 } from "@core/types/waitlist/waitlist.answer.types";
3+
import type {
4+
Answers_v1,
5+
Answers_v2,
6+
} from "@core/types/waitlist/waitlist.answer.types";
37

48
describe("POST /api/waitlist", () => {
5-
beforeEach(() => jest.resetModules());
6-
it("should return 400 if answers are invalid", async () => {
7-
jest.doMock("../service/waitlist.service", () => ({
8-
__esModule: true,
9-
default: {
10-
addToWaitlist: jest.fn(),
11-
},
12-
}));
9+
let app: Express;
10+
let mockAddToWaitlist: jest.Mock;
11+
12+
const createTestApp = async (mocks?: {
13+
env?: Record<string, unknown>;
14+
service?: Record<string, unknown>;
15+
}) => {
16+
if (mocks?.env) {
17+
jest.doMock("@backend/common/constants/env.constants", () => mocks.env);
18+
}
19+
if (mocks?.service) {
20+
jest.doMock("../service/waitlist.service", () => mocks.service);
21+
}
22+
1323
const { WaitlistController } = await import("./waitlist.controller");
1424
const express = (await import("express")).default;
15-
const app = express();
16-
app.use(express.json());
17-
app.post("/api/waitlist", WaitlistController.addToWaitlist);
25+
const testApp = express();
26+
testApp.use(express.json());
27+
testApp.post("/api/waitlist", WaitlistController.addToWaitlist);
28+
return testApp;
29+
};
30+
31+
beforeEach(() => {
32+
jest.resetModules();
33+
mockAddToWaitlist = jest.fn();
34+
});
35+
36+
it("should return 400 if answers are invalid", async () => {
37+
app = await createTestApp({
38+
service: {
39+
__esModule: true,
40+
default: { addToWaitlist: mockAddToWaitlist },
41+
},
42+
});
1843

1944
const res = await request(app)
2045
.post("/api/waitlist")
@@ -24,18 +49,14 @@ describe("POST /api/waitlist", () => {
2449
expect(res.error).toBeDefined();
2550
});
2651

27-
it("should return 200 if answers are valid", async () => {
28-
jest.doMock("../service/waitlist.service", () => ({
29-
__esModule: true,
30-
default: {
31-
addToWaitlist: jest.fn().mockResolvedValue(undefined),
52+
it("should return 200 if v1 answers are valid", async () => {
53+
mockAddToWaitlist.mockResolvedValue(undefined);
54+
app = await createTestApp({
55+
service: {
56+
__esModule: true,
57+
default: { addToWaitlist: mockAddToWaitlist },
3258
},
33-
}));
34-
const { WaitlistController } = await import("./waitlist.controller");
35-
const express = (await import("express")).default;
36-
const app = express();
37-
app.use(express.json());
38-
app.post("/api/waitlist", WaitlistController.addToWaitlist);
59+
});
3960

4061
const answers: Answers_v1 = {
4162
email: "test@example.com",
@@ -47,22 +68,43 @@ describe("POST /api/waitlist", () => {
4768
currentlyPayingFor: [],
4869
anythingElse: "I'm a test",
4970
};
71+
72+
const res = await request(app).post("/api/waitlist").send(answers);
73+
74+
expect(res.status).toBe(200);
75+
expect(res.body.error).not.toBeDefined();
76+
expect(mockAddToWaitlist).toHaveBeenCalledWith(answers.email, answers);
77+
});
78+
79+
it("should return 200 if v2 answers are valid", async () => {
80+
mockAddToWaitlist.mockResolvedValue(undefined);
81+
app = await createTestApp({
82+
service: {
83+
__esModule: true,
84+
default: { addToWaitlist: mockAddToWaitlist },
85+
},
86+
});
87+
88+
const answers: Answers_v2 = {
89+
email: "test@example.com",
90+
schemaVersion: "2",
91+
};
92+
5093
const res = await request(app).post("/api/waitlist").send(answers);
5194

5295
expect(res.status).toBe(200);
5396
expect(res.body.error).not.toBeDefined();
97+
expect(mockAddToWaitlist).toHaveBeenCalledWith(answers.email, answers);
5498
});
55-
// this test is at the bottom to avoid
56-
// having to reset ENV in each test
99+
57100
it("should return 500 if emailer values are missing", async () => {
58-
jest.doMock("@backend/common/constants/env.constants", () => ({
59-
ENV: {},
60-
}));
61-
const { WaitlistController } = await import("./waitlist.controller");
62-
const express = (await import("express")).default;
63-
const app = express();
64-
app.use(express.json());
65-
app.post("/api/waitlist", WaitlistController.addToWaitlist);
101+
app = await createTestApp({
102+
env: { ENV: {} },
103+
service: {
104+
__esModule: true,
105+
default: { addToWaitlist: mockAddToWaitlist },
106+
},
107+
});
66108

67109
const answers: Answers_v1 = {
68110
email: "test@example.com",
@@ -73,6 +115,7 @@ describe("POST /api/waitlist", () => {
73115
currentlyPayingFor: [],
74116
profession: "Founder",
75117
};
118+
76119
const res = await request(app).post("/api/waitlist").send(answers);
77120
expect(res.status).toBe(500);
78121
expect(res.body.error).toBe("Missing emailer value(s)");

packages/backend/src/waitlist/controller/waitlist.controller.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,58 @@ import { Request, Response } from "express";
22
import { z } from "zod";
33
import { BaseError } from "@core/errors/errors.base";
44
import { Logger } from "@core/logger/winston.logger";
5-
import { Answers } from "@core/types/waitlist/waitlist.answer.types";
6-
import { Schema_Waitlist } from "@core/types/waitlist/waitlist.types";
5+
import {
6+
Answers,
7+
Answers_v1,
8+
Answers_v2,
9+
} from "@core/types/waitlist/waitlist.answer.types";
710
import { isMissingWaitlistTagId } from "@backend/common/constants/env.util";
811
import { findCompassUserBy } from "../../user/queries/user.queries";
912
import WaitlistService from "../service/waitlist.service";
1013
import { EmailSchema } from "../types/waitlist.types";
1114

1215
const logger = Logger("app:waitlist.controller");
16+
const WaitlistAnswerSchema = Answers.v1.or(Answers.v2);
1317

1418
export class WaitlistController {
1519
private static EmailQuerySchema = z.object({ email: EmailSchema });
1620

21+
private static handleError(err: unknown, res: Response) {
22+
if (err instanceof BaseError) {
23+
logger.error(err);
24+
return res.status(err.statusCode).json({ error: err.description });
25+
}
26+
if (err instanceof Error) {
27+
logger.error(err);
28+
return res.status(500).json({ error: err.message });
29+
}
30+
logger.error("caught unknown error");
31+
return res.status(500).json({ error: "Server error" });
32+
}
33+
1734
static async addToWaitlist(
18-
req: Request<unknown, unknown, Schema_Waitlist>,
35+
req: Request<unknown, unknown, Answers_v1 | Answers_v2>,
1936
res: Response,
2037
) {
2138
if (isMissingWaitlistTagId()) {
2239
return res.status(500).json({ error: "Missing emailer value(s)" });
2340
}
2441

25-
const parseResult = Answers.v1.safeParse(req.body);
42+
const parseResult = WaitlistAnswerSchema.safeParse(req.body);
2643
if (!parseResult.success) {
2744
return res
2845
.status(400)
2946
.json({ error: "Invalid input", details: parseResult.error.flatten() });
3047
}
3148

32-
const answers = parseResult.data;
3349
try {
3450
const result = await WaitlistService.addToWaitlist(
35-
answers.email,
36-
answers,
51+
parseResult.data.email,
52+
parseResult.data,
3753
);
3854
return res.status(200).json(result);
3955
} catch (err) {
40-
// If the error is a BaseError (including EmailerError), return its status and description
41-
if (err instanceof BaseError) {
42-
logger.error(err);
43-
return res.status(err.statusCode).json({ error: err.description });
44-
}
45-
// If it's a generic Error, return its message
46-
if (err instanceof Error) {
47-
logger.error(err);
48-
return res.status(500).json({ error: err.message });
49-
}
50-
// Otherwise, return a generic server error
51-
logger.error("caught unknown error");
52-
return res.status(500).json({ error: "Server error" });
56+
return WaitlistController.handleError(err, res);
5357
}
5458
}
5559

@@ -81,14 +85,22 @@ export class WaitlistController {
8185
findCompassUserBy("email", email),
8286
WaitlistService.getWaitlistRecord(email),
8387
]);
88+
8489
const isActive = !!existingUser;
85-
const status = {
90+
const name =
91+
waitlistRecord && "firstName" in waitlistRecord
92+
? {
93+
firstName: waitlistRecord.firstName,
94+
lastName: waitlistRecord.lastName,
95+
}
96+
: undefined;
97+
98+
return res.status(200).json({
8699
isOnWaitlist,
87100
isInvited,
88101
isActive,
89-
firstName: waitlistRecord?.firstName ?? existingUser?.firstName,
90-
lastName: waitlistRecord?.lastName ?? existingUser?.lastName,
91-
};
92-
return res.status(200).json(status);
102+
firstName: name?.firstName ?? existingUser?.firstName,
103+
lastName: name?.lastName ?? existingUser?.lastName,
104+
});
93105
}
94106
}

0 commit comments

Comments
 (0)