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
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: "1.3.6"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: "1.3.6"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|||||||
@@ -1,12 +1,45 @@
|
|||||||
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Top-level mocks — configured before the module is imported so the ESM
|
||||||
|
// linker always sees the mocked version, regardless of Bun's module cache.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 },
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import AFTER mocks
|
||||||
|
import {
|
||||||
|
submitTimeEntry,
|
||||||
|
syncOpportunityStatus,
|
||||||
|
} from "../../src/services/cw.opportunityService";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Tests
|
// Tests
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("cw.opportunityService", () => {
|
describe("cw.opportunityService", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mock.restore();
|
postMock.mockReset();
|
||||||
|
postMock.mockImplementation(() =>
|
||||||
|
Promise.resolve({ data: { id: 9001 } }),
|
||||||
|
);
|
||||||
|
updateMock.mockReset();
|
||||||
|
updateMock.mockImplementation(() => Promise.resolve({}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -14,27 +47,6 @@ describe("cw.opportunityService", () => {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
describe("submitTimeEntry()", () => {
|
describe("submitTimeEntry()", () => {
|
||||||
test("submits time entry and returns success", async () => {
|
test("submits time entry and returns success", 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");
|
|
||||||
const result = await submitTimeEntry({
|
const result = await submitTimeEntry({
|
||||||
activityId: 100,
|
activityId: 100,
|
||||||
cwMemberId: 10,
|
cwMemberId: 10,
|
||||||
@@ -49,25 +61,6 @@ describe("cw.opportunityService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("strips milliseconds from ISO timestamps", async () => {
|
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({
|
await submitTimeEntry({
|
||||||
activityId: 100,
|
activityId: 100,
|
||||||
cwMemberId: 10,
|
cwMemberId: 10,
|
||||||
@@ -82,26 +75,10 @@ describe("cw.opportunityService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("returns failure on API error", async () => {
|
test("returns failure on API error", async () => {
|
||||||
mock.module("../../src/constants", () => ({
|
postMock.mockImplementation(() =>
|
||||||
connectWiseApi: {
|
Promise.reject(new Error("CW down")),
|
||||||
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({})) },
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { submitTimeEntry } =
|
|
||||||
await import("../../src/services/cw.opportunityService");
|
|
||||||
const result = await submitTimeEntry({
|
const result = await submitTimeEntry({
|
||||||
activityId: 100,
|
activityId: 100,
|
||||||
cwMemberId: 10,
|
cwMemberId: 10,
|
||||||
@@ -121,25 +98,6 @@ describe("cw.opportunityService", () => {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
describe("syncOpportunityStatus()", () => {
|
describe("syncOpportunityStatus()", () => {
|
||||||
test("syncs status to CW and returns success", async () => {
|
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({
|
const result = await syncOpportunityStatus({
|
||||||
opportunityId: 1001,
|
opportunityId: 1001,
|
||||||
statusCwId: 24,
|
statusCwId: 24,
|
||||||
@@ -150,26 +108,10 @@ describe("cw.opportunityService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("returns failure on API error", async () => {
|
test("returns failure on API error", async () => {
|
||||||
mock.module("../../src/constants", () => ({
|
updateMock.mockImplementation(() =>
|
||||||
connectWiseApi: { post: mock(() => Promise.resolve({ data: {} })) },
|
Promise.reject(new Error("API fail")),
|
||||||
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"))),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { syncOpportunityStatus } =
|
|
||||||
await import("../../src/services/cw.opportunityService");
|
|
||||||
const result = await syncOpportunityStatus({
|
const result = await syncOpportunityStatus({
|
||||||
opportunityId: 1001,
|
opportunityId: 1001,
|
||||||
statusCwId: 24,
|
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> = {}) {
|
function buildCacheMock(overrides: Record<string, any> = {}) {
|
||||||
return new Proxy(overrides, {
|
const keyFn = (...args: any[]) => `mock:key:${args.join(":")}`;
|
||||||
get(target, prop: string) {
|
return {
|
||||||
if (prop in target) return target[prop];
|
// Key helpers
|
||||||
// Key helpers return strings; everything else returns a mock fn
|
activityCacheKey: mock(keyFn),
|
||||||
if (prop.endsWith("CacheKey") || prop.endsWith("DataCacheKey"))
|
companyCwCacheKey: mock(keyFn),
|
||||||
return mock((...args: any[]) => `mock:${prop}:${args.join(":")}`);
|
notesCacheKey: mock(keyFn),
|
||||||
return mock(() => Promise.resolve(null));
|
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