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
|
// Mocks
|
||||||
// module so the ESM linker resolves mocked dependencies.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||||
const updateMock = mock(() => Promise.resolve({}));
|
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
|
// Override the service module itself.
|
||||||
// loaded AFTER mock.module calls take effect. Static imports are hoisted
|
//
|
||||||
// above top-level code in some Bun versions, defeating the mock.
|
// 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"];
|
const stripMs = (d: string) => d.replace(/\.\d{3}Z$/, "Z");
|
||||||
let syncOpportunityStatus: typeof import("../../src/services/cw.opportunityService")["syncOpportunityStatus"];
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
mock.module("../../src/services/cw.opportunityService", () => ({
|
||||||
const mod = await import("../../src/services/cw.opportunityService");
|
async submitTimeEntry(input: any) {
|
||||||
submitTimeEntry = mod.submitTimeEntry;
|
try {
|
||||||
syncOpportunityStatus = mod.syncOpportunityStatus;
|
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
|
// Tests
|
||||||
|
|||||||
@@ -105,9 +105,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||||
() => ({
|
() => ({
|
||||||
@@ -148,9 +145,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||||
() => ({
|
() => ({
|
||||||
@@ -194,9 +188,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||||
() => ({
|
() => ({
|
||||||
@@ -244,9 +235,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||||
() => ({
|
() => ({
|
||||||
@@ -283,9 +271,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||||
() => ({
|
() => ({
|
||||||
@@ -329,9 +314,6 @@ describe("opportunities manager", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mock.module("../../src/modules/cache/opportunityCache", () =>
|
|
||||||
buildCacheMock(),
|
|
||||||
);
|
|
||||||
mock.module(
|
mock.module(
|
||||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
"../../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", () => ({
|
mock.module("../../src/modules/cw-utils/sites/companySites", () => ({
|
||||||
fetchCompanySite: mock(() => Promise.resolve(null)),
|
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", () => ({
|
mock.module("../../src/modules/globalEvents", () => ({
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ const mockCheckColdStatus = mock(() => ({ cold: false, triggeredBy: null }));
|
|||||||
|
|
||||||
mock.module("../../src/modules/algorithms/algo.coldThreshold", () => ({
|
mock.module("../../src/modules/algorithms/algo.coldThreshold", () => ({
|
||||||
checkColdStatus: mockCheckColdStatus,
|
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