|
|
/**
|
|
|
* 真实 RPA 冒烟:POST /api/quotes → 轮询至 done/failed/超时
|
|
|
* 前置:RPA_MOCK_MODE=false,worker:rpa 已启动
|
|
|
*/
|
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
|
|
const BASE = process.env.SMOKE_BASE_URL ?? "http://localhost:3000";
|
|
|
const TOKEN = "demo-host-token";
|
|
|
const POLL_MS = 2_000;
|
|
|
const TIMEOUT_MS = 90_000;
|
|
|
|
|
|
const body = {
|
|
|
request_id: randomUUID(),
|
|
|
customer_id: "CUST_001",
|
|
|
pickup_address: {
|
|
|
street: "1234 Warehouse Blvd",
|
|
|
city: "Los Angeles",
|
|
|
state: "CA",
|
|
|
zip: "90001",
|
|
|
place_id: "place_la_90001",
|
|
|
formatted_address: "1234 Warehouse Blvd, Los Angeles, CA 90001",
|
|
|
selected_from_suggestions: true,
|
|
|
},
|
|
|
delivery_address: {
|
|
|
street: "5678 Distribution Dr",
|
|
|
city: "Dallas",
|
|
|
state: "TX",
|
|
|
zip: "75201",
|
|
|
place_id: "place_dallas_75201",
|
|
|
formatted_address: "5678 Distribution Dr, Dallas, TX 75201",
|
|
|
selected_from_suggestions: true,
|
|
|
},
|
|
|
weight: { value: 500, unit: "lb" as const },
|
|
|
dimensions: { length: 48, width: 40, height: 48, unit: "in" as const },
|
|
|
pallet_count: 2,
|
|
|
cargo_type: "general_freight",
|
|
|
};
|
|
|
|
|
|
async function main(): Promise<void> {
|
|
|
if (process.env.RPA_MOCK_MODE === "true") {
|
|
|
console.error("[FAIL] RPA_MOCK_MODE=true,请设为 false 后再跑真实冒烟");
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
const headers = {
|
|
|
Authorization: `Bearer ${TOKEN}`,
|
|
|
"Content-Type": "application/json",
|
|
|
};
|
|
|
|
|
|
console.log("[1/3] POST /api/quotes ...");
|
|
|
const createRes = await fetch(`${BASE}/api/quotes`, {
|
|
|
method: "POST",
|
|
|
headers,
|
|
|
body: JSON.stringify(body),
|
|
|
});
|
|
|
const created = (await createRes.json()) as {
|
|
|
code: number;
|
|
|
message: string;
|
|
|
data?: { quote_id: string; status: string; source_type?: string };
|
|
|
};
|
|
|
|
|
|
if (created.code !== 0 || !created.data?.quote_id) {
|
|
|
console.error("[FAIL] 创建询价失败:", created);
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
const { quote_id: quoteId, status: createStatus } = created.data;
|
|
|
console.log(` quote_id=${quoteId} status=${createStatus}`);
|
|
|
|
|
|
if (createStatus === "done") {
|
|
|
console.log("[2/3] L2 缓存命中,跳过 RPA");
|
|
|
} else {
|
|
|
console.log("[2/3] 轮询 RPA 结果(最长 90s)...");
|
|
|
}
|
|
|
|
|
|
const started = Date.now();
|
|
|
let lastStatus = createStatus;
|
|
|
|
|
|
while (Date.now() - started < TIMEOUT_MS) {
|
|
|
const detailRes = await fetch(
|
|
|
`${BASE}/api/quotes/${quoteId}?customer_id=CUST_001`,
|
|
|
{ headers: { Authorization: `Bearer ${TOKEN}` } },
|
|
|
);
|
|
|
const detail = (await detailRes.json()) as {
|
|
|
code: number;
|
|
|
data?: {
|
|
|
status: string;
|
|
|
source_type?: string;
|
|
|
is_realtime?: boolean;
|
|
|
quotes?: Array<{ service_level: string; rate_option: string; final_total: number }>;
|
|
|
error_code?: string;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
if (detail.code !== 0 || !detail.data) {
|
|
|
console.error("[FAIL] 查询失败:", detail);
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
lastStatus = detail.data.status;
|
|
|
|
|
|
if (lastStatus === "processing") {
|
|
|
process.stdout.write(".");
|
|
|
await sleep(POLL_MS);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
console.log("");
|
|
|
console.log("[3/3] 结果:");
|
|
|
console.log(
|
|
|
JSON.stringify(
|
|
|
{
|
|
|
status: detail.data.status,
|
|
|
source_type: detail.data.source_type,
|
|
|
is_realtime: detail.data.is_realtime,
|
|
|
error_code: detail.data.error_code,
|
|
|
quotes: detail.data.quotes?.map((q) => ({
|
|
|
tier: `${q.service_level}/${q.rate_option}`,
|
|
|
final_total: q.final_total,
|
|
|
})),
|
|
|
},
|
|
|
null,
|
|
|
2,
|
|
|
),
|
|
|
);
|
|
|
|
|
|
if (lastStatus === "done") {
|
|
|
const count = detail.data.quotes?.length ?? 0;
|
|
|
if (count === 4) {
|
|
|
console.log("[PASS] 真实 RPA 四档报价返回");
|
|
|
process.exit(0);
|
|
|
}
|
|
|
console.error(`[FAIL] 报价档位数=${count},期望 4`);
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
console.error(`[FAIL] 询价终态=${lastStatus}`);
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
console.error(`\n[FAIL] 轮询超时,最后状态=${lastStatus}`);
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
}
|
|
|
|
|
|
main().catch((err) => {
|
|
|
console.error("[FAIL]", err);
|
|
|
process.exit(1);
|
|
|
});
|