jim800121chen 9ab1a11ea4 feat(models): 補 7 個預設模型(B8)+ 詳細頁下載按鈕(B5)
# B8 預設模型(簡單版:寫死常數 + 打包 + 不簽 token)
模型庫「預設模型」原本實際是空的(舊 seed 是 demo 假資料、production 不啟用)。
補 7 個公用預設模型(kl520×4 + kl720×3,來源 local-tool models.json):
- metadata 寫死成 Go 常數(presets.go),不進 DB → 公用、不可刪、無 seed 重複
- 7 個 .nef 打包進 image(assets/preset-models/,~61MB;Dockerfile COPY)
- download 走 visionA 自己(簡單版不簽 token,preset 公用本不需授權):
  download handler 三分支 preset→visionA URL / converted→FAA / uploaded→501
- 新 GET /preset-models/*filepath 靜態 serve(無 auth、Content-Disposition
  attachment 用 mime.FormatMediaType、path-traversal 防禦)
- list/get 含 preset(對所有人可見)

# B5 詳細頁下載按鈕 + preset 可下載
- model-detail-client.tsx 補下載按鈕(行為對齊列表卡片)
- isModelDownloadable 加 preset(source==="preset" → true)
- normalizeModelSummary 對 target_chip toLowerCase(修 preset 大寫 KL520
  在小寫晶片篩選下被隱藏的 bug;收斂所有來源大小寫)

# 測試
backend:presets/preset 下載三分支/靜態 serve/path-traversal/條件對稱 全綠
frontend:詳細頁下載鈕 + preset 可下載 + target_chip 大小寫篩選回歸 38 PASS
Reviewer 兩輪通過(Major-1 + Minor-1/2 修畢、複審 0 問題)

# backlog(未做的優化)
preset 下載改 HMAC presigned / preset .nef 改放 FAA 減 image / metadata 後台可管理

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:38:25 +08:00

77 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestPresetModels_Count 驗證恰好 7 個系統預設模型。
func TestPresetModels_Count(t *testing.T) {
got := PresetModels()
require.Len(t, got, 7)
}
// TestPresetModels_AllSourcePresetNoOwner 驗證每筆 preset 都是公用Source=preset、無 owner、有 StorageKey、無 FAAObjectKey
func TestPresetModels_AllSourcePresetNoOwner(t *testing.T) {
for _, m := range PresetModels() {
assert.Equal(t, SourcePreset, m.Source, "preset %s source", m.ID)
assert.Empty(t, m.OwnerUserID, "preset %s should have no owner", m.ID)
assert.Empty(t, m.FAAObjectKey, "preset %s should not have FAAObjectKey", m.ID)
assert.Equal(t, "models/preset/"+m.ID+".nef", m.StorageKey, "preset %s storage key", m.ID)
assert.NotEmpty(t, m.Name, "preset %s name", m.ID)
assert.NotEmpty(t, m.TargetChip, "preset %s target chip", m.ID)
assert.Greater(t, m.FileSize, int64(0), "preset %s file size", m.ID)
}
}
// TestPresetModels_ExpectedIDs 驗證 7 個 id 與 models.json 對齊(順序固定)。
func TestPresetModels_ExpectedIDs(t *testing.T) {
want := []string{
"kl520-yolov5-detection",
"kl520-fcos-detection",
"kl520-ssd-face-detection",
"kl520-tiny-yolov3",
"kl720-yolov5-detection",
"kl720-resnet18-classification",
"kl720-fcos-detection",
}
got := PresetModels()
require.Len(t, got, len(want))
for i, id := range want {
assert.Equal(t, id, got[i].ID, "preset[%d] id", i)
}
}
// TestPresetByID_FoundAndNotFound 驗證查找。
func TestPresetByID_FoundAndNotFound(t *testing.T) {
m, ok := PresetByID("kl720-resnet18-classification")
require.True(t, ok)
assert.Equal(t, "ImageNet Classification ResNet18 (KL720)", m.Name)
assert.Equal(t, "KL720", m.TargetChip)
assert.Equal(t, []int{1, 3, 224, 224}, m.InputShape)
_, ok = PresetByID("not-a-preset")
assert.False(t, ok)
assert.True(t, IsPresetID("kl520-yolov5-detection"))
assert.False(t, IsPresetID("random-uuid"))
}
// TestPresetModels_ReturnsDeepCopy 驗證回傳是深拷貝——改動回傳值不影響後續呼叫(保護共用常數)。
func TestPresetModels_ReturnsDeepCopy(t *testing.T) {
a, ok := PresetByID("kl520-yolov5-detection")
require.True(t, ok)
origLen := len(a.Classes)
a.Name = "MUTATED"
a.Classes = append(a.Classes, "injected")
a.InputShape[0] = 999
b, ok := PresetByID("kl520-yolov5-detection")
require.True(t, ok)
assert.NotEqual(t, "MUTATED", b.Name, "回傳值修改不應污染共用常數")
assert.Len(t, b.Classes, origLen, "Classes slice 應為獨立拷貝")
assert.Equal(t, 1, b.InputShape[0], "InputShape slice 應為獨立拷貝")
}