import { describe, expect, it } from "vitest"; import { MOTHERSHIP_LIMITS, validateQuoteInput, rejectQuantityField, } from "@/modules/quote/validation"; import { ValidationError } from "@/modules/quote/types"; import { MOTHERSHIP_DELIVERY_CONFIRMED, MOTHERSHIP_PICKUP_CONFIRMED, } from "@/__tests__/fixtures/mothership-address"; const VALID_BODY = { request_id: "550e8400-e29b-41d4-a716-446655440000", customer_id: "CUST_001", pickup_address: { street: "1234 Warehouse Blvd", city: "Los Angeles", state: "CA", zip: "90001", place_id: "ChIJ_pickup", formatted_address: "1234 Warehouse Blvd, Los Angeles, CA 90001, USA", selected_from_suggestions: true, ...MOTHERSHIP_PICKUP_CONFIRMED, }, delivery_address: { street: "5678 Distribution Dr", city: "Dallas", state: "TX", zip: "75201", place_id: "ChIJ_delivery", formatted_address: "5678 Distribution Dr, Dallas, TX 75201, USA", selected_from_suggestions: true, ...MOTHERSHIP_DELIVERY_CONFIRMED, }, weight: { value: 500, unit: "lb" }, dimensions: { length: 48, width: 40, height: 48, unit: "in" }, pallet_count: 2, cargo_type: "general_freight", }; describe("validateQuoteInput", () => { it("正常入参通过", () => { const result = validateQuoteInput(VALID_BODY); expect(result.cargoHash).toHaveLength(32); expect(result.weightLb).toBe(500); }); it("保留业务客户标识但不进入 cargoHash", () => { const a = validateQuoteInput({ ...VALID_BODY, business_customer_id: "BC_001", }); const b = validateQuoteInput({ ...VALID_BODY, business_customer_id: "BC_002", }); expect(a.businessCustomerId).toBe("BC_001"); expect(b.businessCustomerId).toBe("BC_002"); expect(a.cargoHash).toBe(b.cargoHash); }); it("TC-201:weight 缺失 → VALIDATION_FAILED", () => { const { weight: _w, ...rest } = VALID_BODY; expect(() => validateQuoteInput(rest)).toThrow(ValidationError); }); it("TC-202:weight=0 → 无效", () => { expect(() => validateQuoteInput({ ...VALID_BODY, weight: { value: 0, unit: "lb" }, }), ).toThrow(/有效的重量|超出允许范围/); }); it("空邮编通过(MotherShip 不要求填写邮编)", () => { const result = validateQuoteInput({ ...VALID_BODY, pickup_address: { ...VALID_BODY.pickup_address, zip: "" }, delivery_address: { ...VALID_BODY.delivery_address, zip: "" }, }); expect(result.cargoHash).toHaveLength(32); }); it("TC-203:zip 4 位 → 无效", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pickup_address: { ...VALID_BODY.pickup_address, zip: "9001", }, }), ).toThrow(/邮编/); }); it("TC-204:request_id 非 UUID → 无效", () => { expect(() => validateQuoteInput({ ...VALID_BODY, request_id: "not-uuid" }), ).toThrow("请求标识无效"); }); it("E4.3:selected_from_suggestions=false", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pickup_address: { ...VALID_BODY.pickup_address, selected_from_suggestions: false, }, }), ).toThrow(ValidationError); }); it("未确认 MotherShip 精确地址 → 拒绝询价", () => { const { mothership_option_id: _a, mothership_display_label: _b, selected_from_mothership: _c, ...pickup } = VALID_BODY.pickup_address; expect(() => validateQuoteInput({ ...VALID_BODY, pickup_address: pickup, }), ).toThrow(ValidationError); }); it("E4.7:quantity 字段拒绝", () => { expect(() => rejectQuantityField({ ...VALID_BODY, quantity: 5 }), ).toThrow("请使用托盘数 pallet_count"); }); it("E4.6:pallet_count 可为 >25(已取消 1–25 硬限)", () => { const result = validateQuoteInput({ ...VALID_BODY, pallet_count: 26 }); expect(result.palletCount).toBe(26); }); it("E4.6:pallet_count 非正整数拒绝", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pallet_count: 0 }), ).toThrow(/托盘数须为正整数/); }); it("E4.6:单托重量超 9999", () => { expect(() => validateQuoteInput({ ...VALID_BODY, weight: { value: 10_000, unit: "lb" }, }), ).toThrow(/重量超出/); }); it("E4.6:长度超 999 in", () => { expect(() => validateQuoteInput({ ...VALID_BODY, dimensions: { length: 1000, width: 40, height: 48, unit: "in" }, }), ).toThrow(/尺寸超出/); }); it("边界:25 托、9999 lb、999×99×99 in 通过", () => { const result = validateQuoteInput({ ...VALID_BODY, weight: { value: 9_999, unit: "lb" }, dimensions: { length: 999, width: 99, height: 99, unit: "in" }, pallet_count: 25, }); expect(result.palletCount).toBe(25); }); it("SQL 注入拦截", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pickup_address: { ...VALID_BODY.pickup_address, street: "'; DROP TABLE users; --", }, }), ).toThrow(/无效,请检查/); }); it("kg/cm 换算并向上取整", () => { const result = validateQuoteInput({ ...VALID_BODY, weight: { value: 227, unit: "kg" }, dimensions: { length: 120, width: 100, height: 100, unit: "cm" }, }); expect(result.weightLb).toBe(501); expect(result.dimLIn).toBe(48); expect(result.dimWIn).toBe(40); expect(result.dimHIn).toBe(40); }); it("500 kg 换算为 1103 lb", () => { const result = validateQuoteInput({ ...VALID_BODY, weight: { value: 500, unit: "kg" }, }); expect(result.weightLb).toBe(1103); }); it("0.5 t 换算为 1103 lb", () => { const result = validateQuoteInput({ ...VALID_BODY, weight: { value: 0.5, unit: "t" }, }); expect(result.weightLb).toBe(1103); }); it("1.2 m 尺寸换算并向上取整", () => { const result = validateQuoteInput({ ...VALID_BODY, dimensions: { length: 1.2, width: 1, height: 1, unit: "m" }, }); expect(result.dimLIn).toBe(48); expect(result.dimWIn).toBe(40); expect(result.dimHIn).toBe(40); }); it("拒绝 ton/tons 歧义单位", () => { expect(() => validateQuoteInput({ ...VALID_BODY, weight: { value: 0.5, unit: "ton" }, }), ).toThrow(/歧义/); }); it("拒绝 Infinity 重量", () => { expect(() => validateQuoteInput({ ...VALID_BODY, weight: { value: Number.POSITIVE_INFINITY, unit: "lb" }, }), ).toThrow(ValidationError); }); it("登录态 cargo_lines:托数>25 不硬拒,单件均重≤5000 可通过", () => { const result = validateQuoteInput({ ...VALID_BODY, pallet_count: 40, weight: { value: 400, unit: "lb" }, cargo_lines: [ { cargo_type: "pallet", quantity: 40, weight_lb: 400, length_in: 48, width_in: 40, height_in: 48, }, ], }); expect(result.palletCount).toBe(40); }); it("二级刷价仅 mothership_details:托数>25 不硬拒(无 cargo_lines)", () => { const result = validateQuoteInput({ ...VALID_BODY, pallet_count: 107, mothership_details: { pickup: { company_name: "Acme", contact_email: "a@b.com", contact_phone: "555", opens_at: "8:00 AM", closes_at: "5:00 PM", }, delivery: { company_name: "Beta", contact_email: "c@d.com", contact_phone: "555", opens_at: "9:00 AM", closes_at: "5:00 PM", }, }, }); expect(result.palletCount).toBe(107); }); it("登录态 cargo_lines:混装托盘+纸箱,pallet_count=托盘合计可通过", () => { const result = validateQuoteInput({ ...VALID_BODY, pallet_count: 16, weight: { value: 12, unit: "lb" }, dimensions: { length: 2, width: 2, height: 3, unit: "in" }, cargo_lines: [ { cargo_type: "carton", quantity: 4, weight_lb: 12, length_in: 2, width_in: 2, height_in: 3, }, { cargo_type: "pallet", quantity: 2, weight_lb: 6, length_in: 12, width_in: 12, height_in: 12, }, { cargo_type: "pallet", quantity: 5, weight_lb: 31, length_in: 31, width_in: 31, height_in: 31, }, { cargo_type: "carton", quantity: 22, weight_lb: 24, length_in: 23, width_in: 23, height_in: 23, }, { cargo_type: "carton", quantity: 33, weight_lb: 33, length_in: 33, width_in: 33, height_in: 33, }, { cargo_type: "pallet", quantity: 6, weight_lb: 56, length_in: 55, width_in: 55, height_in: 55, }, { cargo_type: "pallet", quantity: 3, weight_lb: 66, length_in: 66, width_in: 66, height_in: 66, }, { cargo_type: "carton", quantity: 32, weight_lb: 69, length_in: 21, width_in: 12, height_in: 55, }, ], }); expect(result.palletCount).toBe(16); expect(result.cargoLines?.length).toBe(8); }); it("登录态 cargo_lines:单件均重>5000 拒绝", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pallet_count: 1, weight: { value: 5001, unit: "lb" }, cargo_lines: [ { cargo_type: "pallet", quantity: 1, weight_lb: 5001, length_in: 48, width_in: 40, height_in: 48, }, ], }), ).toThrow(/单件平均重量超过 5000/); }); it("登录态 cargo_lines:整票总重>45000 拒绝", () => { expect(() => validateQuoteInput({ ...VALID_BODY, pallet_count: 40, weight: { value: 1200, unit: "lb" }, cargo_lines: [ { cargo_type: "pallet", quantity: 40, weight_lb: 1200, length_in: 48, width_in: 40, height_in: 48, }, ], }), ).toThrow(/53 英尺货车最大载重/); }); }); describe("MOTHERSHIP_LIMITS", () => { it("托盘数不再以 25 为硬上限", () => { expect(MOTHERSHIP_LIMITS.palletCount.min).toBe(1); expect(MOTHERSHIP_LIMITS.palletCount.max).toBeGreaterThan(25); }); });