Skip to content

Commit d49cd66

Browse files
author
Rajat
committed
Test suite parallelization fix
1 parent 1c20c0b commit d49cd66

10 files changed

Lines changed: 134 additions & 20 deletions

File tree

apps/queue/jest.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const config = {
2-
preset: "@shelf/jest-mongodb",
32
setupFilesAfterEnv: ["<rootDir>/setupTests.ts"],
43
watchPathIgnorePatterns: ["globalConfig"],
54
moduleNameMapper: {

apps/queue/setupTests.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { MongoMemoryServer } from "mongodb-memory-server";
33

44
let mongod: MongoMemoryServer | null = null;
55

6+
jest.setTimeout(30000);
7+
8+
function getMongoPort(basePort: number) {
9+
const workerId = Number(process.env.JEST_WORKER_ID || "0");
10+
return basePort + workerId;
11+
}
12+
613
// Suppress console.error during tests to reduce noise
714
const originalError = console.error;
815
beforeAll(() => {
@@ -17,14 +24,13 @@ afterAll(() => {
1724
// @shelf/jest-mongodb provides global.__MONGO_URI__ through globalSetup
1825
// If not available, set up MongoDB Memory Server manually
1926
beforeAll(async () => {
20-
let mongoUri = (global as any).__MONGO_URI__ || process.env.MONGO_URL;
21-
22-
// If @shelf/jest-mongodb didn't set up MongoDB, do it manually
23-
if (!mongoUri) {
24-
mongod = await MongoMemoryServer.create();
25-
mongoUri = mongod.getUri();
26-
(global as any).__MONGO_URI__ = mongoUri;
27-
}
27+
mongod = await MongoMemoryServer.create({
28+
instance: {
29+
ip: "127.0.0.1",
30+
port: getMongoPort(38017),
31+
},
32+
});
33+
const mongoUri = mongod.getUri();
2834

2935
if (mongoose.connection.readyState === 0) {
3036
await mongoose.connect(mongoUri);
@@ -33,13 +39,16 @@ beforeAll(async () => {
3339

3440
afterAll(async () => {
3541
if (mongoose.connection.readyState !== 0) {
36-
await mongoose.connection.close();
42+
await mongoose.disconnect();
3743
}
3844

3945
// Clean up manually created MongoDB instance if it exists
4046
if (mongod) {
41-
await mongod.stop();
47+
await mongod.stop({ doCleanup: true, force: true });
48+
mongod = null;
4249
}
50+
51+
delete (global as any).__MONGO_URI__;
4352
});
4453

4554
// Clean up database after each test

apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import UserModel from "../model/user";
1414
import EmailDelivery from "../model/email-delivery";
1515
import * as queries from "../queries";
1616
import * as mail from "../../mail";
17-
import { AdminSequence, InternalUser } from "@courselit/common-logic";
17+
import { InternalUser } from "@courselit/common-logic";
1818
import { getEmailFrom, jwtUtils } from "@courselit/utils";
1919
import { getUnsubLink } from "../../utils/get-unsub-link";
2020
import { getSiteUrl } from "../../utils/get-site-url";
2121
import { sequenceBounceLimit } from "../../constants";
22+
import { AdminSequence } from "@courselit/orm-models";
2223

2324
// Mock dependencies
2425
jest.mock("../../mail");
@@ -288,6 +289,20 @@ describe("processOngoingSequence", () => {
288289
sequenceId: { $ne: TEST_SEQUENCE_ID }, // Keep main test sequence
289290
});
290291
jest.clearAllMocks();
292+
293+
mockedGetSiteUrl.mockReturnValue("https://test.com");
294+
mockedGetUnsubLink.mockReturnValue(
295+
"https://test.com/api/unsubscribe/unsub-token-123",
296+
);
297+
mockedGetEmailFrom.mockImplementation(
298+
({ name, email }) => `${name} <${email}>`,
299+
);
300+
mockedJwtUtils.generateToken = jest.fn().mockReturnValue("test-token");
301+
mockedSendMail.mockResolvedValue(undefined);
302+
});
303+
304+
afterEach(() => {
305+
jest.restoreAllMocks();
291306
});
292307

293308
afterAll(async () => {

apps/web/graphql/communities/logic.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ export async function updateCommunity({
311311
if (slug) {
312312
const newSlug = validateSlug(slug);
313313
if (newSlug !== community.slug) {
314+
const conflictingCommunity =
315+
await CommunityModel.findOne<InternalCommunity>({
316+
domain: ctx.subdomain._id,
317+
slug: newSlug,
318+
communityId: { $ne: community.communityId },
319+
}).select("_id");
320+
321+
if (conflictingCommunity) {
322+
throw new Error(responses.page_id_already_exists);
323+
}
324+
314325
// Page-first atomicity: update Page record first (hard unique constraint)
315326
try {
316327
await PageModel.updateOne(

apps/web/graphql/courses/__tests__/slug.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,19 @@ describe("Course Slug Tests", () => {
7070
});
7171

7272
afterEach(async () => {
73+
if (!domain?._id) {
74+
return;
75+
}
76+
7377
await CourseModel.deleteMany({ domain: domain._id });
7478
await PageModel.deleteMany({ domain: domain._id });
7579
});
7680

7781
afterAll(async () => {
82+
if (!domain?._id) {
83+
return;
84+
}
85+
7886
await CourseModel.deleteMany({ domain: domain._id });
7987
await PageModel.deleteMany({ domain: domain._id });
8088
await UserModel.deleteMany({ domain: domain._id });

apps/web/graphql/courses/logic.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ export const updateCourse = async (
209209
) => {
210210
let course = await getCourseOrThrow(undefined, ctx, courseData.id);
211211

212+
if (
213+
typeof courseData.title === "string" &&
214+
courseData.title.trim() &&
215+
courseData.title !== course.title
216+
) {
217+
const conflictingCourse = await CourseModel.findOne({
218+
domain: ctx.subdomain._id,
219+
title: courseData.title,
220+
courseId: { $ne: course.courseId },
221+
}).select("_id");
222+
223+
if (conflictingCourse) {
224+
throw new Error(responses.page_id_already_exists);
225+
}
226+
}
227+
212228
const mediaIdsMarkedForDeletion: string[] = [];
213229
if (Object.prototype.hasOwnProperty.call(courseData, "description")) {
214230
const nextDescription = (courseData.description ?? "") as string;
@@ -260,6 +276,16 @@ export const updateCourse = async (
260276
if (courseData.slug) {
261277
const newSlug = validateSlug(courseData.slug);
262278
if (newSlug !== course.slug) {
279+
const conflictingCourse = await CourseModel.findOne({
280+
domain: ctx.subdomain._id,
281+
slug: newSlug,
282+
courseId: { $ne: course.courseId },
283+
}).select("_id");
284+
285+
if (conflictingCourse) {
286+
throw new Error(responses.page_id_already_exists);
287+
}
288+
263289
try {
264290
await PageModel.updateOne(
265291
{

apps/web/jest.client.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ const config = {
2525
// '!**/jest.config.ts',
2626
// '!**/setupTests.ts',
2727
// ],
28-
globalSetup: "<rootDir>/jest.setup.ts",
29-
globalTeardown: "<rootDir>/jest.teardown.ts",
3028
moduleNameMapper: {
3129
// Ensure a single React instance is used in tests to avoid
3230
// "A React Element from an older version of React was rendered" errors

apps/web/jest.server.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { Config } from "jest";
22

33
const config: Config = {
4-
preset: "@shelf/jest-mongodb",
54
setupFilesAfterEnv: ["<rootDir>/setupTests.server.ts"],
65
watchPathIgnorePatterns: ["globalConfig"],
6+
testEnvironment: "node",
77
moduleNameMapper: {
88
"@courselit/utils": "<rootDir>/../../packages/utils/src",
99
"@courselit/common-logic": "<rootDir>/../../packages/common-logic/src",

apps/web/setupTests.server.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
import mongoose from "mongoose";
2+
import { MongoMemoryServer } from "mongodb-memory-server";
3+
4+
let mongod: MongoMemoryServer | null = null;
5+
6+
jest.setTimeout(30000);
7+
8+
function getMongoPort(basePort: number) {
9+
const workerId = Number(process.env.JEST_WORKER_ID || "0");
10+
return basePort + workerId;
11+
}
212

313
// Suppress console.error during tests to reduce noise
414
const originalError = console.error;
@@ -12,13 +22,28 @@ afterAll(() => {
1222

1323
// Ensure MongoDB connection is established
1424
beforeAll(async () => {
25+
mongod = await MongoMemoryServer.create({
26+
instance: {
27+
ip: "127.0.0.1",
28+
port: getMongoPort(37017),
29+
},
30+
});
31+
const mongoUri = mongod.getUri();
32+
1533
if (mongoose.connection.readyState === 0) {
16-
await mongoose.connect(global.__MONGO_URI__ || process.env.MONGO_URL);
34+
await mongoose.connect(mongoUri);
1735
}
1836
});
1937

2038
afterAll(async () => {
2139
if (mongoose.connection.readyState !== 0) {
22-
await mongoose.connection.close();
40+
await mongoose.disconnect();
2341
}
42+
43+
if (mongod) {
44+
await mongod.stop({ doCleanup: true, force: true });
45+
mongod = null;
46+
}
47+
48+
delete (global as any).__MONGO_URI__;
2449
});

packages/email-editor/src/lib/email-renderer.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ export interface UtmParams {
1212
campaign: string;
1313
}
1414

15+
function stringifyUnknownError(err: unknown): string {
16+
if (err instanceof Error) {
17+
return err.message;
18+
}
19+
20+
if (typeof err === "string") {
21+
return err;
22+
}
23+
24+
try {
25+
return JSON.stringify(err);
26+
} catch {
27+
return String(err);
28+
}
29+
}
30+
1531
function appendUtmParams(url: string, utm: UtmParams): string {
1632
try {
1733
const urlObj = new URL(url);
@@ -138,9 +154,16 @@ export async function renderEmailToHtml({
138154
blockRegistry={blockRegistry}
139155
/>
140156
);
141-
const html = await pretty(await render(template));
142-
return html;
157+
const renderedHtml = await render(template);
158+
159+
try {
160+
return await pretty(renderedHtml);
161+
} catch {
162+
// Pretty-printing is optional. If formatting fails in a given
163+
// runtime, return the valid rendered markup instead of an error page.
164+
return renderedHtml;
165+
}
143166
} catch (err) {
144-
return `<h1>Error: ${err instanceof Error ? err.message : "Unknown error"}</h1>`;
167+
return `<h1>Error: ${stringifyUnknownError(err)}</h1>`;
145168
}
146169
}

0 commit comments

Comments
 (0)