fix: remove nested .git folders, re-add as normal directories
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mocks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
|
||||
const updateMock = mock(() => Promise.resolve({}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const stripMs = (d: string) => d.replace(/\.\d{3}Z$/, "Z");
|
||||
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user