fix(visionA-frontend): conversion init multipart 對齊 converter 規格 — ref_images + model_id
Phase 0.8b stage e2e 連環 2 個跨層 schema mismatch(5/2 寫 frontend 時對 converter spec 沒驗、5/16 stage e2e 第一次跑通才暴露)。 Bug #5 ref_images field name PHP-style 不對齊 - 現象:converter multer LIMIT_UNEXPECTED_FILE、visionA 透傳 → 400 validation_failed - 原因:frontend `form.append('ref_images[]', img)`、converter `uploader.fields([{name:'ref_images',...}])`(Express/multer 標準、無 []) - 修法:frontend `form.append('ref_images', img)`(FormData 多筆同 key、multer 自動收成陣列) Bug #6 model_id 用 taskName 當值、converter 要 integer - 現象(修完 Bug #5 後暴露):converter validator → 400 validation_error 「model_id 必須為非負整數」 - 原因:frontend `args.taskName ?? args.file.name` → 字串 "input" / "yolov5s_test"; converter validator (`createJob.js:153-164`) 規定 integer 1-65535(KTC tool 內部 model 編號) - 修法:新增 `generateConverterModelId()` helper(Math.random 1-65535)、每次 init 自動生成; taskName 留給 visionA UX(promote 後 model store 的 name),與 converter model_id 解耦 驗證: - pnpm test src/lib/api/conversion.test 22/22 pass - pnpm build 通過 - stage redeploy 兩次(commit ce6a657 後跑出 Bug #5、fix Bug #5 deploy 後跑出 Bug #6、fix Bug #6 已 deploy) 剩餘 e2e 待 user browser 真實上傳 ONNX file 驗證。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ebf46112b
commit
78c1343e9a
@ -151,12 +151,17 @@ describe("initConversion", () => {
|
||||
expect(form).toBeInstanceOf(FormData);
|
||||
// model 檔
|
||||
expect((form!.get("model") as File).name).toBe("yolov5s.onnx");
|
||||
// ref_images[](多筆同 key)
|
||||
const refs = form!.getAll("ref_images[]");
|
||||
// ref_images(多筆同 key — converter multer 'ref_images' field、2026-05-18 從 'ref_images[]' 改正)
|
||||
const refs = form!.getAll("ref_images");
|
||||
expect(refs.length).toBe(1);
|
||||
expect((refs[0] as File).name).toBe("ref.jpg");
|
||||
// text fields — backend 用 `model_id` 欄裝 taskName、`platform` 用 wire 數字格式
|
||||
expect(form!.get("model_id")).toBe("yolov5s_test");
|
||||
// text fields — converter 規格:`model_id` integer 1-65535(2026-05-18 從 taskName 改自動生成)
|
||||
const modelIdStr = form!.get("model_id");
|
||||
expect(typeof modelIdStr).toBe("string");
|
||||
const modelIdNum = Number(modelIdStr);
|
||||
expect(Number.isInteger(modelIdNum)).toBe(true);
|
||||
expect(modelIdNum).toBeGreaterThanOrEqual(1);
|
||||
expect(modelIdNum).toBeLessThanOrEqual(65535);
|
||||
expect(form!.get("platform")).toBe("720");
|
||||
expect(form!.get("version")).toBe("v1.0.0");
|
||||
|
||||
@ -182,10 +187,12 @@ describe("initConversion", () => {
|
||||
expect(job.stage).toBe("onnx");
|
||||
});
|
||||
|
||||
it("沒給 taskName 時用檔名當 model_id", async () => {
|
||||
it("model_id 一律自動生成 1-65535 整數(與 taskName 解耦)", async () => {
|
||||
// 2026-05-18 從「沒 taskName 時用檔名」改成「自動生成」測試。
|
||||
// 理由:converter validator 規定 model_id integer 1-65535、frontend 不該用 taskName / 檔名。
|
||||
const { instances } = installMockXHR();
|
||||
const file = new File(["x"], "model.onnx");
|
||||
const promise = initConversion({ file, targetChip: "KL520" });
|
||||
const promise = initConversion({ file, targetChip: "KL520", taskName: "myTask" });
|
||||
const xhr = instances[0];
|
||||
xhr._fireLoad(200, {
|
||||
success: true,
|
||||
@ -200,7 +207,15 @@ describe("initConversion", () => {
|
||||
},
|
||||
});
|
||||
await promise;
|
||||
expect(xhr._formDataSent!.get("model_id")).toBe("model.onnx");
|
||||
const modelIdStr = xhr._formDataSent!.get("model_id");
|
||||
expect(typeof modelIdStr).toBe("string");
|
||||
const modelIdNum = Number(modelIdStr);
|
||||
// 不應該等於 taskName 或 file.name(converter 拒非整數)
|
||||
expect(modelIdStr).not.toBe("myTask");
|
||||
expect(modelIdStr).not.toBe("model.onnx");
|
||||
expect(Number.isInteger(modelIdNum)).toBe(true);
|
||||
expect(modelIdNum).toBeGreaterThanOrEqual(1);
|
||||
expect(modelIdNum).toBeLessThanOrEqual(65535);
|
||||
expect(xhr._formDataSent!.get("platform")).toBe("520");
|
||||
});
|
||||
|
||||
|
||||
@ -112,6 +112,29 @@ function targetChipToWire(chip: TargetChip): string {
|
||||
return chip.replace(/^KL/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 1-65535 範圍的 converter `model_id`(KTC tool 內部 model 編號)。
|
||||
*
|
||||
* 為什麼這樣設計:
|
||||
* - converter scheduler validator (`createJob.js:153-164`) 規定 `model_id` 必須是
|
||||
* 1 ≤ x ≤ 65535 的整數
|
||||
* - visionA frontend conversion form 沒有對應 UI 欄位(user 從不知這個 ID)
|
||||
* - taskName 是 visionA UX 概念(任務名稱 / promote 後 model store 的 name),
|
||||
* 不能當 converter model_id 用
|
||||
* - 簡單做法:每次 init 都隨機生一個 1-65535、給 converter 用一次性 ID
|
||||
*
|
||||
* 採用 Math.random:
|
||||
* - 不需要密碼學強度的隨機(converter 不靠 model_id 做 auth / dedup)
|
||||
* - 範圍 1-65535(避開 0):Math.floor(Math.random() * 65535) + 1
|
||||
* - 碰撞機率約 1/65535 — 對 user 一次轉檔的場景可接受;converter 端 model_id
|
||||
* 並非唯一鍵(job_id 才是),即使碰撞也只是同號被覆用、不阻擋 e2e
|
||||
*
|
||||
* 2026-05-18 e2e debug 新增。
|
||||
*/
|
||||
function generateConverterModelId(): number {
|
||||
return Math.floor(Math.random() * 65535) + 1;
|
||||
}
|
||||
|
||||
/** backend status `created` / `completed` → UI `queued` / `succeeded` */
|
||||
function normalizeStatus(raw: unknown): ConversionStatus {
|
||||
const v = String(raw ?? "").toLowerCase();
|
||||
@ -204,13 +227,18 @@ export function initConversion(args: InitConversionArgs): Promise<ConversionJob>
|
||||
form.append("model", args.file);
|
||||
if (args.refImages && args.refImages.length > 0) {
|
||||
for (const img of args.refImages) {
|
||||
// backend 接 `ref_images[]`(converter 規格)— FormData 多筆同 key 即可
|
||||
form.append("ref_images[]", img);
|
||||
// converter scheduler multer `uploader.fields([{name:'ref_images',...}])` 預期 name='ref_images'
|
||||
// (不是 PHP/Rails 風格的 'ref_images[]')。FormData 多筆同 key 即可、multer 收成陣列。
|
||||
// 2026-05-18 e2e debug:原本 'ref_images[]' → multer LIMIT_UNEXPECTED_FILE → visionA 回 400 validation_failed
|
||||
form.append("ref_images", img);
|
||||
}
|
||||
}
|
||||
// backend 必填欄位:`model_id`(1–65535 字串使用者編號)+ `version` + `platform`
|
||||
// task spec 用 taskName 作 model_id(轉檔任務的顯示名稱),非 model 庫的 model_id
|
||||
form.append("model_id", args.taskName ?? args.file.name);
|
||||
// backend 必填欄位:`model_id`(converter 規格:integer,範圍 1-65535)+ `version` + `platform`
|
||||
// 2026-05-18 e2e debug:原本送 args.taskName("input" 之類字串)→ converter validator
|
||||
// `model_id 必須為非負整數` 拒絕。taskName 是 visionA UX 概念(轉檔任務顯示名 / promote
|
||||
// 到 model store 的 name),與 converter 內部 KTC tool model_id 是不同 namespace。
|
||||
// 修法:自動生成 1-65535 範圍的隨機整數送給 converter;taskName 留給 promote 流程用。
|
||||
form.append("model_id", String(generateConverterModelId()));
|
||||
form.append("version", args.version ?? "v1.0.0");
|
||||
form.append("platform", targetChipToWire(args.targetChip));
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user