pelagia-portal/App/pelagia-portal/tests/unit/validations.test.ts
Hardik e07ce9bd02 test: unit, integration and E2E test suite (110 unit tests passing)
Unit (Vitest + jsdom):
  po-state-machine.test.ts   21 tests — all transitions and helpers
  permissions.test.ts        11 tests — all 7 roles
  utils.test.ts              17 tests — formatCurrency INR, formatDate, status labels
  validations.test.ts        24 tests — createPoSchema, lineItemSchema, TC defaults
  po-status-badge.test.tsx   17 tests — all 10 statuses
  po-line-items-editor.test.tsx 20 tests — add/remove, GST calc, read-only, diff mode

Integration (Vitest + real DB, mocked auth/notifier):
  create-po.test.ts          — S-01 create, S-02 draft, S-03 submit
  approval-actions.test.ts   — M-02 approve, M-03 reject, M-04 edits/vendor-id, S-06/S-07
  payment-actions.test.ts    — A-01 queue, A-02 mark paid with reference

E2E (Playwright):
  auth.spec.ts               — login, role nav, sign out
  submitter-journey.spec.ts  — S-01 to S-08
  manager-approvals.spec.ts  — M-01 to M-04
  accounts-payment.spec.ts   — A-01, A-02
  po-export.spec.ts          — export buttons, endpoint responses, PDF content
2026-05-06 00:16:10 +05:30

172 lines
6.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { createPoSchema, lineItemSchema, TC_DEFAULTS, TC_FIXED_LINE } from "@/lib/validations/po";
// ── lineItemSchema ────────────────────────────────────────────────────────────
describe("lineItemSchema", () => {
const validItem = { description: "Gear Oil", 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("rejects missing description", () => {
const result = lineItemSchema.safeParse({ ...validItem, description: "" });
expect(result.success).toBe(false);
});
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",
vesselId: "vessel-123",
accountId: "account-456",
lineItems: [{ 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 vesselId", () => {
const result = createPoSchema.safeParse({ ...baseValidPo, vesselId: "" });
expect(result.success).toBe(false);
});
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: [
{ description: "Item A", quantity: "5", unit: "pc", unitPrice: "200" },
{ 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);
expect((TC_DEFAULTS as Record<string, string>)[k]).toBeTruthy();
}
});
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);
});
});