DB 接入塊 0-5 上主幹後的收尾工作,讓 DB-on 模式可真人使用 + 補齊功能與測試。 OIDC / pairing FK 修復(接 DB 上線必要): - 新建 internal/user package(User + Store + InMemory + Postgres);OIDC callback 驗證 id_token 成功後 fail-closed upsert users(sub 直接當 users.id,MC sub 為 UUID) - pairing exchange 雲端自建 device(不動 local-tool)+ 同 tx 綁 session token; 自建 device 空 serial 寫 NULL(避免撞 partial unique) - device.SaveTx / session.CreateTx 新增 tx-aware 版本 B4 model metadata: - 轉檔 result 的 analysis_info(input_shape/classes/framework)串進 model: converter_client → flow → adapter → model.Model → PG → ModelResponse DTO - input_shape 優先用陣列、後備四維組 NCHW、缺一不亂組;全 optional 防禦性 - 前端詳細頁顯示(另 repo);轉檔端串接交接檔 b4-converter-handoff.md nginx healthz(部署層): - 新增 /healthz/deep 轉發 backend(ping PG+Redis、down 回 503)給 LB - 修掉 default_server return 444 短路 bug(docker healthcheck 長期 unhealthy 真因) storage error 統一映射(不洩漏 storage 後端細節)。 測試:補 internal/api(storage/errors handler)、cmd/api-server(seed/adapter)、 internal/db(redis)、relay/session 弱處,含 testcontainers integration。 DB 接入相關 package 真環境覆蓋達 88-94%。全程 Reviewer 審查 + 130 真 PG/Redis dbtest 綠。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
2.8 KiB
Go
82 lines
2.8 KiB
Go
// InMemoryStore 的單元測試(DB-on FK 收尾,問題 #1)。
|
||
//
|
||
// 不帶 build tag:屬於預設 `go test ./...` 範圍(無需 Docker)。
|
||
// 驗證 Upsert / Get 的核心語意,並與 postgres_store_db_test.go 的 dbtest 對齊行為。
|
||
package user
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestInMemoryStore_UpsertAndGet(t *testing.T) {
|
||
ctx := context.Background()
|
||
s := NewInMemoryStore()
|
||
|
||
in := &User{ID: "sub-001", Email: "alice@example.com", Name: "Alice", Roles: []string{"admin"}}
|
||
require.NoError(t, s.Upsert(ctx, in))
|
||
|
||
got, err := s.Get(ctx, "sub-001")
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "sub-001", got.ID)
|
||
assert.Equal(t, "alice@example.com", got.Email)
|
||
assert.Equal(t, "Alice", got.Name)
|
||
assert.Equal(t, []string{"admin"}, got.Roles)
|
||
assert.False(t, got.CreatedAt.IsZero(), "CreatedAt 應自動填入")
|
||
assert.False(t, got.UpdatedAt.IsZero(), "UpdatedAt 應自動填入")
|
||
}
|
||
|
||
func TestInMemoryStore_Upsert_UpdatesAndPreservesCreatedAt(t *testing.T) {
|
||
ctx := context.Background()
|
||
s := NewInMemoryStore()
|
||
|
||
require.NoError(t, s.Upsert(ctx, &User{ID: "sub-002", Email: "old@example.com", Name: "Old"}))
|
||
first, err := s.Get(ctx, "sub-002")
|
||
require.NoError(t, err)
|
||
origCreated := first.CreatedAt
|
||
|
||
// 同 ID 再 upsert:更新 email/name,保留 created_at。
|
||
require.NoError(t, s.Upsert(ctx, &User{ID: "sub-002", Email: "new@example.com", Name: "New"}))
|
||
second, err := s.Get(ctx, "sub-002")
|
||
require.NoError(t, err)
|
||
|
||
assert.Equal(t, "new@example.com", second.Email, "email 應更新")
|
||
assert.Equal(t, "New", second.Name, "name 應更新")
|
||
assert.Equal(t, origCreated, second.CreatedAt, "created_at 應保留")
|
||
}
|
||
|
||
func TestInMemoryStore_Get_NotFound(t *testing.T) {
|
||
ctx := context.Background()
|
||
s := NewInMemoryStore()
|
||
|
||
_, err := s.Get(ctx, "nope")
|
||
assert.ErrorIs(t, err, ErrNotFound)
|
||
}
|
||
|
||
func TestInMemoryStore_Upsert_RequiresIDAndEmail(t *testing.T) {
|
||
ctx := context.Background()
|
||
s := NewInMemoryStore()
|
||
|
||
assert.Error(t, s.Upsert(ctx, &User{ID: "", Email: "x@y.z"}), "缺 ID 應回錯")
|
||
assert.Error(t, s.Upsert(ctx, &User{ID: "sub", Email: ""}), "缺 email 應回錯(users.email NOT NULL)")
|
||
assert.Error(t, s.Upsert(ctx, nil), "nil 應回錯")
|
||
}
|
||
|
||
// TestInMemoryStore_Upsert_RolesCopied 確認 Upsert/Get 對 roles 做 copy,
|
||
// 外部後續修改傳入 slice 不影響 store 內容(對齊 in-memory copy 語意)。
|
||
func TestInMemoryStore_Upsert_RolesCopied(t *testing.T) {
|
||
ctx := context.Background()
|
||
s := NewInMemoryStore()
|
||
|
||
roles := []string{"a", "b"}
|
||
require.NoError(t, s.Upsert(ctx, &User{ID: "sub-003", Email: "c@d.e", Roles: roles}))
|
||
roles[0] = "MUTATED" // 外部改傳入 slice
|
||
|
||
got, err := s.Get(ctx, "sub-003")
|
||
require.NoError(t, err)
|
||
assert.Equal(t, []string{"a", "b"}, got.Roles, "store 內 roles 不應被外部 mutation 影響")
|
||
}
|