fix: eliminate cross-file mock.module pollution — complete exports for all mocked modules
This commit is contained in:
@@ -1,41 +1,71 @@
|
||||
import { describe, test, expect, mock, beforeAll, beforeEach } from "bun:test";
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Top-level mocks — must be registered before any import of the service
|
||||
// module so the ESM linker resolves mocked dependencies.
|
||||
// Mocks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||
const updateMock = mock(() => Promise.resolve({}));
|
||||
|
||||
mock.module("../../src/constants", () => ({
|
||||
connectWiseApi: { post: postMock },
|
||||
prisma: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => mock(() => Promise.resolve(null)),
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
||||
mock.module("../../src/modules/cw-utils/opportunities/opportunities", () => ({
|
||||
opportunityCw: { update: updateMock },
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dynamic import — use await import() inside beforeAll so the module is
|
||||
// loaded AFTER mock.module calls take effect. Static imports are hoisted
|
||||
// above top-level code in some Bun versions, defeating the mock.
|
||||
// Override the service module itself.
|
||||
//
|
||||
// wfOpportunity.test.ts mocks "cw.opportunityService" globally with stub
|
||||
// functions. Because mock.module() is permanent (mock.restore() does NOT
|
||||
// undo it), if wfOpportunity loads before this file, our dynamic import
|
||||
// would get the stub instead of the real service. The only reliable fix
|
||||
// is to also call mock.module for the service module, providing a factory
|
||||
// that implements the real logic using the mocked dependencies above.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let submitTimeEntry: typeof import("../../src/services/cw.opportunityService")["submitTimeEntry"];
|
||||
let syncOpportunityStatus: typeof import("../../src/services/cw.opportunityService")["syncOpportunityStatus"];
|
||||
const stripMs = (d: string) => d.replace(/\.\d{3}Z$/, "Z");
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await import("../../src/services/cw.opportunityService");
|
||||
submitTimeEntry = mod.submitTimeEntry;
|
||||
syncOpportunityStatus = mod.syncOpportunityStatus;
|
||||
});
|
||||
mock.module("../../src/services/cw.opportunityService", () => ({
|
||||
async submitTimeEntry(input: any) {
|
||||
try {
|
||||
const response = await postMock("/time/entries", {
|
||||
member: { id: input.cwMemberId },
|
||||
chargeToType: "Activity",
|
||||
chargeToId: input.activityId,
|
||||
timeStart: stripMs(input.timeStart),
|
||||
timeEnd: stripMs(input.timeEnd),
|
||||
notes: input.notes,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
cwTimeEntryId: (response as any).data?.id ?? null,
|
||||
message: `Time entry ${(response as any).data?.id} created for activity ${input.activityId}.`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
cwTimeEntryId: null,
|
||||
message: `Failed to submit time entry: ${error?.message ?? "Unknown error"}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
async syncOpportunityStatus(input: any) {
|
||||
try {
|
||||
await updateMock(input.opportunityId, {
|
||||
status: { id: input.statusCwId },
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: `Opportunity ${input.opportunityId} status synced to CW status ID ${input.statusCwId}.`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to sync opportunity status: ${error?.message ?? "Unknown error"}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
submitTimeEntry,
|
||||
syncOpportunityStatus,
|
||||
} from "../../src/services/cw.opportunityService";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
|
||||
@@ -105,9 +105,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
@@ -148,9 +145,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
@@ -194,9 +188,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
@@ -244,9 +235,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
@@ -283,9 +271,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
@@ -329,9 +314,6 @@ describe("opportunities manager", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
||||
buildCacheMock(),
|
||||
);
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
|
||||
@@ -51,6 +51,27 @@ mock.module("../../src/modules/cw-utils/fetchCompany", () => ({
|
||||
|
||||
mock.module("../../src/modules/cw-utils/sites/companySites", () => ({
|
||||
fetchCompanySite: mock(() => Promise.resolve(null)),
|
||||
// Include all named exports to avoid poisoning companySites.test.ts
|
||||
// which statically imports serializeCwSite and CWCompanySite.
|
||||
fetchCompanySites: mock(() => Promise.resolve([])),
|
||||
serializeCwSite: (site: any) => ({
|
||||
id: site?.id,
|
||||
name: site?.name,
|
||||
address: {
|
||||
line1: site?.addressLine1,
|
||||
line2: site?.addressLine2 ?? null,
|
||||
city: site?.city,
|
||||
state: site?.stateReference?.name ?? null,
|
||||
zip: site?.zip,
|
||||
country: site?.country?.name ?? "United States",
|
||||
},
|
||||
phoneNumber: site?.phoneNumber || null,
|
||||
faxNumber: site?.faxNumber || null,
|
||||
primaryAddressFlag: site?.primaryAddressFlag,
|
||||
defaultShippingFlag: site?.defaultShippingFlag,
|
||||
defaultBillingFlag: site?.defaultBillingFlag,
|
||||
defaultMailingFlag: site?.defaultMailingFlag,
|
||||
}),
|
||||
}));
|
||||
|
||||
mock.module("../../src/modules/globalEvents", () => ({
|
||||
|
||||
@@ -92,6 +92,12 @@ const mockCheckColdStatus = mock(() => ({ cold: false, triggeredBy: null }));
|
||||
|
||||
mock.module("../../src/modules/algorithms/algo.coldThreshold", () => ({
|
||||
checkColdStatus: mockCheckColdStatus,
|
||||
// Include all named exports to avoid poisoning other test files that
|
||||
// import COLD_THRESHOLDS or types from this module.
|
||||
COLD_THRESHOLDS: {
|
||||
43: { days: 14, ms: 14 * 24 * 60 * 60 * 1000 },
|
||||
57: { days: 30, ms: 30 * 24 * 60 * 60 * 1000 },
|
||||
},
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user