fix: remove nested .git folders, re-add as normal directories
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { ActivityController } from "../../../src/controllers/ActivityController";
|
||||
import { buildMockCWActivity } from "../../setup";
|
||||
|
||||
describe("ActivityController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets all public properties from CW data", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.cwActivityId).toBe(5001);
|
||||
expect(ctrl.name).toBe("Test Activity");
|
||||
expect(ctrl.notes).toBe("Activity notes");
|
||||
});
|
||||
|
||||
test("maps type reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.typeCwId).toBe(1);
|
||||
expect(ctrl.typeName).toBe("Call");
|
||||
});
|
||||
|
||||
test("maps status reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.statusCwId).toBe(2);
|
||||
expect(ctrl.statusName).toBe("Open");
|
||||
});
|
||||
|
||||
test("maps company reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.companyCwId).toBe(123);
|
||||
expect(ctrl.companyName).toBe("Test Company");
|
||||
expect(ctrl.companyIdentifier).toBe("TestCo");
|
||||
});
|
||||
|
||||
test("maps contact reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.contactCwId).toBe(200);
|
||||
expect(ctrl.contactName).toBe("Jane Doe");
|
||||
});
|
||||
|
||||
test("maps opportunity reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.opportunityCwId).toBe(1001);
|
||||
expect(ctrl.opportunityName).toBe("Test Opportunity");
|
||||
});
|
||||
|
||||
test("maps assignTo reference", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.assignToCwId).toBe(10);
|
||||
expect(ctrl.assignToName).toBe("John Roberts");
|
||||
expect(ctrl.assignToIdentifier).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("maps dates correctly", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.dateStart).toBeInstanceOf(Date);
|
||||
expect(ctrl.dateEnd).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("maps _info dates", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.cwLastUpdated).toBeInstanceOf(Date);
|
||||
expect(ctrl.cwDateEntered).toBeInstanceOf(Date);
|
||||
expect(ctrl.cwEnteredBy).toBe("jroberts");
|
||||
expect(ctrl.cwUpdatedBy).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("handles null optional fields gracefully", () => {
|
||||
const ctrl = new ActivityController(
|
||||
buildMockCWActivity({
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
company: undefined,
|
||||
contact: undefined,
|
||||
opportunity: undefined,
|
||||
assignTo: undefined,
|
||||
dateStart: undefined,
|
||||
dateEnd: undefined,
|
||||
notes: undefined,
|
||||
_info: {},
|
||||
}),
|
||||
);
|
||||
expect(ctrl.typeCwId).toBeNull();
|
||||
expect(ctrl.typeName).toBeNull();
|
||||
expect(ctrl.statusCwId).toBeNull();
|
||||
expect(ctrl.companyCwId).toBeNull();
|
||||
expect(ctrl.contactCwId).toBeNull();
|
||||
expect(ctrl.opportunityCwId).toBeNull();
|
||||
expect(ctrl.assignToCwId).toBeNull();
|
||||
expect(ctrl.dateStart).toBeNull();
|
||||
expect(ctrl.dateEnd).toBeNull();
|
||||
expect(ctrl.notes).toBeNull();
|
||||
expect(ctrl.cwLastUpdated).toBeNull();
|
||||
});
|
||||
|
||||
test("maps phoneNumber and email", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.phoneNumber).toBe("555-1234");
|
||||
expect(ctrl.email).toBe("jane@test.com");
|
||||
});
|
||||
|
||||
test("maps notifyFlag", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.notifyFlag).toBe(false);
|
||||
});
|
||||
|
||||
test("maps customFields", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.customFields).toEqual([]);
|
||||
});
|
||||
|
||||
test("maps mobileGuid", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
expect(ctrl.mobileGuid).toBe("guid-abc123");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns cwActivityId", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.cwActivityId).toBe(5001);
|
||||
});
|
||||
|
||||
test("returns name and notes", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.name).toBe("Test Activity");
|
||||
expect(json.notes).toBe("Activity notes");
|
||||
});
|
||||
|
||||
test("formats type as reference object", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.type).toEqual({ id: 1, name: "Call" });
|
||||
});
|
||||
|
||||
test("type is null when no type set", () => {
|
||||
const ctrl = new ActivityController(
|
||||
buildMockCWActivity({ type: undefined }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.type).toBeNull();
|
||||
});
|
||||
|
||||
test("formats company as reference object with identifier", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.company).toEqual({
|
||||
id: 123,
|
||||
identifier: "TestCo",
|
||||
name: "Test Company",
|
||||
});
|
||||
});
|
||||
|
||||
test("formats assignTo as reference object with identifier", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.assignTo).toEqual({
|
||||
id: 10,
|
||||
identifier: "jroberts",
|
||||
name: "John Roberts",
|
||||
});
|
||||
});
|
||||
|
||||
test("formats opportunity as reference object", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.opportunity).toEqual({
|
||||
id: 1001,
|
||||
name: "Test Opportunity",
|
||||
});
|
||||
});
|
||||
|
||||
test("includes dates and meta", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.dateStart).toBeInstanceOf(Date);
|
||||
expect(json.dateEnd).toBeInstanceOf(Date);
|
||||
expect(json.cwLastUpdated).toBeInstanceOf(Date);
|
||||
expect(json.cwDateEntered).toBeInstanceOf(Date);
|
||||
expect(json.cwEnteredBy).toBe("jroberts");
|
||||
expect(json.cwUpdatedBy).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("includes customFields array", () => {
|
||||
const ctrl = new ActivityController(buildMockCWActivity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.customFields).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { CatalogItemController } from "../../../src/controllers/CatalogItemController";
|
||||
import { buildMockCatalogItem } from "../../setup";
|
||||
|
||||
describe("CatalogItemController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets core identification fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.id).toBe("cat-1");
|
||||
expect(ctrl.cwCatalogId).toBe(500);
|
||||
expect(ctrl.identifier).toBe("USW-Pro-24");
|
||||
});
|
||||
|
||||
test("sets name and description fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.name).toBe("UniFi Switch Pro 24");
|
||||
expect(ctrl.description).toBe("24-port managed switch");
|
||||
expect(ctrl.customerDescription).toBe("Enterprise switch");
|
||||
expect(ctrl.internalNotes).toBeNull();
|
||||
});
|
||||
|
||||
test("sets category and subcategory fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.category).toBe("Technology");
|
||||
expect(ctrl.categoryCwId).toBe(18);
|
||||
expect(ctrl.subcategory).toBe("Network-Switch");
|
||||
expect(ctrl.subcategoryCwId).toBe(112);
|
||||
});
|
||||
|
||||
test("sets manufacturer fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.manufacturer).toBe("Ubiquiti");
|
||||
expect(ctrl.manufactureCwId).toBe(248);
|
||||
expect(ctrl.partNumber).toBe("USW-Pro-24");
|
||||
});
|
||||
|
||||
test("sets vendor fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.vendorName).toBe("Ubiquiti Inc");
|
||||
expect(ctrl.vendorSku).toBe("USW-Pro-24");
|
||||
expect(ctrl.vendorCwId).toBe(100);
|
||||
});
|
||||
|
||||
test("sets financial fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.price).toBe(500.0);
|
||||
expect(ctrl.cost).toBe(360.0);
|
||||
});
|
||||
|
||||
test("sets boolean flags", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.inactive).toBe(false);
|
||||
expect(ctrl.salesTaxable).toBe(true);
|
||||
});
|
||||
|
||||
test("sets inventory fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.onHand).toBe(10);
|
||||
});
|
||||
|
||||
test("sets timestamps", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.cwLastUpdated).toBeInstanceOf(Date);
|
||||
expect(ctrl.createdAt).toBeInstanceOf(Date);
|
||||
expect(ctrl.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("builds linked items recursively", () => {
|
||||
const linked = buildMockCatalogItem({
|
||||
id: "cat-2",
|
||||
name: "Linked Item",
|
||||
linkedItems: undefined,
|
||||
});
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: [linked] }),
|
||||
);
|
||||
const items = ctrl.getLinkedItems();
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].id).toBe("cat-2");
|
||||
expect(items[0]).toBeInstanceOf(CatalogItemController);
|
||||
});
|
||||
|
||||
test("defaults to empty linked items when undefined", () => {
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: undefined }),
|
||||
);
|
||||
expect(ctrl.getLinkedItems()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("handles null optional fields", () => {
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({
|
||||
description: null,
|
||||
customerDescription: null,
|
||||
identifier: null,
|
||||
category: null,
|
||||
categoryCwId: null,
|
||||
subcategory: null,
|
||||
subcategoryCwId: null,
|
||||
manufacturer: null,
|
||||
manufactureCwId: null,
|
||||
partNumber: null,
|
||||
vendorName: null,
|
||||
vendorSku: null,
|
||||
vendorCwId: null,
|
||||
cwLastUpdated: null,
|
||||
}),
|
||||
);
|
||||
expect(ctrl.description).toBeNull();
|
||||
expect(ctrl.customerDescription).toBeNull();
|
||||
expect(ctrl.identifier).toBeNull();
|
||||
expect(ctrl.category).toBeNull();
|
||||
expect(ctrl.manufacturer).toBeNull();
|
||||
expect(ctrl.cwLastUpdated).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getLinkedItems
|
||||
// -------------------------------------------------------------------
|
||||
describe("getLinkedItems()", () => {
|
||||
test("returns empty array when no linked items", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
expect(ctrl.getLinkedItems()).toEqual([]);
|
||||
});
|
||||
|
||||
test("returns array of CatalogItemController instances", () => {
|
||||
const linked1 = buildMockCatalogItem({ id: "cat-2", name: "Item 2" });
|
||||
const linked2 = buildMockCatalogItem({ id: "cat-3", name: "Item 3" });
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: [linked1, linked2] }),
|
||||
);
|
||||
const items = ctrl.getLinkedItems();
|
||||
expect(items).toHaveLength(2);
|
||||
expect(items[0].name).toBe("Item 2");
|
||||
expect(items[1].name).toBe("Item 3");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns all core fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("cat-1");
|
||||
expect(json.cwCatalogId).toBe(500);
|
||||
expect(json.identifier).toBe("USW-Pro-24");
|
||||
expect(json.name).toBe("UniFi Switch Pro 24");
|
||||
expect(json.description).toBe("24-port managed switch");
|
||||
expect(json.customerDescription).toBe("Enterprise switch");
|
||||
expect(json.internalNotes).toBeNull();
|
||||
});
|
||||
|
||||
test("returns classification fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.category).toBe("Technology");
|
||||
expect(json.categoryCwId).toBe(18);
|
||||
expect(json.subcategory).toBe("Network-Switch");
|
||||
expect(json.subcategoryCwId).toBe(112);
|
||||
expect(json.manufacturer).toBe("Ubiquiti");
|
||||
expect(json.manufactureCwId).toBe(248);
|
||||
expect(json.partNumber).toBe("USW-Pro-24");
|
||||
});
|
||||
|
||||
test("returns financial fields", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.price).toBe(500.0);
|
||||
expect(json.cost).toBe(360.0);
|
||||
});
|
||||
|
||||
test("returns boolean flags", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.inactive).toBe(false);
|
||||
expect(json.salesTaxable).toBe(true);
|
||||
});
|
||||
|
||||
test("returns timestamps", () => {
|
||||
const ctrl = new CatalogItemController(buildMockCatalogItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.createdAt).toBeInstanceOf(Date);
|
||||
expect(json.updatedAt).toBeInstanceOf(Date);
|
||||
expect(json.cwLastUpdated).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("excludes linkedItems when includeLinkedItems not set", () => {
|
||||
const linked = buildMockCatalogItem({ id: "cat-2" });
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: [linked] }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.linkedItems).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes linkedItems when includeLinkedItems is true", () => {
|
||||
const linked = buildMockCatalogItem({ id: "cat-2", name: "Linked" });
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: [linked] }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeLinkedItems: true });
|
||||
expect(json.linkedItems).toHaveLength(1);
|
||||
expect(json.linkedItems[0].id).toBe("cat-2");
|
||||
expect(json.linkedItems[0].name).toBe("Linked");
|
||||
});
|
||||
|
||||
test("linked items toJson does not recursively include their linked items", () => {
|
||||
const linked = buildMockCatalogItem({ id: "cat-2" });
|
||||
const ctrl = new CatalogItemController(
|
||||
buildMockCatalogItem({ linkedItems: [linked] }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeLinkedItems: true });
|
||||
// Nested linked items called without opts, so linkedItems is undefined
|
||||
expect(json.linkedItems[0].linkedItems).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { CompanyController } from "../../../src/controllers/CompanyController";
|
||||
import { buildMockCompany } from "../../setup";
|
||||
|
||||
const mockCwData = {
|
||||
company: {
|
||||
addressLine1: "123 Main St",
|
||||
addressLine2: null,
|
||||
city: "Springfield",
|
||||
state: "IL",
|
||||
zip: "62701",
|
||||
country: { name: "United States" },
|
||||
_info: { contacts_href: "" },
|
||||
} as any,
|
||||
defaultContact: {
|
||||
id: 100,
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
inactiveFlag: false,
|
||||
title: "CEO",
|
||||
defaultPhoneNbr: "555-1234",
|
||||
communicationItems: [
|
||||
{ type: { name: "Email" }, value: "john@test.com" },
|
||||
{ type: { name: "Phone" }, value: "555-1234" },
|
||||
],
|
||||
} as any,
|
||||
allContacts: [] as any[],
|
||||
};
|
||||
|
||||
describe("CompanyController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets public properties from company data", () => {
|
||||
const data = buildMockCompany();
|
||||
const ctrl = new CompanyController(data);
|
||||
expect(ctrl.id).toBe("company-1");
|
||||
expect(ctrl.name).toBe("Test Company");
|
||||
expect(ctrl.cw_Identifier).toBe("TestCo");
|
||||
expect(ctrl.cw_CompanyId).toBe(123);
|
||||
});
|
||||
|
||||
test("accepts optional CW data", () => {
|
||||
const data = buildMockCompany();
|
||||
const ctrl = new CompanyController(data, mockCwData);
|
||||
expect(ctrl.cw_Data).toBeDefined();
|
||||
expect(ctrl.cw_Data?.company.city).toBe("Springfield");
|
||||
});
|
||||
|
||||
test("cw_Data is undefined when not provided", () => {
|
||||
const data = buildMockCompany();
|
||||
const ctrl = new CompanyController(data);
|
||||
expect(ctrl.cw_Data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns base fields without options", () => {
|
||||
const ctrl = new CompanyController(buildMockCompany(), mockCwData);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("company-1");
|
||||
expect(json.name).toBe("Test Company");
|
||||
expect(json.cw_Identifier).toBe("TestCo");
|
||||
expect(json.cw_CompanyId).toBe(123);
|
||||
});
|
||||
|
||||
test("excludes address when includeAddress is false", () => {
|
||||
const ctrl = new CompanyController(buildMockCompany(), mockCwData);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: false,
|
||||
});
|
||||
expect(json.cw_Data.address).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes address when includeAddress is true", () => {
|
||||
const ctrl = new CompanyController(buildMockCompany(), mockCwData);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: true,
|
||||
includePrimaryContact: false,
|
||||
});
|
||||
expect(json.cw_Data.address).toBeDefined();
|
||||
expect(json.cw_Data.address!.city).toBe("Springfield");
|
||||
expect(json.cw_Data.address!.state).toBe("IL");
|
||||
});
|
||||
|
||||
test("includes primary contact when includePrimaryContact is true", () => {
|
||||
const ctrl = new CompanyController(buildMockCompany(), mockCwData);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: true,
|
||||
});
|
||||
expect(json.cw_Data.primaryContact).toBeDefined();
|
||||
expect(json.cw_Data.primaryContact!.firstName).toBe("John");
|
||||
expect(json.cw_Data.primaryContact!.lastName).toBe("Doe");
|
||||
expect(json.cw_Data.primaryContact!.email).toBe("john@test.com");
|
||||
});
|
||||
|
||||
test("excludes primary contact when includePrimaryContact is false", () => {
|
||||
const ctrl = new CompanyController(buildMockCompany(), mockCwData);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: false,
|
||||
});
|
||||
expect(json.cw_Data.primaryContact).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes allContacts when includeAllContacts is true", () => {
|
||||
const cwDataWithContacts = {
|
||||
...mockCwData,
|
||||
allContacts: [
|
||||
{
|
||||
id: 200,
|
||||
firstName: "Jane",
|
||||
lastName: "Smith",
|
||||
inactiveFlag: false,
|
||||
title: "CTO",
|
||||
defaultPhoneNbr: "555-5678",
|
||||
communicationItems: [
|
||||
{ type: { name: "Email" }, value: "jane@test.com" },
|
||||
],
|
||||
} as any,
|
||||
],
|
||||
};
|
||||
const ctrl = new CompanyController(
|
||||
buildMockCompany(),
|
||||
cwDataWithContacts,
|
||||
);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: false,
|
||||
includeAllContacts: true,
|
||||
});
|
||||
expect(json.cw_Data.allContacts).toBeDefined();
|
||||
expect(json.cw_Data.allContacts).toHaveLength(1);
|
||||
expect(json.cw_Data.allContacts![0]!.firstName).toBe("Jane");
|
||||
});
|
||||
|
||||
test("email is null when no Email communication item", () => {
|
||||
const noEmailCw = {
|
||||
...mockCwData,
|
||||
defaultContact: {
|
||||
...mockCwData.defaultContact,
|
||||
communicationItems: [{ type: { name: "Phone" }, value: "555" }],
|
||||
},
|
||||
};
|
||||
const ctrl = new CompanyController(buildMockCompany(), noEmailCw);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: true,
|
||||
});
|
||||
expect(json.cw_Data.primaryContact!.email).toBeNull();
|
||||
});
|
||||
|
||||
test("email is null when communicationItems is missing", () => {
|
||||
const noCIData = {
|
||||
...mockCwData,
|
||||
defaultContact: {
|
||||
...mockCwData.defaultContact,
|
||||
communicationItems: undefined,
|
||||
},
|
||||
};
|
||||
const ctrl = new CompanyController(buildMockCompany(), noCIData);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: false,
|
||||
includePrimaryContact: true,
|
||||
});
|
||||
expect(json.cw_Data.primaryContact!.email).toBeNull();
|
||||
});
|
||||
|
||||
test("country defaults to United States when null", () => {
|
||||
const noCntry = {
|
||||
...mockCwData,
|
||||
company: { ...mockCwData.company, country: null },
|
||||
};
|
||||
const ctrl = new CompanyController(buildMockCompany(), noCntry);
|
||||
const json = ctrl.toJson({
|
||||
includeAddress: true,
|
||||
includePrimaryContact: false,
|
||||
});
|
||||
expect(json.cw_Data.address!.country).toBe("United States");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { CredentialController } from "../../../src/controllers/CredentialController";
|
||||
import { buildMockCredential } from "../../setup";
|
||||
import { ValueType } from "../../../src/modules/credentials/credentialTypeDefs";
|
||||
|
||||
describe("CredentialController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor & _buildFields
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets public properties from credential data", () => {
|
||||
const data = buildMockCredential();
|
||||
const ctrl = new CredentialController(data);
|
||||
expect(ctrl.id).toBe("cred-1");
|
||||
expect(ctrl.name).toBe("Test Credential");
|
||||
expect(ctrl.notes).toBeNull();
|
||||
expect(ctrl.typeId).toBe("ctype-1");
|
||||
expect(ctrl.companyId).toBe("company-1");
|
||||
expect(ctrl.subCredentialOfId).toBeNull();
|
||||
});
|
||||
|
||||
test("builds fields from type definition", () => {
|
||||
const data = buildMockCredential();
|
||||
const ctrl = new CredentialController(data);
|
||||
expect(Array.isArray(ctrl.fields)).toBe(true);
|
||||
expect(ctrl.fields).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("plain fields have value from raw data", () => {
|
||||
const data = buildMockCredential();
|
||||
const ctrl = new CredentialController(data);
|
||||
const usernameField = ctrl.fields.find((f: any) => f.id === "username");
|
||||
expect(usernameField).toBeDefined();
|
||||
expect(usernameField.value).toBe("admin");
|
||||
expect(usernameField.secure).toBe(false);
|
||||
});
|
||||
|
||||
test("secure fields reference secure value ID", () => {
|
||||
const data = buildMockCredential();
|
||||
const ctrl = new CredentialController(data);
|
||||
const passwordField = ctrl.fields.find((f: any) => f.id === "password");
|
||||
expect(passwordField).toBeDefined();
|
||||
expect(passwordField.secure).toBe(true);
|
||||
expect(passwordField.value).toBe("secure-sv-1");
|
||||
});
|
||||
|
||||
test("handles sub-credentials in constructor", () => {
|
||||
const subCred = buildMockCredential({
|
||||
id: "sub-cred-1",
|
||||
name: "Sub Cred",
|
||||
subCredentialOfId: "cred-1",
|
||||
type: {
|
||||
id: "ctype-1",
|
||||
name: "Login Credential",
|
||||
permissionScope: "credential.login",
|
||||
icon: null,
|
||||
fields: [
|
||||
{
|
||||
id: "username",
|
||||
name: "Username",
|
||||
required: true,
|
||||
secure: false,
|
||||
valueType: "plain_text",
|
||||
},
|
||||
],
|
||||
},
|
||||
securevalues: [],
|
||||
subCredentials: [],
|
||||
});
|
||||
const parent = buildMockCredential({
|
||||
subCredentials: [subCred],
|
||||
});
|
||||
const ctrl = new CredentialController(parent);
|
||||
// The parent should have the sub-credential processed
|
||||
expect(ctrl.id).toBe("cred-1");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getType / getCompany
|
||||
// -------------------------------------------------------------------
|
||||
describe("getType() / getCompany()", () => {
|
||||
test("getType returns the credential type", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const type = ctrl.getType();
|
||||
expect(type.id).toBe("ctype-1");
|
||||
expect(type.name).toBe("Login Credential");
|
||||
});
|
||||
|
||||
test("getCompany returns the company", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const company = ctrl.getCompany();
|
||||
expect(company.id).toBe("company-1");
|
||||
expect(company.name).toBe("Test Company");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns structured JSON without secure field IDs by default", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("cred-1");
|
||||
expect(json.name).toBe("Test Credential");
|
||||
expect(json.typeId).toBe("ctype-1");
|
||||
expect(json.companyId).toBe("company-1");
|
||||
expect(json.type.id).toBe("ctype-1");
|
||||
expect(json.company.id).toBe("company-1");
|
||||
expect(json.secureFieldIds).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes secure field IDs when includeSecureValues is true", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const json = ctrl.toJson({ includeSecureValues: true });
|
||||
expect(json.secureFieldIds).toBeDefined();
|
||||
expect(json.secureFieldIds).toContain("password");
|
||||
});
|
||||
|
||||
test("includes subCredentialOfId when present", () => {
|
||||
const data = buildMockCredential({ subCredentialOfId: "parent-1" });
|
||||
const ctrl = new CredentialController(data);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.subCredentialOfId).toBe("parent-1");
|
||||
});
|
||||
|
||||
test("excludes subCredentialOfId when null", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.subCredentialOfId).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes timestamp fields", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.createdAt).toBeDefined();
|
||||
expect(json.updatedAt).toBeDefined();
|
||||
});
|
||||
|
||||
test("subCredentials is undefined when empty", () => {
|
||||
const ctrl = new CredentialController(buildMockCredential());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.subCredentials).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Sub-credential field building
|
||||
// -------------------------------------------------------------------
|
||||
describe("sub-credential field building", () => {
|
||||
test("builds fields differently for sub-credentials", () => {
|
||||
const subData = buildMockCredential({
|
||||
id: "sub-1",
|
||||
subCredentialOfId: "parent-1",
|
||||
fields: { sub_user: "jdoe" },
|
||||
type: {
|
||||
id: "ctype-1",
|
||||
name: "Login",
|
||||
permissionScope: "credential.login",
|
||||
icon: null,
|
||||
fields: [
|
||||
{
|
||||
id: "sub_user",
|
||||
name: "Sub User",
|
||||
required: true,
|
||||
secure: false,
|
||||
valueType: ValueType.PLAIN_TEXT,
|
||||
},
|
||||
],
|
||||
},
|
||||
securevalues: [
|
||||
{
|
||||
id: "sv-2",
|
||||
name: "sub_pass",
|
||||
content: "enc",
|
||||
hash: "hash",
|
||||
credentialId: "sub-1",
|
||||
createdAt: new Date("2025-01-01"),
|
||||
updatedAt: new Date("2025-01-01"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const ctrl = new CredentialController(subData);
|
||||
// Sub-credential fields are built as array with id/value/secure
|
||||
expect(Array.isArray(ctrl.fields)).toBe(true);
|
||||
const plainField = ctrl.fields.find((f: any) => f.id === "sub_user");
|
||||
expect(plainField).toBeDefined();
|
||||
expect(plainField.secure).toBe(false);
|
||||
const secureField = ctrl.fields.find((f: any) => f.id === "sub_pass");
|
||||
expect(secureField).toBeDefined();
|
||||
expect(secureField.secure).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { CredentialTypeController } from "../../../src/controllers/CredentialTypeController";
|
||||
import { buildMockCredentialType } from "../../setup";
|
||||
import { ValueType } from "../../../src/modules/credentials/credentialTypeDefs";
|
||||
|
||||
describe("CredentialTypeController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets public properties", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
expect(ctrl.id).toBe("ctype-1");
|
||||
expect(ctrl.name).toBe("Login Credential");
|
||||
expect(ctrl.permissionScope).toBe("credential.login");
|
||||
expect(ctrl.icon).toBeNull();
|
||||
expect(ctrl.fields).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("parses timestamps", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
expect(ctrl.createdAt).toBeInstanceOf(Date);
|
||||
expect(ctrl.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getFieldDefinition
|
||||
// -------------------------------------------------------------------
|
||||
describe("getFieldDefinition()", () => {
|
||||
test("returns matching field", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
const field = ctrl.getFieldDefinition("username");
|
||||
expect(field).toBeDefined();
|
||||
expect(field!.name).toBe("Username");
|
||||
});
|
||||
|
||||
test("returns undefined for unknown field", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
expect(ctrl.getFieldDefinition("nonexistent")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getRequiredFields
|
||||
// -------------------------------------------------------------------
|
||||
describe("getRequiredFields()", () => {
|
||||
test("returns only required fields", () => {
|
||||
const data = buildMockCredentialType({
|
||||
fields: [
|
||||
{
|
||||
id: "a",
|
||||
name: "A",
|
||||
required: true,
|
||||
secure: false,
|
||||
valueType: ValueType.PLAIN_TEXT,
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
name: "B",
|
||||
required: false,
|
||||
secure: false,
|
||||
valueType: ValueType.PLAIN_TEXT,
|
||||
},
|
||||
{
|
||||
id: "c",
|
||||
name: "C",
|
||||
required: true,
|
||||
secure: true,
|
||||
valueType: ValueType.PASSWORD,
|
||||
},
|
||||
],
|
||||
});
|
||||
const ctrl = new CredentialTypeController(data);
|
||||
const required = ctrl.getRequiredFields();
|
||||
expect(required).toHaveLength(2);
|
||||
expect(required.map((f) => f.id)).toEqual(["a", "c"]);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getSecureFields
|
||||
// -------------------------------------------------------------------
|
||||
describe("getSecureFields()", () => {
|
||||
test("returns only secure fields", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
const secure = ctrl.getSecureFields();
|
||||
expect(secure).toHaveLength(1);
|
||||
expect(secure[0]!.id).toBe("password");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// countCredentials
|
||||
// -------------------------------------------------------------------
|
||||
describe("countCredentials()", () => {
|
||||
test("returns 0 when no credentials", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
expect(ctrl.countCredentials()).toBe(0);
|
||||
});
|
||||
|
||||
test("returns correct count", () => {
|
||||
const data = buildMockCredentialType({
|
||||
credentials: [{ id: "c1" }, { id: "c2" }, { id: "c3" }],
|
||||
});
|
||||
const ctrl = new CredentialTypeController(data);
|
||||
expect(ctrl.countCredentials()).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns base JSON without credential count by default", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("ctype-1");
|
||||
expect(json.name).toBe("Login Credential");
|
||||
expect(json.credentialCount).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes credential count when option is set", () => {
|
||||
const data = buildMockCredentialType({
|
||||
credentials: [{ id: "c1" }, { id: "c2" }],
|
||||
});
|
||||
const ctrl = new CredentialTypeController(data);
|
||||
const json = ctrl.toJson({ includeCredentialCount: true });
|
||||
expect(json.credentialCount).toBe(2);
|
||||
});
|
||||
|
||||
test("includes all expected keys", () => {
|
||||
const ctrl = new CredentialTypeController(buildMockCredentialType());
|
||||
const json = ctrl.toJson();
|
||||
expect(json).toHaveProperty("id");
|
||||
expect(json).toHaveProperty("name");
|
||||
expect(json).toHaveProperty("permissionScope");
|
||||
expect(json).toHaveProperty("icon");
|
||||
expect(json).toHaveProperty("fields");
|
||||
expect(json).toHaveProperty("createdAt");
|
||||
expect(json).toHaveProperty("updatedAt");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { CwMemberController } from "../../../src/controllers/CwMemberController";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildMockCwMember(overrides: Record<string, any> = {}) {
|
||||
return {
|
||||
id: "member-1",
|
||||
cwMemberId: 42,
|
||||
identifier: "jdoe",
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
officeEmail: "jdoe@example.com",
|
||||
inactiveFlag: false,
|
||||
apiKey: null,
|
||||
cwLastUpdated: new Date("2026-02-01"),
|
||||
createdAt: new Date("2026-01-01"),
|
||||
updatedAt: new Date("2026-02-01"),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("CwMemberController", () => {
|
||||
// -----------------------------------------------------------------
|
||||
// Constructor
|
||||
// -----------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets all public properties from data", () => {
|
||||
const data = buildMockCwMember();
|
||||
const ctrl = new CwMemberController(data as any);
|
||||
|
||||
expect(ctrl.id).toBe("member-1");
|
||||
expect(ctrl.cwMemberId).toBe(42);
|
||||
expect(ctrl.identifier).toBe("jdoe");
|
||||
expect(ctrl.firstName).toBe("John");
|
||||
expect(ctrl.lastName).toBe("Doe");
|
||||
expect(ctrl.officeEmail).toBe("jdoe@example.com");
|
||||
expect(ctrl.inactiveFlag).toBe(false);
|
||||
expect(ctrl.apiKey).toBeNull();
|
||||
expect(ctrl.cwLastUpdated).toEqual(new Date("2026-02-01"));
|
||||
expect(ctrl.createdAt).toEqual(new Date("2026-01-01"));
|
||||
expect(ctrl.updatedAt).toEqual(new Date("2026-02-01"));
|
||||
});
|
||||
|
||||
test("handles null officeEmail", () => {
|
||||
const data = buildMockCwMember({ officeEmail: null });
|
||||
const ctrl = new CwMemberController(data as any);
|
||||
expect(ctrl.officeEmail).toBeNull();
|
||||
});
|
||||
|
||||
test("handles apiKey set", () => {
|
||||
const data = buildMockCwMember({ apiKey: "secret-key" });
|
||||
const ctrl = new CwMemberController(data as any);
|
||||
expect(ctrl.apiKey).toBe("secret-key");
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// fullName getter
|
||||
// -----------------------------------------------------------------
|
||||
describe("fullName", () => {
|
||||
test("returns firstName + lastName", () => {
|
||||
const ctrl = new CwMemberController(buildMockCwMember() as any);
|
||||
expect(ctrl.fullName).toBe("John Doe");
|
||||
});
|
||||
|
||||
test("returns trimmed name when lastName is empty", () => {
|
||||
const ctrl = new CwMemberController(
|
||||
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,
|
||||
);
|
||||
expect(ctrl.fullName).toBe("Doe");
|
||||
});
|
||||
|
||||
test("falls back to identifier when both names are empty", () => {
|
||||
const ctrl = new CwMemberController(
|
||||
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
|
||||
// -----------------------------------------------------------------
|
||||
describe("toJson", () => {
|
||||
test("returns all fields including fullName", () => {
|
||||
const ctrl = new CwMemberController(buildMockCwMember() as any);
|
||||
const json = ctrl.toJson();
|
||||
|
||||
expect(json.id).toBe("member-1");
|
||||
expect(json.cwMemberId).toBe(42);
|
||||
expect(json.identifier).toBe("jdoe");
|
||||
expect(json.firstName).toBe("John");
|
||||
expect(json.lastName).toBe("Doe");
|
||||
expect(json.fullName).toBe("John Doe");
|
||||
expect(json.officeEmail).toBe("jdoe@example.com");
|
||||
expect(json.inactiveFlag).toBe(false);
|
||||
expect(json.apiKey).toBeNull();
|
||||
expect(json.cwLastUpdated).toEqual(new Date("2026-02-01"));
|
||||
expect(json.createdAt).toEqual(new Date("2026-01-01"));
|
||||
expect(json.updatedAt).toEqual(new Date("2026-02-01"));
|
||||
});
|
||||
|
||||
test("includes fullName in JSON", () => {
|
||||
const ctrl = new CwMemberController(
|
||||
buildMockCwMember({ firstName: "", lastName: "" }) as any,
|
||||
);
|
||||
expect(ctrl.toJson().fullName).toBe("jdoe");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,397 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { ForecastProductController } from "../../../src/controllers/ForecastProductController";
|
||||
import { buildMockCWForecastItem } from "../../setup";
|
||||
|
||||
describe("ForecastProductController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets core identification fields", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.cwForecastId).toBe(7001);
|
||||
expect(ctrl.forecastDescription).toBe("Network Switch");
|
||||
});
|
||||
|
||||
test("maps opportunity reference", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.opportunityCwId).toBe(1001);
|
||||
expect(ctrl.opportunityName).toBe("Test Opportunity");
|
||||
});
|
||||
|
||||
test("maps quantity", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.quantity).toBe(5);
|
||||
});
|
||||
|
||||
test("maps status reference", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.statusCwId).toBe(1);
|
||||
expect(ctrl.statusName).toBe("Won");
|
||||
});
|
||||
|
||||
test("maps catalogItem reference", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.catalogItemCwId).toBe(500);
|
||||
expect(ctrl.catalogItemIdentifier).toBe("USW-Pro-24");
|
||||
});
|
||||
|
||||
test("maps product details", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.productDescription).toBe("UniFi Switch Pro 24");
|
||||
expect(ctrl.productClass).toBe("Product");
|
||||
expect(ctrl.forecastType).toBe("Product");
|
||||
});
|
||||
|
||||
test("maps financials", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.revenue).toBe(2500.0);
|
||||
expect(ctrl.cost).toBe(1800.0);
|
||||
expect(ctrl.margin).toBe(700.0);
|
||||
expect(ctrl.percentage).toBe(100);
|
||||
});
|
||||
|
||||
test("maps boolean flags", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.includeFlag).toBe(true);
|
||||
expect(ctrl.linkFlag).toBe(false);
|
||||
expect(ctrl.recurringFlag).toBe(false);
|
||||
expect(ctrl.taxableFlag).toBe(true);
|
||||
});
|
||||
|
||||
test("maps sequence and sub number", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.sequenceNumber).toBe(1);
|
||||
expect(ctrl.subNumber).toBe(0);
|
||||
});
|
||||
|
||||
test("maps recurring fields", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.recurringRevenue).toBe(0);
|
||||
expect(ctrl.recurringCost).toBe(0);
|
||||
expect(ctrl.cycles).toBe(0);
|
||||
});
|
||||
|
||||
test("sets cancellation defaults", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.cancelledFlag).toBe(false);
|
||||
expect(ctrl.quantityCancelled).toBe(0);
|
||||
expect(ctrl.cancelledReason).toBeNull();
|
||||
expect(ctrl.cancelledBy).toBeNull();
|
||||
expect(ctrl.cancelledDate).toBeNull();
|
||||
});
|
||||
|
||||
test("sets inventory defaults", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.onHand).toBeNull();
|
||||
expect(ctrl.inStock).toBeNull();
|
||||
});
|
||||
|
||||
test("maps _info to cwLastUpdated", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.cwLastUpdated).toBeInstanceOf(Date);
|
||||
expect(ctrl.cwUpdatedBy).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("handles missing optional fields", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({
|
||||
opportunity: undefined,
|
||||
status: undefined,
|
||||
catalogItem: undefined,
|
||||
_info: undefined,
|
||||
}),
|
||||
);
|
||||
expect(ctrl.opportunityCwId).toBeNull();
|
||||
expect(ctrl.statusCwId).toBeNull();
|
||||
expect(ctrl.catalogItemCwId).toBeNull();
|
||||
expect(ctrl.cwLastUpdated).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// applyCancellationData
|
||||
// -------------------------------------------------------------------
|
||||
describe("applyCancellationData()", () => {
|
||||
test("applies cancellation data", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyCancellationData({
|
||||
cancelledFlag: true,
|
||||
quantityCancelled: 3,
|
||||
cancelledReason: "Out of stock",
|
||||
cancelledBy: 42,
|
||||
cancelledDate: "2026-02-20T00:00:00Z",
|
||||
});
|
||||
expect(ctrl.cancelledFlag).toBe(true);
|
||||
expect(ctrl.quantityCancelled).toBe(3);
|
||||
expect(ctrl.cancelledReason).toBe("Out of stock");
|
||||
expect(ctrl.cancelledBy).toBe(42);
|
||||
expect(ctrl.cancelledDate).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("handles partial cancellation data", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyCancellationData({});
|
||||
expect(ctrl.cancelledFlag).toBe(false);
|
||||
expect(ctrl.quantityCancelled).toBe(0);
|
||||
expect(ctrl.cancelledReason).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// applyProcurementCustomFields
|
||||
// -------------------------------------------------------------------
|
||||
describe("applyProcurementCustomFields()", () => {
|
||||
test("sets productNarrative from custom field id 46", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyProcurementCustomFields({
|
||||
customFields: [{ id: 46, value: "Custom narrative text" }],
|
||||
});
|
||||
expect(ctrl.productNarrative).toBe("Custom narrative text");
|
||||
});
|
||||
|
||||
test("does not overwrite productNarrative when field 46 is missing", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.productNarrative = "existing";
|
||||
ctrl.applyProcurementCustomFields({
|
||||
customFields: [{ id: 99, value: "other" }],
|
||||
});
|
||||
expect(ctrl.productNarrative).toBe("existing");
|
||||
});
|
||||
|
||||
test("handles empty customFields array", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyProcurementCustomFields({ customFields: [] });
|
||||
expect(ctrl.productNarrative).toBeNull();
|
||||
});
|
||||
|
||||
test("handles undefined customFields", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyProcurementCustomFields({});
|
||||
expect(ctrl.productNarrative).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// applyInventoryData
|
||||
// -------------------------------------------------------------------
|
||||
describe("applyInventoryData()", () => {
|
||||
test("sets onHand and inStock true when quantity > 0", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyInventoryData({ onHand: 10 });
|
||||
expect(ctrl.onHand).toBe(10);
|
||||
expect(ctrl.inStock).toBe(true);
|
||||
});
|
||||
|
||||
test("sets inStock false when onHand is 0", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyInventoryData({ onHand: 0 });
|
||||
expect(ctrl.onHand).toBe(0);
|
||||
expect(ctrl.inStock).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Computed properties
|
||||
// -------------------------------------------------------------------
|
||||
describe("computed properties", () => {
|
||||
test("profit returns revenue - cost", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.profit).toBe(700.0);
|
||||
});
|
||||
|
||||
test("cancelled returns false by default", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.cancelled).toBe(false);
|
||||
});
|
||||
|
||||
test("cancelled returns true after applyCancellationData", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 1 });
|
||||
expect(ctrl.cancelled).toBe(true);
|
||||
});
|
||||
|
||||
test("cancellationType returns null when not cancelled", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
expect(ctrl.cancellationType).toBeNull();
|
||||
});
|
||||
|
||||
test("cancellationType returns 'full' when all units cancelled", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5 }),
|
||||
);
|
||||
ctrl.applyCancellationData({
|
||||
cancelledFlag: true,
|
||||
quantityCancelled: 5,
|
||||
});
|
||||
expect(ctrl.cancellationType).toBe("full");
|
||||
});
|
||||
|
||||
test("cancellationType returns 'partial' when some units cancelled", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5 }),
|
||||
);
|
||||
ctrl.applyCancellationData({
|
||||
cancelledFlag: true,
|
||||
quantityCancelled: 2,
|
||||
});
|
||||
expect(ctrl.cancellationType).toBe("partial");
|
||||
});
|
||||
|
||||
test("effectiveQuantity returns full quantity when not cancelled", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5 }),
|
||||
);
|
||||
expect(ctrl.effectiveQuantity).toBe(5);
|
||||
});
|
||||
|
||||
test("effectiveQuantity returns reduced quantity for partial cancel", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5 }),
|
||||
);
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 2 });
|
||||
expect(ctrl.effectiveQuantity).toBe(3);
|
||||
});
|
||||
|
||||
test("effectiveQuantity returns 0 for full cancellation", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5 }),
|
||||
);
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
|
||||
expect(ctrl.effectiveQuantity).toBe(0);
|
||||
});
|
||||
|
||||
test("effectiveRevenue returns full revenue when not cancelled", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
|
||||
);
|
||||
expect(ctrl.effectiveRevenue).toBe(2500);
|
||||
});
|
||||
|
||||
test("effectiveRevenue returns proportional revenue for partial cancel", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
|
||||
);
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 2 });
|
||||
expect(ctrl.effectiveRevenue).toBe(1500);
|
||||
});
|
||||
|
||||
test("effectiveRevenue returns 0 for full cancellation", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
|
||||
);
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
|
||||
expect(ctrl.effectiveRevenue).toBe(0);
|
||||
});
|
||||
|
||||
test("effectiveCost returns full cost when not cancelled", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, cost: 1800 }),
|
||||
);
|
||||
expect(ctrl.effectiveCost).toBe(1800);
|
||||
});
|
||||
|
||||
test("effectiveCost returns 0 for full cancellation", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, cost: 1800 }),
|
||||
);
|
||||
ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
|
||||
expect(ctrl.effectiveCost).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns id as cwForecastId", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe(7001);
|
||||
});
|
||||
|
||||
test("returns financial fields", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.revenue).toBe(2500.0);
|
||||
expect(json.cost).toBe(1800.0);
|
||||
expect(json.margin).toBe(700.0);
|
||||
expect(json.profit).toBe(700.0);
|
||||
});
|
||||
|
||||
test("returns cancellation info", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.cancelled).toBe(false);
|
||||
expect(json.cancellationType).toBeNull();
|
||||
});
|
||||
|
||||
test("returns status as reference object", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.status).toEqual({ id: 1, name: "Won" });
|
||||
});
|
||||
|
||||
test("returns catalogItem as reference object", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.catalogItem).toEqual({
|
||||
id: 500,
|
||||
identifier: "USW-Pro-24",
|
||||
});
|
||||
});
|
||||
|
||||
test("returns opportunity as reference object", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.opportunity).toEqual({
|
||||
id: 1001,
|
||||
name: "Test Opportunity",
|
||||
});
|
||||
});
|
||||
|
||||
test("includes inventory data", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
ctrl.applyInventoryData({ onHand: 10 });
|
||||
const json = ctrl.toJson();
|
||||
expect(json.onHand).toBe(10);
|
||||
expect(json.inStock).toBe(true);
|
||||
});
|
||||
|
||||
test("includes boolean flags", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.includeFlag).toBe(true);
|
||||
expect(json.linkFlag).toBe(false);
|
||||
expect(json.recurringFlag).toBe(false);
|
||||
expect(json.taxableFlag).toBe(true);
|
||||
});
|
||||
|
||||
test("includes sequence and timing info", () => {
|
||||
const ctrl = new ForecastProductController(buildMockCWForecastItem());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.sequenceNumber).toBe(1);
|
||||
expect(json.subNumber).toBe(0);
|
||||
expect(json.cwLastUpdated).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("includes customerDescription and productNarrative", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ customerDescription: "Customer desc" }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.customerDescription).toBe("Customer desc");
|
||||
expect(json.productNarrative).toBeNull();
|
||||
});
|
||||
|
||||
test("includes effective* computed fields", () => {
|
||||
const ctrl = new ForecastProductController(
|
||||
buildMockCWForecastItem({ quantity: 5, revenue: 2500, cost: 1800 }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.effectiveQuantity).toBe(5);
|
||||
expect(json.effectiveRevenue).toBe(2500);
|
||||
expect(json.effectiveCost).toBe(1800);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { GeneratedQuoteController } from "../../../src/controllers/GeneratedQuoteController";
|
||||
import {
|
||||
buildMockGeneratedQuote,
|
||||
buildMockOpportunity,
|
||||
buildMockUser,
|
||||
} from "../../setup";
|
||||
|
||||
describe("GeneratedQuoteController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets core identification fields", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
expect(ctrl.id).toBe("quote-1");
|
||||
expect(ctrl.quoteFileName).toBe("Quote-TestOpp.pdf");
|
||||
expect(ctrl.opportunityId).toBe("opp-1");
|
||||
expect(ctrl.createdById).toBe("user-1");
|
||||
});
|
||||
|
||||
test("sets quoteRegenData", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
expect(ctrl.quoteRegenData).toEqual({ theme: "default" });
|
||||
});
|
||||
|
||||
test("sets quoteFile as Uint8Array", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
expect(ctrl.quoteFile).toBeInstanceOf(Uint8Array);
|
||||
expect(ctrl.quoteFile.length).toBe(4);
|
||||
});
|
||||
|
||||
test("sets timestamps", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
expect(ctrl.createdAt).toBeInstanceOf(Date);
|
||||
expect(ctrl.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("wraps included opportunity in OpportunityController", () => {
|
||||
const opp = buildMockOpportunity();
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ opportunity: opp }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeOpportunity: true });
|
||||
expect(json.opportunity).toBeDefined();
|
||||
expect(json.opportunity.id).toBe("opp-1");
|
||||
});
|
||||
|
||||
test("wraps included createdBy in UserController", () => {
|
||||
const user = buildMockUser({ roles: [] });
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ createdBy: user }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeCreatedBy: true });
|
||||
expect(json.createdBy).toBeDefined();
|
||||
expect(json.createdBy.id).toBe("user-1");
|
||||
});
|
||||
|
||||
test("sets _opportunity to null when opportunity not included", () => {
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ opportunity: null }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeOpportunity: true });
|
||||
expect(json.opportunity).toBeUndefined();
|
||||
});
|
||||
|
||||
test("sets _createdBy to null when createdBy not included", () => {
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ createdBy: null }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeCreatedBy: true });
|
||||
expect(json.createdBy).toBeUndefined();
|
||||
});
|
||||
|
||||
test("handles null createdById", () => {
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ createdById: null }),
|
||||
);
|
||||
expect(ctrl.createdById).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns core fields by default", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("quote-1");
|
||||
expect(json.quoteFileName).toBe("Quote-TestOpp.pdf");
|
||||
expect(json.opportunityId).toBe("opp-1");
|
||||
expect(json.createdById).toBe("user-1");
|
||||
expect(json.createdAt).toBeInstanceOf(Date);
|
||||
expect(json.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("excludes quoteFile by default", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.quoteFile).toBeUndefined();
|
||||
});
|
||||
|
||||
test("excludes quoteRegenData by default", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.quoteRegenData).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes quoteRegenData when requested", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson({ includeRegenData: true });
|
||||
expect(json.quoteRegenData).toEqual({ theme: "default" });
|
||||
});
|
||||
|
||||
test("includes quoteFile as raw Uint8Array when includeFile is true", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson({ includeFile: true });
|
||||
expect(json.quoteFile).toBeInstanceOf(Uint8Array);
|
||||
});
|
||||
|
||||
test("includes quoteFile as base64 when both flags set", () => {
|
||||
const ctrl = new GeneratedQuoteController(buildMockGeneratedQuote());
|
||||
const json = ctrl.toJson({
|
||||
includeFile: true,
|
||||
encodeFileAsBase64: true,
|
||||
});
|
||||
expect(typeof json.quoteFile).toBe("string");
|
||||
// Should be valid base64
|
||||
expect(() => Buffer.from(json.quoteFile, "base64")).not.toThrow();
|
||||
});
|
||||
|
||||
test("excludes opportunity when not requested", () => {
|
||||
const opp = buildMockOpportunity();
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ opportunity: opp }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.opportunity).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes opportunity when requested and available", () => {
|
||||
const opp = buildMockOpportunity();
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ opportunity: opp }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeOpportunity: true });
|
||||
expect(json.opportunity).toBeDefined();
|
||||
expect(json.opportunity.name).toBe("Test Opportunity");
|
||||
});
|
||||
|
||||
test("excludes createdBy when not requested", () => {
|
||||
const user = buildMockUser({ roles: [] });
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ createdBy: user }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.createdBy).toBeUndefined();
|
||||
});
|
||||
|
||||
test("includes createdBy when requested and available", () => {
|
||||
const user = buildMockUser({ roles: [] });
|
||||
const ctrl = new GeneratedQuoteController(
|
||||
buildMockGeneratedQuote({ createdBy: user }),
|
||||
);
|
||||
const json = ctrl.toJson({ includeCreatedBy: true });
|
||||
expect(json.createdBy).toBeDefined();
|
||||
expect(json.createdBy.name).toBe("Test User");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,283 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { OpportunityController } from "../../../src/controllers/OpportunityController";
|
||||
import { ActivityController } from "../../../src/controllers/ActivityController";
|
||||
import { CompanyController } from "../../../src/controllers/CompanyController";
|
||||
import {
|
||||
buildMockOpportunity,
|
||||
buildMockCompany,
|
||||
buildMockCWActivity,
|
||||
} from "../../setup";
|
||||
|
||||
describe("OpportunityController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets core identification fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.id).toBe("opp-1");
|
||||
expect(ctrl.cwOpportunityId).toBe(1001);
|
||||
expect(ctrl.name).toBe("Test Opportunity");
|
||||
expect(ctrl.notes).toBe("Some notes");
|
||||
});
|
||||
|
||||
test("sets type, stage, status references", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.typeName).toBe("New Business");
|
||||
expect(ctrl.typeCwId).toBe(1);
|
||||
expect(ctrl.stageName).toBe("Proposal");
|
||||
expect(ctrl.stageCwId).toBe(2);
|
||||
expect(ctrl.statusName).toBe("Active");
|
||||
expect(ctrl.statusCwId).toBe(3);
|
||||
});
|
||||
|
||||
test("sets priority, rating, source", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.priorityName).toBe("High");
|
||||
expect(ctrl.priorityCwId).toBe(4);
|
||||
expect(ctrl.ratingName).toBe("Hot");
|
||||
expect(ctrl.ratingCwId).toBe(5);
|
||||
expect(ctrl.source).toBe("Referral");
|
||||
});
|
||||
|
||||
test("sets sales rep fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.primarySalesRepName).toBe("John");
|
||||
expect(ctrl.primarySalesRepIdentifier).toBe("jroberts");
|
||||
expect(ctrl.primarySalesRepCwId).toBe(10);
|
||||
expect(ctrl.secondarySalesRepName).toBeNull();
|
||||
});
|
||||
|
||||
test("sets company/contact/site fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.companyCwId).toBe(123);
|
||||
expect(ctrl.companyName).toBe("Test Company");
|
||||
expect(ctrl.contactCwId).toBe(200);
|
||||
expect(ctrl.contactName).toBe("Jane Doe");
|
||||
expect(ctrl.siteCwId).toBe(300);
|
||||
expect(ctrl.siteName).toBe("Main Office");
|
||||
});
|
||||
|
||||
test("sets financial and location fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.totalSalesTax).toBe(50.0);
|
||||
expect(ctrl.customerPO).toBe("PO-12345");
|
||||
expect(ctrl.locationName).toBe("HQ");
|
||||
expect(ctrl.departmentName).toBe("Sales");
|
||||
});
|
||||
|
||||
test("sets date fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
expect(ctrl.expectedCloseDate).toBeInstanceOf(Date);
|
||||
expect(ctrl.pipelineChangeDate).toBeInstanceOf(Date);
|
||||
expect(ctrl.dateBecameLead).toBeInstanceOf(Date);
|
||||
expect(ctrl.closedDate).toBeNull();
|
||||
expect(ctrl.closedFlag).toBe(false);
|
||||
});
|
||||
|
||||
test("accepts company controller via opts", () => {
|
||||
const company = new CompanyController(buildMockCompany());
|
||||
const ctrl = new OpportunityController(buildMockOpportunity(), {
|
||||
company,
|
||||
});
|
||||
const json = ctrl.toJson();
|
||||
// Company should be a full object, not just {id, name}
|
||||
expect(json.company.id).toBe("company-1");
|
||||
expect(json.company.name).toBe("Test Company");
|
||||
});
|
||||
|
||||
test("accepts activities via opts", () => {
|
||||
const activities = [new ActivityController(buildMockCWActivity())];
|
||||
const ctrl = new OpportunityController(buildMockOpportunity(), {
|
||||
activities,
|
||||
});
|
||||
const json = ctrl.toJson();
|
||||
expect(json.activities).toHaveLength(1);
|
||||
expect(json.activities[0].cwActivityId).toBe(5001);
|
||||
});
|
||||
|
||||
test("accepts customFields via opts", () => {
|
||||
const customFields = [
|
||||
{
|
||||
id: 1,
|
||||
caption: "Custom1",
|
||||
type: "Text",
|
||||
entryMethod: "EntryField",
|
||||
numberOfDecimals: 0,
|
||||
value: "test",
|
||||
},
|
||||
];
|
||||
const ctrl = new OpportunityController(buildMockOpportunity(), {
|
||||
customFields,
|
||||
});
|
||||
const json = ctrl.toJson();
|
||||
expect(json.customFields).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("has empty activities/customFields without opts", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.activities).toEqual([]);
|
||||
expect(json.customFields).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// mapCwToDb (static)
|
||||
// -------------------------------------------------------------------
|
||||
describe("mapCwToDb()", () => {
|
||||
const cwOpportunity = {
|
||||
id: 1001,
|
||||
name: "CW Opp",
|
||||
notes: "CW notes",
|
||||
type: { id: 1, name: "New Business" },
|
||||
stage: { id: 2, name: "Proposal" },
|
||||
status: { id: 3, name: "Active" },
|
||||
priority: { id: 4, name: "High" },
|
||||
rating: null,
|
||||
source: "Web",
|
||||
campaign: null,
|
||||
primarySalesRep: { id: 10, identifier: "jroberts", name: "John" },
|
||||
secondarySalesRep: null,
|
||||
company: { id: 123, identifier: "TestCo", name: "Test Co" },
|
||||
contact: { id: 200, name: "Jane" },
|
||||
site: { id: 300, name: "Main" },
|
||||
customerPO: "PO-1",
|
||||
totalSalesTax: 25.5,
|
||||
location: { id: 400, name: "HQ" },
|
||||
department: { id: 500, name: "Sales" },
|
||||
expectedCloseDate: "2026-04-01T00:00:00Z",
|
||||
pipelineChangeDate: "2026-02-15T00:00:00Z",
|
||||
dateBecameLead: "2026-01-01T00:00:00Z",
|
||||
closedDate: null,
|
||||
closedFlag: false,
|
||||
closedBy: null,
|
||||
customFields: [],
|
||||
_info: { lastUpdated: "2026-02-28T12:00:00Z" },
|
||||
} as any;
|
||||
|
||||
test("maps name and notes", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.name).toBe("CW Opp");
|
||||
expect(result.notes).toBe("CW notes");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
test("maps null references to null", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.ratingName).toBeNull();
|
||||
expect(result.ratingCwId).toBeNull();
|
||||
expect(result.campaignName).toBeNull();
|
||||
});
|
||||
|
||||
test("maps sales rep fields", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.primarySalesRepName).toBe("John");
|
||||
expect(result.primarySalesRepIdentifier).toBe("jroberts");
|
||||
expect(result.secondarySalesRepName).toBeNull();
|
||||
});
|
||||
|
||||
test("maps dates to Date objects", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.expectedCloseDate).toBeInstanceOf(Date);
|
||||
expect(result.closedDate).toBeNull();
|
||||
});
|
||||
|
||||
test("maps closedFlag", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.closedFlag).toBe(false);
|
||||
});
|
||||
|
||||
test("maps cwLastUpdated from _info", () => {
|
||||
const result = OpportunityController.mapCwToDb(cwOpportunity);
|
||||
expect(result.cwLastUpdated).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns core fields", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("opp-1");
|
||||
expect(json.cwOpportunityId).toBe(1001);
|
||||
expect(json.name).toBe("Test Opportunity");
|
||||
});
|
||||
|
||||
test("formats type as reference object", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.type).toEqual({ id: 1, name: "New Business" });
|
||||
});
|
||||
|
||||
test("formats stage as reference object", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.stage).toEqual({ id: 2, name: "Proposal" });
|
||||
});
|
||||
|
||||
test("formats status as reference object", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.status).toEqual({ id: 3, name: "Active" });
|
||||
});
|
||||
|
||||
test("formats primarySalesRep with identifier", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.primarySalesRep).toEqual({
|
||||
id: 10,
|
||||
identifier: "jroberts",
|
||||
name: "John",
|
||||
});
|
||||
});
|
||||
|
||||
test("secondarySalesRep is null when not set", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.secondarySalesRep).toBeNull();
|
||||
});
|
||||
|
||||
test("contact formats as reference object", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.contact).toEqual({ id: 200, name: "Jane Doe" });
|
||||
});
|
||||
|
||||
test("company falls back to CW reference when no controller", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.company).toEqual({ id: 123, name: "Test Company" });
|
||||
});
|
||||
|
||||
test("includes financial data", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.totalSalesTax).toBe(50.0);
|
||||
expect(json.customerPO).toBe("PO-12345");
|
||||
});
|
||||
|
||||
test("includes dates", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.expectedCloseDate).toBeInstanceOf(Date);
|
||||
expect(json.closedFlag).toBe(false);
|
||||
});
|
||||
|
||||
test("includes timestamps", () => {
|
||||
const ctrl = new OpportunityController(buildMockOpportunity());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.createdAt).toBeInstanceOf(Date);
|
||||
expect(json.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { RoleController } from "../../../src/controllers/RoleController";
|
||||
import { buildMockRole, buildMockUser } from "../../setup";
|
||||
|
||||
describe("RoleController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets public properties from role data", () => {
|
||||
const data = buildMockRole();
|
||||
const ctrl = new RoleController(data);
|
||||
expect(ctrl.id).toBe("role-1");
|
||||
expect(ctrl.title).toBe("Test Role");
|
||||
expect(ctrl.moniker).toBe("test-role");
|
||||
expect(ctrl.deleted).toBe(false);
|
||||
});
|
||||
|
||||
test("sets timestamps", () => {
|
||||
const ctrl = new RoleController(buildMockRole());
|
||||
expect(ctrl.createdAt).toBeInstanceOf(Date);
|
||||
expect(ctrl.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// getUsers
|
||||
// -------------------------------------------------------------------
|
||||
describe("getUsers()", () => {
|
||||
test("returns empty collection when no users", () => {
|
||||
const ctrl = new RoleController(buildMockRole({ users: [] }));
|
||||
const users = ctrl.getUsers();
|
||||
expect(users.size).toBe(0);
|
||||
});
|
||||
|
||||
test("returns collection of UserController instances", () => {
|
||||
const userData = buildMockUser({ id: "u-1" });
|
||||
const ctrl = new RoleController(buildMockRole({ users: [userData] }));
|
||||
const users = ctrl.getUsers();
|
||||
expect(users.size).toBe(1);
|
||||
expect(users.get("u-1")).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns base JSON without permissions or users by default", () => {
|
||||
const ctrl = new RoleController(buildMockRole());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("role-1");
|
||||
expect(json.title).toBe("Test Role");
|
||||
expect(json.moniker).toBe("test-role");
|
||||
expect(json.permissions).toBeUndefined();
|
||||
expect(json.users).toBeUndefined();
|
||||
expect(json.createdAt).toBeDefined();
|
||||
expect(json.updatedAt).toBeDefined();
|
||||
});
|
||||
|
||||
test("includes users when viewUsers is true", () => {
|
||||
const userData = buildMockUser({
|
||||
id: "u-1",
|
||||
roles: [{ id: "role-1", moniker: "test-role" }],
|
||||
});
|
||||
const ctrl = new RoleController(buildMockRole({ users: [userData] }));
|
||||
const json = ctrl.toJson({ viewUsers: true });
|
||||
expect(json.users).toBeDefined();
|
||||
expect(json.users).toHaveLength(1);
|
||||
expect(json.users![0]!.id).toBe("u-1");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { SessionController } from "../../../src/controllers/SessionController";
|
||||
import { buildMockSession } from "../../setup";
|
||||
|
||||
describe("SessionController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets all public properties from session data", () => {
|
||||
const data = buildMockSession();
|
||||
const ctrl = new SessionController(data);
|
||||
expect(ctrl.id).toBe("session-1");
|
||||
expect(ctrl.sessionKey).toBe("sk-abc123");
|
||||
expect(ctrl.userId).toBe("user-1");
|
||||
expect(ctrl.expires).toBeInstanceOf(Date);
|
||||
expect(ctrl.refreshedAt).toBeNull();
|
||||
expect(ctrl.invalidatedAt).toBeNull();
|
||||
expect(ctrl.terminated).toBe(false);
|
||||
});
|
||||
|
||||
test("sets custom values from overrides", () => {
|
||||
const refreshDate = new Date("2025-06-01");
|
||||
const ctrl = new SessionController(
|
||||
buildMockSession({ refreshedAt: refreshDate }),
|
||||
);
|
||||
expect(ctrl.refreshedAt).toEqual(refreshDate);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// invalidate
|
||||
// -------------------------------------------------------------------
|
||||
describe("invalidate()", () => {
|
||||
test("throws when session is already invalidated", async () => {
|
||||
const ctrl = new SessionController(
|
||||
buildMockSession({ invalidatedAt: new Date() }),
|
||||
);
|
||||
await expect(ctrl.invalidate()).rejects.toThrow(
|
||||
"Session has already been invalidated",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// generateTokens
|
||||
// -------------------------------------------------------------------
|
||||
describe("generateTokens()", () => {
|
||||
test("throws when tokens have already been generated", async () => {
|
||||
const ctrl = new SessionController(
|
||||
buildMockSession({ refreshTokenGenerated: true }),
|
||||
);
|
||||
await expect(ctrl.generateTokens()).rejects.toThrow(
|
||||
"Tokens have alredy been generated",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { UnifiSiteController } from "../../../src/controllers/UnifiSiteController";
|
||||
import { buildMockUnifiSite } from "../../setup";
|
||||
|
||||
describe("UnifiSiteController", () => {
|
||||
describe("constructor", () => {
|
||||
test("sets all properties from site data", () => {
|
||||
const ctrl = new UnifiSiteController(buildMockUnifiSite());
|
||||
expect(ctrl.id).toBe("usite-1");
|
||||
expect(ctrl.name).toBe("Main Office");
|
||||
expect(ctrl.siteId).toBe("default");
|
||||
expect(ctrl.companyId).toBeNull();
|
||||
});
|
||||
|
||||
test("accepts non-null companyId", () => {
|
||||
const ctrl = new UnifiSiteController(
|
||||
buildMockUnifiSite({ companyId: "company-1" }),
|
||||
);
|
||||
expect(ctrl.companyId).toBe("company-1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toJson()", () => {
|
||||
test("returns all properties", () => {
|
||||
const ctrl = new UnifiSiteController(
|
||||
buildMockUnifiSite({ companyId: "comp-abc" }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json).toEqual({
|
||||
id: "usite-1",
|
||||
name: "Main Office",
|
||||
siteId: "default",
|
||||
companyId: "comp-abc",
|
||||
});
|
||||
});
|
||||
|
||||
test("companyId is null when unlinked", () => {
|
||||
const ctrl = new UnifiSiteController(buildMockUnifiSite());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.companyId).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import UserController from "../../../src/controllers/UserController";
|
||||
import { buildMockUser } from "../../setup";
|
||||
|
||||
describe("UserController", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------
|
||||
describe("constructor", () => {
|
||||
test("sets all public properties", () => {
|
||||
const ctrl = new UserController(buildMockUser());
|
||||
expect(ctrl.id).toBe("user-1");
|
||||
expect(ctrl.name).toBe("Test User");
|
||||
expect(ctrl.login).toBe("test@example.com");
|
||||
expect(ctrl.email).toBe("test@example.com");
|
||||
expect(ctrl.image).toBeNull();
|
||||
expect(ctrl.cwIdentifier).toBeNull();
|
||||
});
|
||||
|
||||
test("sets cwIdentifier when provided", () => {
|
||||
const ctrl = new UserController(
|
||||
buildMockUser({ cwIdentifier: "jroberts" }),
|
||||
);
|
||||
expect(ctrl.cwIdentifier).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("sets timestamps", () => {
|
||||
const ctrl = new UserController(buildMockUser());
|
||||
expect(ctrl.createdAt).toBeInstanceOf(Date);
|
||||
expect(ctrl.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
test("builds roles collection", () => {
|
||||
const mockRole = {
|
||||
id: "role-1",
|
||||
title: "Admin",
|
||||
moniker: "admin",
|
||||
permissions: "tok",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
const ctrl = new UserController(buildMockUser({ roles: [mockRole] }));
|
||||
// _roles is private, but we can verify via toJson
|
||||
const json = ctrl.toJson();
|
||||
expect(json.roles).toContain("admin");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// toJson
|
||||
// -------------------------------------------------------------------
|
||||
describe("toJson()", () => {
|
||||
test("returns full JSON by default", () => {
|
||||
const ctrl = new UserController(buildMockUser());
|
||||
const json = ctrl.toJson();
|
||||
expect(json.id).toBe("user-1");
|
||||
expect(json.name).toBe("Test User");
|
||||
expect(json.login).toBe("test@example.com");
|
||||
expect(json.email).toBe("test@example.com");
|
||||
expect(json.image).toBeNull();
|
||||
expect(json.createdAt).toBeDefined();
|
||||
expect(json.updatedAt).toBeDefined();
|
||||
});
|
||||
|
||||
test("safeReturn hides sensitive fields", () => {
|
||||
const ctrl = new UserController(buildMockUser());
|
||||
const json = ctrl.toJson({ safeReturn: true });
|
||||
expect(json.id).toBe("user-1");
|
||||
expect(json.name).toBe("Test User");
|
||||
expect(json.login).toBeUndefined();
|
||||
expect(json.email).toBeUndefined();
|
||||
expect(json.cwIdentifier).toBeUndefined();
|
||||
expect(json.roles).toBeUndefined();
|
||||
expect(json.permissions).toBeUndefined();
|
||||
});
|
||||
|
||||
test("cwIdentifier included in full JSON", () => {
|
||||
const ctrl = new UserController(
|
||||
buildMockUser({ cwIdentifier: "jroberts" }),
|
||||
);
|
||||
const json = ctrl.toJson();
|
||||
expect(json.cwIdentifier).toBe("jroberts");
|
||||
});
|
||||
|
||||
test("roles is undefined when user has no roles", () => {
|
||||
const ctrl = new UserController(buildMockUser({ roles: [] }));
|
||||
const json = ctrl.toJson();
|
||||
// _roles.size == 0, so roles is undefined
|
||||
expect(json.roles).toBeUndefined();
|
||||
});
|
||||
|
||||
test("roles returns monikers when present", () => {
|
||||
const mockRoles = [
|
||||
{
|
||||
id: "r1",
|
||||
title: "Admin",
|
||||
moniker: "admin",
|
||||
permissions: "t",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: "r2",
|
||||
title: "User",
|
||||
moniker: "user",
|
||||
permissions: "t",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
const ctrl = new UserController(buildMockUser({ roles: mockRoles }));
|
||||
const json = ctrl.toJson();
|
||||
expect(json.roles).toHaveLength(2);
|
||||
expect(json.roles).toContain("admin");
|
||||
expect(json.roles).toContain("user");
|
||||
});
|
||||
|
||||
test("permissions returns empty array when user has no permissions token", () => {
|
||||
const ctrl = new UserController(buildMockUser({ permissions: null }));
|
||||
const json = ctrl.toJson();
|
||||
expect(json.permissions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// readPermissions
|
||||
// -------------------------------------------------------------------
|
||||
describe("readPermissions()", () => {
|
||||
test("returns empty array when permissions is null", () => {
|
||||
const ctrl = new UserController(buildMockUser({ permissions: null }));
|
||||
expect(ctrl.readPermissions()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user