all the haul

This commit is contained in:
2026-04-07 23:56:31 +00:00
parent 87cce83030
commit 24f303355b
244 changed files with 33743 additions and 11249 deletions
+38 -16
View File
@@ -50,7 +50,7 @@ mock.module("../src/managers/sessions", () => ({
Promise.resolve({
accessToken: "mock-access",
refreshToken: "mock-refresh",
}),
})
),
fetch: mock(() => Promise.resolve(null)),
},
@@ -93,6 +93,14 @@ mock.module("../src/constants", () => ({
unifiPassword: "test-pass",
io: { of: mock(() => ({ on: mock() })) },
engine: {},
collectorSocket: {
connected: false,
connect: mock(),
disconnect: mock(),
on: mock(),
emit: mock(),
},
connectCollectorSocket: mock(),
}));
// ---------------------------------------------------------------------------
@@ -108,7 +116,7 @@ mock.module("../src/constants", () => ({
* never encounter "export not found" errors.
*/
export function buildMockConstants(
overrides: Record<string, any> = {},
overrides: Record<string, any> = {}
): Record<string, any> {
return {
prisma: createMockPrisma(),
@@ -142,6 +150,14 @@ export function buildMockConstants(
unifiPassword: "test-pass",
io: { of: mock(() => ({ on: mock() })) },
engine: {},
collectorSocket: {
connected: false,
connect: mock(),
disconnect: mock(),
on: mock(),
emit: mock(),
},
connectCollectorSocket: mock(),
...overrides,
};
}
@@ -152,7 +168,7 @@ export function buildMockConstants(
* never encounter "export not found" errors.
*/
export function buildMockGlobalEvents(
overrides: Record<string, any> = {},
overrides: Record<string, any> = {}
): Record<string, any> {
return {
events: {
@@ -175,7 +191,7 @@ export function createMockPrisma() {
get(_target, prop) {
return mock(() => Promise.resolve(null));
},
},
}
);
return new Proxy(
@@ -186,7 +202,7 @@ export function createMockPrisma() {
return mock(() => Promise.resolve());
return createModelProxy();
},
},
}
);
}
@@ -200,7 +216,7 @@ export function createMockUnifi() {
updateWlanConf: mock(() => Promise.resolve({})),
getNetworks: mock(() => Promise.resolve([])),
createSite: mock(() =>
Promise.resolve({ name: "default", description: "Default" }),
Promise.resolve({ name: "default", description: "Default" })
),
getWlanGroups: mock(() => Promise.resolve([])),
createWlanGroup: mock(() => Promise.resolve({})),
@@ -252,10 +268,13 @@ export function buildMockRole(overrides: Record<string, any> = {}) {
/** Build a minimal Prisma-shaped Company row. */
export function buildMockCompany(overrides: Record<string, any> = {}) {
return {
id: "company-1",
id: 123,
uid: "company-1",
name: "Test Company",
cw_Identifier: "TestCo",
cw_CompanyId: 123,
phone: "555-1234",
website: "https://test.com",
contacts: [],
companyAddresses: [],
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
...overrides,
@@ -354,16 +373,16 @@ export function buildMockUnifiSite(overrides: Record<string, any> = {}) {
/** Build a minimal Prisma-shaped Opportunity row. */
export function buildMockOpportunity(overrides: Record<string, any> = {}) {
return {
id: "opp-1",
cwOpportunityId: 1001,
uid: "opp-1",
id: 1001,
name: "Test Opportunity",
notes: "Some notes",
typeName: "New Business",
typeCwId: 1,
typeId: 1,
type: { id: 1, name: "New Business" },
stageName: "Proposal",
stageCwId: 2,
statusName: "Active",
statusCwId: 3,
statusId: 3,
status: { id: 3, name: "Active" },
priorityName: "High",
priorityCwId: 4,
ratingName: "Hot",
@@ -379,12 +398,14 @@ export function buildMockOpportunity(overrides: Record<string, any> = {}) {
secondarySalesRepCwId: null,
companyCwId: 123,
companyName: "Test Company",
companyId: "company-1",
contactCwId: 200,
contactName: "Jane Doe",
siteCwId: 300,
siteName: "Main Office",
customerPO: "PO-12345",
totalSalesTax: 50.0,
probability: 80,
locationName: "HQ",
locationCwId: 400,
departmentName: "Sales",
@@ -396,8 +417,9 @@ export function buildMockOpportunity(overrides: Record<string, any> = {}) {
closedFlag: false,
closedByName: null,
closedByCwId: null,
companyId: "company-1",
cwLastUpdated: new Date("2026-02-28"),
cwDateEntered: new Date("2026-01-01"),
productSequence: [],
createdAt: new Date("2026-01-01"),
updatedAt: new Date("2026-02-28"),
company: null,
+19 -118
View File
@@ -6,7 +6,7 @@ import { buildMockCWActivity } from "../setup";
// ---------------------------------------------------------------------------
function createStablePrismaMock(
overrides: Record<string, Record<string, any>> = {},
overrides: Record<string, Record<string, any>> = {}
) {
return new Proxy(
{},
@@ -17,7 +17,7 @@ function createStablePrismaMock(
if (overrides[model]) return overrides[model];
return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
},
},
}
);
}
@@ -34,54 +34,15 @@ describe("activities manager", () => {
// fetchItem
// -------------------------------------------------------------------
describe("fetchItem()", () => {
test("returns an ActivityController on success", async () => {
const cwData = buildMockCWActivity();
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {
fetch: mock(() => Promise.resolve(cwData)),
fetchByCompany: mock(() => Promise.resolve([])),
fetchByOpportunity: mock(() => Promise.resolve([])),
delete: mock(() => Promise.resolve()),
countItems: mock(() => Promise.resolve(0)),
update: mock(() => Promise.resolve(cwData)),
},
}));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
post: mock(() => Promise.resolve({ data: {} })),
patch: mock(() => Promise.resolve({ data: {} })),
delete: mock(() => Promise.resolve({ data: {} })),
},
}));
const { activities } = await import("../../src/managers/activities");
const result = await activities.fetchItem(5001);
expect(result).toBeDefined();
expect(result.cwActivityId).toBe(5001);
});
test("throws GenericError on failure", async () => {
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {
fetch: mock(() => Promise.reject(new Error("CW API down"))),
},
}));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
},
}));
test("throws NotAvailable error (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
try {
await activities.fetchItem(9999);
expect(true).toBe(false);
await activities.fetchItem(5001);
expect(true).toBe(false); // Should not reach here
} catch (e: any) {
expect(e.name).toBe("FetchActivityError");
expect(e.status).toBeDefined();
expect(e.name).toBe("NotAvailable");
expect(e.status).toBe(501);
expect(e.message).toContain("not yet implemented");
}
});
});
@@ -90,38 +51,16 @@ describe("activities manager", () => {
// fetchPages
// -------------------------------------------------------------------
describe("fetchPages()", () => {
test("returns array of ActivityControllers", async () => {
const cwData = [buildMockCWActivity(), buildMockCWActivity({ id: 5002 })];
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: cwData })),
post: mock(() => Promise.resolve({ data: {} })),
},
}));
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {},
}));
test("returns empty array (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
const result = await activities.fetchPages(1, 10);
expect(result).toBeArrayOfSize(2);
expect(result).toBeArrayOfSize(0);
});
test("clamps page to minimum 1", async () => {
const getMock = mock(() => Promise.resolve({ data: [] }));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: { get: getMock },
}));
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {},
}));
test("returns empty array for negative page (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
await activities.fetchPages(-5, 10);
const url = getMock.mock.calls[0]?.[0] as string;
expect(url).toContain("page=1");
const result = await activities.fetchPages(-5, 10);
expect(result).toBeArrayOfSize(0);
});
});
@@ -129,23 +68,10 @@ describe("activities manager", () => {
// fetchByCompany
// -------------------------------------------------------------------
describe("fetchByCompany()", () => {
test("returns ActivityControllers for a company", async () => {
const items = [buildMockCWActivity()];
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {
fetchByCompany: mock(() => Promise.resolve(items)),
},
}));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
},
}));
test("returns empty array (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
const result = await activities.fetchByCompany(123);
expect(result).toBeArrayOfSize(1);
expect(result).toBeArrayOfSize(0);
});
});
@@ -153,23 +79,10 @@ describe("activities manager", () => {
// fetchByOpportunity
// -------------------------------------------------------------------
describe("fetchByOpportunity()", () => {
test("returns ActivityControllers for an opportunity", async () => {
const items = [buildMockCWActivity()];
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {
fetchByOpportunity: mock(() => Promise.resolve(items)),
},
}));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
},
}));
test("returns empty array (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
const result = await activities.fetchByOpportunity(1001);
expect(result).toBeArrayOfSize(1);
expect(result).toBeArrayOfSize(0);
});
});
@@ -221,22 +134,10 @@ describe("activities manager", () => {
// count
// -------------------------------------------------------------------
describe("count()", () => {
test("returns count from activityCw", async () => {
mock.module("../../src/modules/cw-utils/activities/activities", () => ({
activityCw: {
countItems: mock(() => Promise.resolve(42)),
},
}));
mock.module("../../src/constants", () => ({
prisma: createStablePrismaMock(),
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
},
}));
test("returns 0 (not yet implemented)", async () => {
const { activities } = await import("../../src/managers/activities");
const result = await activities.count();
expect(result).toBe(42);
expect(result).toBe(0);
});
});
});
@@ -0,0 +1,129 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { buildMockCompany } from "../../setup";
const getMock: any = mock(() => Promise.resolve({ data: [] as any[] }));
mock.module("../../../src/constants", () => ({
prisma: new Proxy(
{},
{
get() {
return mock(() => Promise.resolve([]));
},
},
),
connectWiseApi: {
get: getMock,
post: mock(() => Promise.resolve({ data: {} })),
put: mock(() => Promise.resolve({ data: {} })),
patch: mock(() => Promise.resolve({ data: {} })),
delete: mock(() => Promise.resolve({ data: {} })),
},
}));
function buildConfig(id: number) {
return {
id,
name: `Config ${id}`,
activeFlag: true,
serialNumber: `SN-${id}`,
type: { id: 9, name: "Firewall" },
notes: "test notes",
status: { id: 2, name: "Active" },
questions: [
{
questionId: 11,
question: "Hostname",
answer: `host-${id}`,
fieldType: "Text",
},
],
_info: { lastUpdated: "2026-03-31T00:00:00Z" },
};
}
describe("CompanyController.fetchConfigurations", () => {
beforeEach(() => {
getMock.mockReset();
});
test("fetches and transforms company configurations from ConnectWise", async () => {
const { CompanyController } = await import(
"../../../src/controllers/CompanyController"
);
getMock.mockImplementation(() => Promise.resolve({ data: [buildConfig(1)] }));
const controller = new CompanyController(buildMockCompany({ id: 123 }) as any);
const result = await controller.fetchConfigurations();
expect(getMock).toHaveBeenCalledTimes(1);
const requestUrl = String((getMock.mock.calls[0] as any[])?.[0] ?? "");
expect(requestUrl).toContain(
"/company/configurations?page=1&pageSize=1000&conditions=company%2Fid%3D123",
);
expect(result).toHaveLength(1);
const first = result[0] as any;
expect(first.id).toBe(1);
expect(first.name).toBe("Config 1");
expect(first.active).toBe(true);
expect(first.serialNumber).toBe("SN-1");
expect(first.type?.id).toBe(9);
expect(first.status?.name).toBe("Active");
expect(first.questions?.[0]?.question).toBe("Hostname");
});
test("paginates when page is full", async () => {
const { CompanyController } = await import(
"../../../src/controllers/CompanyController"
);
const firstPage = Array.from({ length: 1000 }, (_, i) => buildConfig(i + 1));
const secondPage = [buildConfig(1001)];
getMock
.mockImplementationOnce(() => Promise.resolve({ data: firstPage }))
.mockImplementationOnce(() => Promise.resolve({ data: secondPage }));
const controller = new CompanyController(buildMockCompany({ id: 456 }) as any);
const result = await controller.fetchConfigurations();
expect(getMock).toHaveBeenCalledTimes(2);
const firstUrl = String((getMock.mock.calls[0] as any[])?.[0] ?? "");
const secondUrl = String((getMock.mock.calls[1] as any[])?.[0] ?? "");
expect(firstUrl).toContain("page=1");
expect(secondUrl).toContain("page=2");
expect(result).toHaveLength(1001);
});
test("throws GenericError when CW request fails", async () => {
const { CompanyController } = await import(
"../../../src/controllers/CompanyController"
);
const err: any = new Error("Unauthorized");
err.isAxiosError = true;
err.response = {
status: 401,
data: { message: "Unauthorized" },
statusText: "Unauthorized",
};
getMock.mockImplementation(() => Promise.reject(err));
const controller = new CompanyController(buildMockCompany({ id: 321 }) as any);
try {
await controller.fetchConfigurations();
expect(true).toBe(false);
} catch (error: any) {
expect(error.name).toBe("ConnectWiseFetchFailed");
expect(error.status).toBe(401);
expect(error.message).toBe(
"Failed to fetch company configurations from ConnectWise",
);
expect(error.cause).toBe("Unauthorized");
}
});
});
@@ -72,83 +72,26 @@ describe("CwMemberController", () => {
test("returns trimmed name when lastName is empty", () => {
const ctrl = new CwMemberController(
buildMockCwMember({ lastName: "" }) as any,
buildMockCwMember({ lastName: "" }) as any
);
expect(ctrl.fullName).toBe("John");
});
test("returns trimmed name when firstName is empty", () => {
const ctrl = new CwMemberController(
buildMockCwMember({ firstName: "" }) as any,
buildMockCwMember({ firstName: "" }) as any
);
expect(ctrl.fullName).toBe("Doe");
});
test("falls back to identifier when both names are empty", () => {
const ctrl = new CwMemberController(
buildMockCwMember({ firstName: "", lastName: "" }) as any,
buildMockCwMember({ firstName: "", lastName: "" }) as any
);
expect(ctrl.fullName).toBe("jdoe");
});
});
// -----------------------------------------------------------------
// mapCwToDb (static)
// -----------------------------------------------------------------
describe("mapCwToDb", () => {
test("maps CW member fields to DB schema", () => {
const cwItem = {
identifier: "jdoe",
firstName: "John",
lastName: "Doe",
officeEmail: "jdoe@example.com",
inactiveFlag: false,
_info: { lastUpdated: "2026-02-01T12:00:00Z" },
};
const result = CwMemberController.mapCwToDb(cwItem as any);
expect(result.identifier).toBe("jdoe");
expect(result.firstName).toBe("John");
expect(result.lastName).toBe("Doe");
expect(result.officeEmail).toBe("jdoe@example.com");
expect(result.inactiveFlag).toBe(false);
expect(result.cwLastUpdated).toEqual(new Date("2026-02-01T12:00:00Z"));
});
test("handles null/missing fields with defaults", () => {
const cwItem = {
identifier: "empty",
firstName: null,
lastName: null,
officeEmail: null,
inactiveFlag: null,
_info: null,
};
const result = CwMemberController.mapCwToDb(cwItem as any);
expect(result.firstName).toBe("");
expect(result.lastName).toBe("");
expect(result.officeEmail).toBeNull();
expect(result.inactiveFlag).toBe(false);
expect(result.cwLastUpdated).toBeInstanceOf(Date);
});
test("handles undefined _info.lastUpdated", () => {
const cwItem = {
identifier: "test",
firstName: "A",
lastName: "B",
officeEmail: null,
inactiveFlag: false,
_info: {},
};
const result = CwMemberController.mapCwToDb(cwItem as any);
// Without lastUpdated, falls through to new Date()
expect(result.cwLastUpdated).toBeInstanceOf(Date);
});
});
// -----------------------------------------------------------------
// toJson
// -----------------------------------------------------------------
@@ -173,7 +116,7 @@ describe("CwMemberController", () => {
test("includes fullName in JSON", () => {
const ctrl = new CwMemberController(
buildMockCwMember({ firstName: "", lastName: "" }) as any,
buildMockCwMember({ firstName: "", lastName: "" }) as any
);
expect(ctrl.toJson().fullName).toBe("jdoe");
});
@@ -164,10 +164,9 @@ describe("OpportunityController", () => {
test("maps type, stage, status references", () => {
const result = OpportunityController.mapCwToDb(cwOpportunity);
expect(result.typeName).toBe("New Business");
expect(result.typeCwId).toBe(1);
expect(result.stageName).toBe("Proposal");
expect(result.statusName).toBe("Active");
expect(result.typeId).toBe(1);
expect(result.stageId).toBe(2);
expect(result.statusId).toBe(3);
});
test("maps null references to null", () => {
+101 -13
View File
@@ -6,7 +6,7 @@ import { buildMockCatalogItem, buildMockConstants } from "../setup";
// ---------------------------------------------------------------------------
function createStablePrismaMock(
overrides: Record<string, Record<string, any>> = {},
overrides: Record<string, Record<string, any>> = {}
) {
return new Proxy(
{},
@@ -17,7 +17,7 @@ function createStablePrismaMock(
if (overrides[model]) return overrides[model];
return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
},
},
}
);
}
@@ -43,7 +43,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(mockData)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -60,7 +60,7 @@ describe("procurement manager", () => {
prisma: createStablePrismaMock({
catalogItem: { findFirst },
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -77,7 +77,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -108,7 +108,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -126,7 +126,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -134,6 +134,48 @@ describe("procurement manager", () => {
const opts = findMany.mock.calls[0]?.[0];
expect(opts.skip).toBe(0); // (max(0,1)-1) * 10 = 0
});
test("falls back to safe pagination when page/rpp are invalid", async () => {
const findMany = mock(() => Promise.resolve([]));
mock.module("../../src/constants", () =>
buildMockConstants({
prisma: createStablePrismaMock({
catalogItem: {
findMany,
findFirst: mock(() => Promise.resolve(null)),
},
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
await procurement.fetchPages(Number.NaN, Number.NaN);
const opts = findMany.mock.calls[0]?.[0];
expect(opts.skip).toBe(0);
expect(opts.take).toBe(30);
});
test("uses relational manufacturer filter when manufacturer option is provided", async () => {
const findMany = mock(() => Promise.resolve([]));
mock.module("../../src/constants", () =>
buildMockConstants({
prisma: createStablePrismaMock({
catalogItem: {
findMany,
findFirst: mock(() => Promise.resolve(null)),
},
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
await procurement.fetchPages(1, 20, { manufacturer: "Ubiquiti" });
const where = findMany.mock.calls[0]?.[0]?.where;
expect(JSON.stringify(where)).toContain(
'"manufacturer":{"is":{"name":{"contains":"Ubiquiti"'
);
});
});
// -------------------------------------------------------------------
@@ -150,13 +192,59 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
const result = await procurement.search("switch", 1, 10);
expect(result).toBeArrayOfSize(1);
});
test("ignores invalid numeric filters instead of passing NaN to Prisma", async () => {
const findMany = mock(() => Promise.resolve([]));
mock.module("../../src/constants", () =>
buildMockConstants({
prisma: createStablePrismaMock({
catalogItem: {
findMany,
findFirst: mock(() => Promise.resolve(null)),
},
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
await procurement.search("switch", Number.NaN, Number.NaN, {
minPrice: Number.NaN,
maxPrice: Number.NaN,
});
const where = findMany.mock.calls[0]?.[0]?.where;
expect(where).toHaveProperty("OR");
expect(JSON.stringify(where)).not.toContain("NaN");
});
test("uses relational manufacturer search clause", async () => {
const findMany = mock(() => Promise.resolve([]));
mock.module("../../src/constants", () =>
buildMockConstants({
prisma: createStablePrismaMock({
catalogItem: {
findMany,
findFirst: mock(() => Promise.resolve(null)),
},
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
await procurement.search("wir", 1, 20);
const where = findMany.mock.calls[0]?.[0]?.where;
expect(JSON.stringify(where)).toContain(
'"manufacturer":{"is":{"name":{"contains":"wir"'
);
});
});
// -------------------------------------------------------------------
@@ -172,7 +260,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -194,7 +282,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -217,7 +305,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -235,7 +323,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");
@@ -256,7 +344,7 @@ describe("procurement manager", () => {
findFirst: mock(() => Promise.resolve(null)),
},
}),
}),
})
);
const { procurement } = await import("../../src/managers/procurement");