從 edge-ai-platform POC 轉為正式產品的雲端後端,含以下整合階段:
- Phase 0:雛形骨架 — `cmd/api-server` (REST :3721) + `cmd/remote-proxy`
(tunnel :3800 / internal :3801) 雙 binary 共用 internal/,沿用 POC 的
WebSocket+yamux tunnel 協定但解耦 relay 與 API
- Phase 0.6:OIDC BFF 接 Innovedus Member Center
- internal/oidc package(coreos/go-oidc + PKCE S256 + state + nonce)
- internal/usersession package(HMAC-SHA256 cookie + RotateSessionID
防 session fixation, OWASP ASVS V3.2.1)
- 4 個 OIDC handler(/api/auth/login|callback|me|logout)+ AuthMiddleware
- 完全拔除 StaticAuthProvider,OIDC 是唯一認證路徑
- 9 個 ADR(含 ADR-010 BFF / ADR-011 取代 static auth /
ADR-012 pending session shared cookie / ADR-013 PKCE-only public client)
- Phase 0.7:A1 改造 + security audit 修復
- OIDC ClientSecret 變選填,支援 stage MC 的 public PKCE-only client
(AuthStyleInParams 強制 token endpoint 不送 client_secret)
- 預留 ServiceClient* 欄位給未來 client_credentials grant
- 移除 13+ 處 resolveUserID(uc, StaticUserID) fallback 改 strict mode
(Audit C1:multi-tenant 隔離破口)
- Pairing exchange MarkUsed 失敗 abort + revoke session token(Audit M3)
- 新增 all_endpoints_require_auth_test 整合測試(51 endpoint × 401)
驗證:go test -race -count=3 ./... 17 packages 全綠 / go vet 0 warning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package model
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestInMemoryRepository_SaveAndGet(t *testing.T) {
|
||
ctx := context.Background()
|
||
r := NewInMemoryRepository()
|
||
|
||
m := &Model{
|
||
ID: "m-1",
|
||
OwnerUserID: "user-1",
|
||
Name: "yolo-v5",
|
||
StorageKey: "models/user-1/m-1.nef",
|
||
FileSize: 1024 * 1024,
|
||
Source: SourceUploaded,
|
||
TargetChip: "kl520",
|
||
}
|
||
require.NoError(t, r.Save(ctx, m))
|
||
|
||
got, err := r.Get(ctx, "m-1")
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "yolo-v5", got.Name)
|
||
assert.False(t, got.CreatedAt.IsZero())
|
||
}
|
||
|
||
func TestInMemoryRepository_Get_NotFound(t *testing.T) {
|
||
r := NewInMemoryRepository()
|
||
_, err := r.Get(context.Background(), "nope")
|
||
assert.ErrorIs(t, err, ErrNotFound)
|
||
}
|
||
|
||
func TestInMemoryRepository_List_Filter(t *testing.T) {
|
||
ctx := context.Background()
|
||
r := NewInMemoryRepository()
|
||
|
||
require.NoError(t, r.Save(ctx, &Model{ID: "1", OwnerUserID: "u1", TargetChip: "kl520", Source: SourceUploaded}))
|
||
require.NoError(t, r.Save(ctx, &Model{ID: "2", OwnerUserID: "u1", TargetChip: "kl720", Source: SourceConverted}))
|
||
require.NoError(t, r.Save(ctx, &Model{ID: "3", OwnerUserID: "u2", TargetChip: "kl520", Source: SourceUploaded}))
|
||
|
||
// 依 owner 過濾
|
||
list, err := r.List(ctx, ListFilter{OwnerUserID: "u1"})
|
||
require.NoError(t, err)
|
||
assert.Len(t, list, 2)
|
||
|
||
// 依 chip 過濾
|
||
list, err = r.List(ctx, ListFilter{OwnerUserID: "u1", TargetChip: "kl520"})
|
||
require.NoError(t, err)
|
||
assert.Len(t, list, 1)
|
||
assert.Equal(t, "1", list[0].ID)
|
||
|
||
// 依 source 過濾
|
||
list, err = r.List(ctx, ListFilter{Source: SourceConverted})
|
||
require.NoError(t, err)
|
||
assert.Len(t, list, 1)
|
||
|
||
// 無 owner 過濾(admin 用)
|
||
list, err = r.List(ctx, ListFilter{})
|
||
require.NoError(t, err)
|
||
assert.Len(t, list, 3)
|
||
}
|
||
|
||
func TestInMemoryRepository_Delete_SoftDelete(t *testing.T) {
|
||
ctx := context.Background()
|
||
r := NewInMemoryRepository()
|
||
|
||
require.NoError(t, r.Save(ctx, &Model{ID: "1", OwnerUserID: "u"}))
|
||
require.NoError(t, r.Delete(ctx, "1"))
|
||
|
||
_, err := r.Get(ctx, "1")
|
||
assert.ErrorIs(t, err, ErrNotFound)
|
||
|
||
list, _ := r.List(ctx, ListFilter{OwnerUserID: "u"})
|
||
assert.Empty(t, list)
|
||
}
|
||
|
||
func TestInMemoryRepository_Save_RequiresID(t *testing.T) {
|
||
r := NewInMemoryRepository()
|
||
assert.Error(t, r.Save(context.Background(), &Model{Name: "no-id"}))
|
||
}
|
||
|
||
func TestSizeValidator_Check(t *testing.T) {
|
||
v := NewSizeValidator(100) // 100 MB
|
||
|
||
// 剛好在 limit 以內
|
||
assert.NoError(t, v.Check(100*1024*1024))
|
||
assert.NoError(t, v.Check(50*1024*1024))
|
||
|
||
// 超過
|
||
err := v.Check(101 * 1024 * 1024)
|
||
assert.ErrorIs(t, err, ErrFileTooLarge)
|
||
|
||
// 大小為 0 不是錯誤(Repository 不管)
|
||
assert.NoError(t, v.Check(0))
|
||
}
|
||
|
||
func TestSizeValidator_NoLimit(t *testing.T) {
|
||
v := NewSizeValidator(0) // 0 or negative 視為無限制
|
||
assert.NoError(t, v.Check(1024*1024*1024*10)) // 10 GB
|
||
}
|