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>
60 lines
2.1 KiB
Go
60 lines
2.1 KiB
Go
// memPairingExchanger 的單元測試(DB-on FK 收尾,問題 #2)。
|
||
//
|
||
// 不帶 build tag:屬於預設 `go test ./...` 範圍(無需 Docker)。
|
||
// 驗證 in-memory exchanger 自建 device + 建 session token、且 session token 綁到該 device,
|
||
// 與 pairing_exchange_db_test.go 的 Postgres dbtest 對齊行為。
|
||
package api
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
|
||
"visiona-backend/internal/auth"
|
||
"visiona-backend/internal/device"
|
||
)
|
||
|
||
func TestMemExchange_ProvisionCreatesDeviceAndSession(t *testing.T) {
|
||
ctx := context.Background()
|
||
devRepo := device.NewInMemoryRepository()
|
||
sessions := auth.NewInMemorySessionTokenStore()
|
||
exchanger := NewInMemoryPairingExchanger(devRepo, sessions)
|
||
|
||
res, err := exchanger.Provision(ctx, "owner-1", "parent-hash", auth.SessionTokenTTL)
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, res.DeviceID)
|
||
require.NotEmpty(t, res.SessionPlaintext)
|
||
require.NotNil(t, res.SessionInfo)
|
||
|
||
// device 自建、owner 對齊
|
||
dev, err := devRepo.Get(ctx, res.DeviceID)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "owner-1", dev.OwnerUserID)
|
||
assert.Equal(t, defaultPairedDeviceName, dev.Name)
|
||
assert.Equal(t, defaultPairedDeviceType, dev.DeviceType)
|
||
assert.NotNil(t, dev.PairedAt)
|
||
|
||
// session token 綁到自建 device
|
||
tok, err := sessions.Get(ctx, res.SessionPlaintext)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, res.DeviceID, tok.DeviceID, "session token 應綁到自建 device")
|
||
assert.Equal(t, "owner-1", tok.UserID)
|
||
assert.Equal(t, "parent-hash", tok.ParentTokenHash)
|
||
}
|
||
|
||
func TestMemExchange_Provision_DistinctDevices(t *testing.T) {
|
||
ctx := context.Background()
|
||
exchanger := NewInMemoryPairingExchanger(
|
||
device.NewInMemoryRepository(), auth.NewInMemorySessionTokenStore())
|
||
|
||
res1, err := exchanger.Provision(ctx, "owner-1", "", auth.SessionTokenTTL)
|
||
require.NoError(t, err)
|
||
res2, err := exchanger.Provision(ctx, "owner-1", "", auth.SessionTokenTTL)
|
||
require.NoError(t, err)
|
||
|
||
assert.NotEqual(t, res1.DeviceID, res2.DeviceID)
|
||
assert.NotEqual(t, res1.SessionPlaintext, res2.SessionPlaintext)
|
||
}
|