|
|
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<string, unknown> = {}) {
|
|
|
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 位/);
|
|
|
});
|
|
|
});
|