import { describe, expect, it } from "vitest"; import { validateFlockQuoteInput, defaultFlockPickupDate, } from "@/modules/flock/validation"; import { ValidationError } from "@/modules/quote/types"; import { kgToLb, toAxelWholePounds } from "@/modules/quote/unit-converter"; function baseBody(overrides: Record = {}) { return { request_id: "550e8400-e29b-41d4-a716-446655440000", customer_id: "CUST_001", flock_input: { pickup_date: defaultFlockPickupDate(), pickup_zip: "90001", delivery_zip: "75201", pallet_count: 6, total_weight: { value: 3500, unit: "lb" }, dimensions: { length: 48, width: 40, height: 48, unit: "in" }, ...overrides, }, }; } describe("validateFlockQuoteInput", () => { it("接受合法英制入参", () => { const parsed = validateFlockQuoteInput(baseBody()); expect(parsed.palletCount).toBe(6); expect(parsed.totalWeightLb).toBe(3500); expect(parsed.lengthIn).toBe(48); }); it("500kg 换算为 1103lb(小数进一)", () => { expect(toAxelWholePounds(kgToLb(500))).toBe(1103); const parsed = validateFlockQuoteInput( baseBody({ total_weight: { value: 500, unit: "kg" } }), ); expect(parsed.totalWeightLb).toBe(1103); }); it("0.5t 换算为 1103lb(小数进一)", () => { const parsed = validateFlockQuoteInput( baseBody({ total_weight: { value: 0.5, unit: "t" } }), ); expect(parsed.totalWeightLb).toBe(1103); }); it("拒绝 ton/tons 歧义单位", () => { expect(() => validateFlockQuoteInput( baseBody({ total_weight: { value: 0.5, unit: "ton" } }), ), ).toThrow(/歧义/); }); it("1.2m 尺寸换算并向上取整", () => { const parsed = validateFlockQuoteInput( baseBody({ dimensions: { length: 1.2, width: 1, height: 1, unit: "m" }, }), ); expect(parsed.lengthIn).toBe(48); expect(parsed.widthIn).toBe(40); expect(parsed.heightIn).toBe(40); }); it("托盘数越界返回中文提示", () => { expect(() => validateFlockQuoteInput(baseBody({ pallet_count: 3 })), ).toThrow(ValidationError); expect(() => validateFlockQuoteInput(baseBody({ pallet_count: 3 })), ).toThrow(/4 到 20/); expect(() => validateFlockQuoteInput(baseBody({ pallet_count: 21 })), ).toThrow(/4 到 20/); }); it("总重超 45000lb 拒绝", () => { expect(() => validateFlockQuoteInput( baseBody({ total_weight: { value: 45001, unit: "lb" } }), ), ).toThrow(/45000/); }); it("尺寸超限拒绝", () => { expect(() => validateFlockQuoteInput( baseBody({ dimensions: { length: 637, width: 40, height: 48, unit: "in" }, }), ), ).toThrow(/636/); expect(() => validateFlockQuoteInput( baseBody({ dimensions: { length: 48, width: 103, height: 48, unit: "in" }, }), ), ).toThrow(/102/); expect(() => validateFlockQuoteInput( baseBody({ dimensions: { length: 48, width: 40, height: 109, unit: "in" }, }), ), ).toThrow(/108/); }); it("线性英尺超拖车拒绝(对应官网 Linear feet 报错)", () => { expect(() => validateFlockQuoteInput( baseBody({ pallet_count: 18, dimensions: { length: 403, width: 102, height: 108, unit: "in" }, }), ), ).toThrow(/占用车长|挂车长度/); }); it("缺少 ZIP 提示中文", () => { expect(() => validateFlockQuoteInput(baseBody({ pickup_zip: "" })), ).toThrow(/提货邮编/); }); it("提货日期早于今天拒绝", () => { const d = new Date(); d.setDate(d.getDate() - 1); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); const yyyy = d.getFullYear(); expect(() => validateFlockQuoteInput( baseBody({ pickup_date: `${mm}/${dd}/${yyyy}` }), ), ).toThrow(/不能早于今天/); }); it("提货日期为周末拒绝", () => { const d = new Date(); d.setHours(12, 0, 0, 0); const day = d.getDay(); const daysUntilSat = day === 6 ? 7 : (6 - day + 7) % 7 || 7; d.setDate(d.getDate() + daysUntilSat); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); const yyyy = d.getFullYear(); expect(() => validateFlockQuoteInput( baseBody({ pickup_date: `${mm}/${dd}/${yyyy}` }), ), ).toThrow(/周六或周日/); }); it("登录态调度字段透传", () => { const parsed = validateFlockQuoteInput( baseBody({ form_mode: "logged_in_quick", pallet_count: 2, total_weight: { value: 6000, unit: "lb" }, pickup_service: "during_window", pickup_window_start_time: "8:00 am", pickup_window_end_time: "5:00 pm", delivery_service: "must_arrive_by", delivery_must_arrive_by_date: "2026-08-01", call_for_delivery_appointment: true, pickup_location_type: "residential", delivery_location_type: "limited_farm", }), ); expect(parsed.formMode).toBe("logged_in_quick"); expect(parsed.pickupService).toBe("during_window"); expect(parsed.pickupWindowStartTime).toBe("8:00 am"); expect(parsed.deliveryService).toBe("must_arrive_by"); expect(parsed.deliveryMustArriveByDate).toBe("2026-08-01"); expect(parsed.callForDeliveryAppointment).toBe(true); expect(parsed.pickupLocationType).toBe("residential"); }); it("接受可选 registration 块", () => { const parsed = validateFlockQuoteInput( baseBody({ registration: { email: "test@qq.com", phone: "7536407420", first_name: "Li", }, }), ); expect(parsed.registration?.email).toBe("test@qq.com"); expect(parsed.registration?.phone).toBe("7536407420"); expect(parsed.registration?.firstName).toBe("Li"); }); it("无效邮箱拒绝", () => { expect(() => validateFlockQuoteInput( baseBody({ registration: { email: "not-an-email" } }), ), ).toThrow(/工作邮箱/); }); it("无效电话拒绝", () => { expect(() => validateFlockQuoteInput( baseBody({ registration: { phone: "123" } }), ), ).toThrow(/10 位/); }); });