import { describe, it, expect } from "vitest"; import { createPoSchema, lineItemSchema, TC_DEFAULTS, TC_FIXED_LINE } from "@/lib/validations/po"; // ── lineItemSchema ──────────────────────────────────────────────────────────── describe("lineItemSchema", () => { const validItem = { name: "Gear Oil", description: "Gear Oil 15W40", quantity: "10", unit: "L", unitPrice: "182" }; it("accepts a valid line item", () => { const result = lineItemSchema.safeParse(validItem); expect(result.success).toBe(true); }); it("defaults gstRate to 0.18 when omitted", () => { const result = lineItemSchema.safeParse(validItem); expect(result.success && result.data.gstRate).toBeCloseTo(0.18); }); it("accepts gstRate of 0 (zero-rated supply)", () => { const result = lineItemSchema.safeParse({ ...validItem, gstRate: "0" }); expect(result.success && result.data.gstRate).toBe(0); }); it("accepts all valid GST rates: 0, 0.05, 0.12, 0.18, 0.28", () => { for (const rate of [0, 0.05, 0.12, 0.18, 0.28]) { const r = lineItemSchema.safeParse({ ...validItem, gstRate: String(rate) }); expect(r.success).toBe(true); } }); it("rejects gstRate > 1", () => { const result = lineItemSchema.safeParse({ ...validItem, gstRate: "1.5" }); expect(result.success).toBe(false); }); it("rejects gstRate < 0", () => { const result = lineItemSchema.safeParse({ ...validItem, gstRate: "-0.1" }); expect(result.success).toBe(false); }); it("rejects zero or negative quantity", () => { expect(lineItemSchema.safeParse({ ...validItem, quantity: "0" }).success).toBe(false); expect(lineItemSchema.safeParse({ ...validItem, quantity: "-1" }).success).toBe(false); }); it("rejects negative unit price", () => { const result = lineItemSchema.safeParse({ ...validItem, unitPrice: "-10" }); expect(result.success).toBe(false); }); it("allows zero unit price (donation/free item)", () => { const result = lineItemSchema.safeParse({ ...validItem, unitPrice: "0" }); expect(result.success).toBe(true); }); it("accepts empty description (description is optional)", () => { const result = lineItemSchema.safeParse({ ...validItem, description: "" }); expect(result.success).toBe(true); }); it("size is optional and omitted when empty", () => { const withSize = lineItemSchema.safeParse({ ...validItem, size: "10mm" }); expect(withSize.success && withSize.data.size).toBe("10mm"); const noSize = lineItemSchema.safeParse(validItem); expect(noSize.success && noSize.data.size).toBeUndefined(); }); }); // ── createPoSchema ──────────────────────────────────────────────────────────── const baseValidPo = { title: "Test Purchase Order", costCentreRef: "v:vessel-123", accountId: "account-456", lineItems: [{ name: "Item A", description: "Item A", quantity: "5", unit: "pc", unitPrice: "200" }], }; describe("createPoSchema", () => { it("accepts a minimal valid PO", () => { const result = createPoSchema.safeParse(baseValidPo); expect(result.success).toBe(true); }); it("defaults currency to INR", () => { const result = createPoSchema.safeParse(baseValidPo); expect(result.success && result.data.currency).toBe("INR"); }); it("rejects missing title", () => { const result = createPoSchema.safeParse({ ...baseValidPo, title: "" }); expect(result.success).toBe(false); }); it("rejects title longer than 200 characters", () => { const result = createPoSchema.safeParse({ ...baseValidPo, title: "x".repeat(201) }); expect(result.success).toBe(false); }); it("rejects missing costCentreRef", () => { const result = createPoSchema.safeParse({ ...baseValidPo, costCentreRef: "" }); expect(result.success).toBe(false); }); it("rejects invalid costCentreRef format", () => { const result = createPoSchema.safeParse({ ...baseValidPo, costCentreRef: "invalid-id" }); expect(result.success).toBe(false); }); it("accepts site costCentreRef (s: prefix)", () => { const result = createPoSchema.safeParse({ ...baseValidPo, costCentreRef: "s:site-123" }); expect(result.success).toBe(true); }); it("rejects empty lineItems array", () => { const result = createPoSchema.safeParse({ ...baseValidPo, lineItems: [] }); expect(result.success).toBe(false); }); it("accepts multiple line items", () => { const result = createPoSchema.safeParse({ ...baseValidPo, lineItems: [ { name: "Item A", description: "Item A", quantity: "5", unit: "pc", unitPrice: "200" }, { name: "Item B", description: "Item B", quantity: "2", unit: "L", unitPrice: "150", gstRate: "0.12" }, ], }); expect(result.success).toBe(true); }); it("accepts all TC structured fields as optional", () => { const result = createPoSchema.safeParse({ ...baseValidPo, tcDelivery: "Within 3 days", tcDispatch: "Freight on supplier", tcInspection: "Required", tcTransitInsurance: "Supplier's account", tcPaymentTerms: "Net 30", tcOthers: "No asbestos", }); expect(result.success).toBe(true); expect(result.success && result.data.tcDelivery).toBe("Within 3 days"); expect(result.success && result.data.tcPaymentTerms).toBe("Net 30"); }); it("accepts PI quotation and requisition fields", () => { const result = createPoSchema.safeParse({ ...baseValidPo, piQuotationNo: "Verbal", requisitionNo: "REQN-2026-001", placeOfDelivery: "Navi Mumbai", }); expect(result.success).toBe(true); }); }); // ── Constants ───────────────────────────────────────────────────────────────── describe("TC_FIXED_LINE", () => { it("references purchase order number for communications", () => { expect(TC_FIXED_LINE).toMatch(/purchase order/i); expect(TC_FIXED_LINE).toMatch(/communications/i); }); }); describe("TC_DEFAULTS", () => { it("has all 6 required keys", () => { const keys = ["tcDelivery", "tcDispatch", "tcInspection", "tcTransitInsurance", "tcPaymentTerms", "tcOthers"]; for (const k of keys) { expect(TC_DEFAULTS).toHaveProperty(k); // All keys must exist (tcOthers is intentionally empty string as a blank default) expect((TC_DEFAULTS as Record)[k]).toBeDefined(); } }); it("default delivery mentions days", () => { expect(TC_DEFAULTS.tcDelivery).toMatch(/day/i); }); it("default payment terms mentions days", () => { expect(TC_DEFAULTS.tcPaymentTerms).toMatch(/day/i); }); });