fix(frontend): normalizeDevice serialNumber 加 trim(WP-C Minor #1)

純空白字串(" ")原判 truthy → 會以空白 serial 組出必失敗的請求路由。
改為先 trim 再判定:serial !== "" ? serial : null。順帶消除字面 "0"
被 falsy 誤判為 null 的隱性行為。補純空白 → null 邊界測試。

Reviewer 通過(0C/0M/0Mi)。tsc/eslint 0、18 test 綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-07-16 11:40:35 +08:00
parent 26b433eb10
commit 51dfd0f2d8
2 changed files with 9 additions and 4 deletions

View File

@ -175,7 +175,7 @@ describe("useDeviceStore", () => {
});
});
it("serial_number 正規化snake_case / camelCase 都吃、缺欄位與空字串 → nullWP-C", async () => {
it("serial_number 正規化snake_case / camelCase 都吃、缺欄位與空字串/純空白 → nullWP-C", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
jsonResponse({
success: true,
@ -188,6 +188,8 @@ describe("useDeviceStore", () => {
{ id: "dev-3", name: "C", type: "kl520", status: "connected" },
// 空字串(防禦性;無法路由)→ null
{ id: "dev-4", name: "D", type: "kl520", status: "connected", serial_number: "" },
// 純空白防禦性trim 後為空、無法路由)→ null
{ id: "dev-5", name: "E", type: "kl520", status: "connected", serial_number: " " },
],
}),
);
@ -198,6 +200,7 @@ describe("useDeviceStore", () => {
expect(devices[1]?.serialNumber).toBe("KN99887766");
expect(devices[2]?.serialNumber).toBeNull();
expect(devices[3]?.serialNumber).toBeNull();
expect(devices[4]?.serialNumber).toBeNull();
});
it("fetchDevices 遇到 501 NOT_IMPLEMENTED 時視為空 list不記錯誤", async () => {

View File

@ -112,14 +112,16 @@ function normalizeDevice(raw: unknown): Device {
const rawRemoteStatus = pick<string>("remote_status", "remoteStatus") as
| RemoteStatus
| undefined;
// serial_number 為 omitempty缺欄位 → null防禦性把空字串也視為 null
// (空字串無法路由,語意上等同「未回報序號」)。
// serial_number 為 omitempty缺欄位 → null防禦性把空字串純空白也視為 null
// (空字串與空白皆無法路由,語意上等同「未回報序號」)。先 trim 再判定,
// 避免純空白(" ")誤判為 truthy 而以空白 serial 組出必失敗的請求路由。
const rawSerial = pick<string>("serial_number", "serialNumber");
const serial = rawSerial != null ? String(rawSerial).trim() : "";
return {
id: String(pick<string>("id") ?? ""),
name: String(pick<string>("name") ?? pick<string>("device_name") ?? ""),
alias: pick<string>("alias") ?? undefined,
serialNumber: rawSerial ? String(rawSerial) : null,
serialNumber: serial !== "" ? serial : null,
type: String(pick<string>("type", "device_type") ?? ""),
status: (pick<string>("status") as DeviceHardwareStatus) ?? "disconnected",
remoteStatus: