feat: restructure sales, add PDF quote generation and WebSocket support
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -138,6 +138,40 @@ describe("ForecastProductController", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// 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
|
||||
// -------------------------------------------------------------------
|
||||
@@ -203,6 +237,67 @@ describe("ForecastProductController", () => {
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -279,5 +374,24 @@ describe("ForecastProductController", () => {
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user