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(); }); }); // ------------------------------------------------------------------- // 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"); }); }); // ------------------------------------------------------------------- // 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); }); }); });