fix: resolve CI test failures — explicit cache mock exports, hoisted service mocks, pinned Bun 1.3.6
This commit is contained in:
@@ -14,6 +14,8 @@ jobs:
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
@@ -14,6 +14,8 @@ jobs:
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// Top-level mocks — configured before the module is imported so the ESM
|
||||
// linker always sees the mocked version, regardless of Bun's module cache.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("cw.opportunityService", () => {
|
||||
beforeEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||
const updateMock = mock(() => Promise.resolve({}));
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// submitTimeEntry
|
||||
// -------------------------------------------------------------------
|
||||
describe("submitTimeEntry()", () => {
|
||||
test("submits time entry and returns success", async () => {
|
||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||
mock.module("../../src/constants", () => ({
|
||||
mock.module("../../src/constants", () => ({
|
||||
connectWiseApi: { post: postMock },
|
||||
prisma: new Proxy(
|
||||
{},
|
||||
@@ -23,18 +16,37 @@ describe("cw.opportunityService", () => {
|
||||
get: () => mock(() => Promise.resolve(null)),
|
||||
},
|
||||
),
|
||||
}));
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
opportunityCw: {
|
||||
update: mock(() => Promise.resolve({})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
||||
const { submitTimeEntry } =
|
||||
await import("../../src/services/cw.opportunityService");
|
||||
mock.module("../../src/modules/cw-utils/opportunities/opportunities", () => ({
|
||||
opportunityCw: { update: updateMock },
|
||||
}));
|
||||
|
||||
// Import AFTER mocks
|
||||
import {
|
||||
submitTimeEntry,
|
||||
syncOpportunityStatus,
|
||||
} from "../../src/services/cw.opportunityService";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("cw.opportunityService", () => {
|
||||
beforeEach(() => {
|
||||
postMock.mockReset();
|
||||
postMock.mockImplementation(() =>
|
||||
Promise.resolve({ data: { id: 9001 } }),
|
||||
);
|
||||
updateMock.mockReset();
|
||||
updateMock.mockImplementation(() => Promise.resolve({}));
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// submitTimeEntry
|
||||
// -------------------------------------------------------------------
|
||||
describe("submitTimeEntry()", () => {
|
||||
test("submits time entry and returns success", async () => {
|
||||
const result = await submitTimeEntry({
|
||||
activityId: 100,
|
||||
cwMemberId: 10,
|
||||
@@ -49,25 +61,6 @@ describe("cw.opportunityService", () => {
|
||||
});
|
||||
|
||||
test("strips milliseconds from ISO timestamps", async () => {
|
||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||
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: mock(() => Promise.resolve({})) },
|
||||
}),
|
||||
);
|
||||
|
||||
const { submitTimeEntry } =
|
||||
await import("../../src/services/cw.opportunityService");
|
||||
await submitTimeEntry({
|
||||
activityId: 100,
|
||||
cwMemberId: 10,
|
||||
@@ -82,26 +75,10 @@ describe("cw.opportunityService", () => {
|
||||
});
|
||||
|
||||
test("returns failure on API error", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
connectWiseApi: {
|
||||
post: mock(() => Promise.reject(new Error("CW down"))),
|
||||
},
|
||||
prisma: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => mock(() => Promise.resolve(null)),
|
||||
},
|
||||
),
|
||||
}));
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
opportunityCw: { update: mock(() => Promise.resolve({})) },
|
||||
}),
|
||||
postMock.mockImplementation(() =>
|
||||
Promise.reject(new Error("CW down")),
|
||||
);
|
||||
|
||||
const { submitTimeEntry } =
|
||||
await import("../../src/services/cw.opportunityService");
|
||||
const result = await submitTimeEntry({
|
||||
activityId: 100,
|
||||
cwMemberId: 10,
|
||||
@@ -121,25 +98,6 @@ describe("cw.opportunityService", () => {
|
||||
// -------------------------------------------------------------------
|
||||
describe("syncOpportunityStatus()", () => {
|
||||
test("syncs status to CW and returns success", async () => {
|
||||
const updateMock = mock(() => Promise.resolve({}));
|
||||
mock.module("../../src/constants", () => ({
|
||||
connectWiseApi: { post: mock(() => Promise.resolve({ data: {} })) },
|
||||
prisma: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => mock(() => Promise.resolve(null)),
|
||||
},
|
||||
),
|
||||
}));
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
opportunityCw: { update: updateMock },
|
||||
}),
|
||||
);
|
||||
|
||||
const { syncOpportunityStatus } =
|
||||
await import("../../src/services/cw.opportunityService");
|
||||
const result = await syncOpportunityStatus({
|
||||
opportunityId: 1001,
|
||||
statusCwId: 24,
|
||||
@@ -150,26 +108,10 @@ describe("cw.opportunityService", () => {
|
||||
});
|
||||
|
||||
test("returns failure on API error", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
connectWiseApi: { post: mock(() => Promise.resolve({ data: {} })) },
|
||||
prisma: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => mock(() => Promise.resolve(null)),
|
||||
},
|
||||
),
|
||||
}));
|
||||
mock.module(
|
||||
"../../src/modules/cw-utils/opportunities/opportunities",
|
||||
() => ({
|
||||
opportunityCw: {
|
||||
update: mock(() => Promise.reject(new Error("API fail"))),
|
||||
},
|
||||
}),
|
||||
updateMock.mockImplementation(() =>
|
||||
Promise.reject(new Error("API fail")),
|
||||
);
|
||||
|
||||
const { syncOpportunityStatus } =
|
||||
await import("../../src/services/cw.opportunityService");
|
||||
const result = await syncOpportunityStatus({
|
||||
opportunityId: 1001,
|
||||
statusCwId: 24,
|
||||
|
||||
@@ -25,17 +25,49 @@ function createStablePrismaMock(
|
||||
);
|
||||
}
|
||||
|
||||
/** Build a complete cache mock — any unspecified export returns a mock fn. */
|
||||
/**
|
||||
* Build a complete cache mock with explicit named exports.
|
||||
*
|
||||
* Uses concrete properties instead of a Proxy so that Bun's ESM mock
|
||||
* resolution can discover every named export at module-link time
|
||||
* (some Bun versions do not enumerate Proxy keys for static imports).
|
||||
*/
|
||||
function buildCacheMock(overrides: Record<string, any> = {}) {
|
||||
return new Proxy(overrides, {
|
||||
get(target, prop: string) {
|
||||
if (prop in target) return target[prop];
|
||||
// Key helpers return strings; everything else returns a mock fn
|
||||
if (prop.endsWith("CacheKey") || prop.endsWith("DataCacheKey"))
|
||||
return mock((...args: any[]) => `mock:${prop}:${args.join(":")}`);
|
||||
return mock(() => Promise.resolve(null));
|
||||
},
|
||||
});
|
||||
const keyFn = (...args: any[]) => `mock:key:${args.join(":")}`;
|
||||
return {
|
||||
// Key helpers
|
||||
activityCacheKey: mock(keyFn),
|
||||
companyCwCacheKey: mock(keyFn),
|
||||
notesCacheKey: mock(keyFn),
|
||||
contactsCacheKey: mock(keyFn),
|
||||
productsCacheKey: mock(keyFn),
|
||||
siteCacheKey: mock(keyFn),
|
||||
oppCwDataCacheKey: mock(keyFn),
|
||||
// Read helpers
|
||||
getCachedActivities: mock(() => Promise.resolve(null)),
|
||||
getCachedCompanyCwData: mock(() => Promise.resolve(null)),
|
||||
getCachedNotes: mock(() => Promise.resolve(null)),
|
||||
getCachedContacts: mock(() => Promise.resolve(null)),
|
||||
getCachedProducts: mock(() => Promise.resolve(null)),
|
||||
getCachedSite: mock(() => Promise.resolve(null)),
|
||||
getCachedOppCwData: mock(() => Promise.resolve(null)),
|
||||
// Write / fetch helpers
|
||||
fetchAndCacheActivities: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheCompanyCwData: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheNotes: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheContacts: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheProducts: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheSite: mock(() => Promise.resolve(null)),
|
||||
fetchAndCacheOppCwData: mock(() => Promise.resolve(null)),
|
||||
// Invalidation helpers
|
||||
invalidateNotesCache: mock(() => Promise.resolve()),
|
||||
invalidateContactsCache: mock(() => Promise.resolve()),
|
||||
invalidateProductsCache: mock(() => Promise.resolve()),
|
||||
invalidateAllOpportunityCaches: mock(() => Promise.resolve()),
|
||||
// Background refresh
|
||||
refreshOpportunityCache: mock(() => Promise.resolve()),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user