134 lines
4.4 KiB
TypeScript
134 lines
4.4 KiB
TypeScript
import { describe, test, expect, mock, beforeAll, beforeEach } from "bun:test";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Top-level mocks — must be registered before any import of the service
|
|
// module so the ESM linker resolves mocked dependencies.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
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.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
let submitTimeEntry: typeof import("../../src/services/cw.opportunityService")["submitTimeEntry"];
|
|
let syncOpportunityStatus: typeof import("../../src/services/cw.opportunityService")["syncOpportunityStatus"];
|
|
|
|
beforeAll(async () => {
|
|
const mod = await import("../../src/services/cw.opportunityService");
|
|
submitTimeEntry = mod.submitTimeEntry;
|
|
syncOpportunityStatus = mod.syncOpportunityStatus;
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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,
|
|
timeStart: "2026-03-01T09:00:00.000Z",
|
|
timeEnd: "2026-03-01T10:00:00.000Z",
|
|
notes: "Design review",
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.cwTimeEntryId).toBe(9001);
|
|
expect(result.message).toContain("9001");
|
|
});
|
|
|
|
test("strips milliseconds from ISO timestamps", async () => {
|
|
await submitTimeEntry({
|
|
activityId: 100,
|
|
cwMemberId: 10,
|
|
timeStart: "2026-03-01T09:00:00.123Z",
|
|
timeEnd: "2026-03-01T10:00:00.456Z",
|
|
notes: "test",
|
|
});
|
|
|
|
const body = postMock.mock.calls[0]?.[1];
|
|
expect(body.timeStart).toBe("2026-03-01T09:00:00Z");
|
|
expect(body.timeEnd).toBe("2026-03-01T10:00:00Z");
|
|
});
|
|
|
|
test("returns failure on API error", async () => {
|
|
postMock.mockImplementation(() =>
|
|
Promise.reject(new Error("CW down")),
|
|
);
|
|
|
|
const result = await submitTimeEntry({
|
|
activityId: 100,
|
|
cwMemberId: 10,
|
|
timeStart: "2026-03-01T09:00:00Z",
|
|
timeEnd: "2026-03-01T10:00:00Z",
|
|
notes: "test",
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.cwTimeEntryId).toBeNull();
|
|
expect(result.message).toContain("Failed");
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// syncOpportunityStatus
|
|
// -------------------------------------------------------------------
|
|
describe("syncOpportunityStatus()", () => {
|
|
test("syncs status to CW and returns success", async () => {
|
|
const result = await syncOpportunityStatus({
|
|
opportunityId: 1001,
|
|
statusCwId: 24,
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.message).toContain("1001");
|
|
});
|
|
|
|
test("returns failure on API error", async () => {
|
|
updateMock.mockImplementation(() =>
|
|
Promise.reject(new Error("API fail")),
|
|
);
|
|
|
|
const result = await syncOpportunityStatus({
|
|
opportunityId: 1001,
|
|
statusCwId: 24,
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain("Failed");
|
|
});
|
|
});
|
|
});
|