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>
90 lines
3.6 KiB
Go
90 lines
3.6 KiB
Go
// dbon_fk_fix_test.go — DB-on FK 收尾兩個問題的 integration 驗證(in-memory wiring)。
|
||
//
|
||
// 不帶 build tag:屬於預設 `go test ./...` 範圍。用 fixture 的 in-memory store + in-memory
|
||
// exchanger 走真實 OIDC login flow / pairing exchange flow,驗證:
|
||
// - 問題 #1:OIDC callback 成功後 user 被 provision 進 UserStore。
|
||
// - 問題 #2:pairing exchange 成功後自建一筆 device,且該 device owner = 登入 user。
|
||
//
|
||
// DB-on 行為(真 FK / 交易)由 internal/user/postgres_store_db_test.go 與
|
||
// internal/api/pairing_exchange_db_test.go 的 dbtest 覆蓋(待 130 補跑)。
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"net/http"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
|
||
"visiona-backend/internal/api"
|
||
"visiona-backend/internal/auth"
|
||
)
|
||
|
||
// TestDBOnFix_OIDCCallbackProvisionsUser 驗證問題 #1:OIDC callback 成功後 user 進 UserStore。
|
||
func TestDBOnFix_OIDCCallbackProvisionsUser(t *testing.T) {
|
||
f := setupFixture(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
w.WriteHeader(http.StatusNoContent)
|
||
}))
|
||
defer f.Close()
|
||
|
||
// 登入前:user 不存在
|
||
_, err := f.userStore.Get(context.Background(), "alice-sub")
|
||
require.Error(t, err, "登入前 user 不應存在")
|
||
|
||
// 走完整 OIDC login flow(callback 會 Upsert user)
|
||
_ = f.AuthenticatedClient(t, "alice-sub", "alice@example.com")
|
||
|
||
// 登入後:user 已被 provision
|
||
got, err := f.userStore.Get(context.Background(), "alice-sub")
|
||
require.NoError(t, err, "OIDC callback 後 user 應被 provision 進 UserStore")
|
||
assert.Equal(t, "alice-sub", got.ID)
|
||
assert.Equal(t, "alice@example.com", got.Email)
|
||
}
|
||
|
||
// TestDBOnFix_ExchangeProvisionsDevice 驗證問題 #2:pairing exchange 後自建 device(owner 對齊)。
|
||
func TestDBOnFix_ExchangeProvisionsDevice(t *testing.T) {
|
||
f := setupFixture(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
w.WriteHeader(http.StatusNoContent)
|
||
}))
|
||
defer f.Close()
|
||
|
||
const sub = "bob-sub"
|
||
client := f.AuthenticatedClient(t, sub, "bob@example.com")
|
||
|
||
// 登入後但 exchange 前:該 user 名下無 device
|
||
before, err := f.deviceRepo.List(context.Background(), sub)
|
||
require.NoError(t, err)
|
||
require.Empty(t, before, "exchange 前不應有 device")
|
||
|
||
// 1) 產 pairing token
|
||
tokResp, err := client.Post(f.apiServer.URL+"/api/pairing/token", "", nil)
|
||
require.NoError(t, err)
|
||
defer tokResp.Body.Close()
|
||
require.Equal(t, http.StatusOK, tokResp.StatusCode)
|
||
var tokBody map[string]any
|
||
require.NoError(t, json.NewDecoder(tokResp.Body).Decode(&tokBody))
|
||
pairingTok := tokBody["data"].(map[string]any)["token"].(string)
|
||
|
||
// 2) exchange(不走 AuthMiddleware)→ 應自建 device + 建 session token
|
||
reqBody, _ := json.Marshal(api.PairingExchangeRequest{PairingToken: pairingTok})
|
||
exchResp, err := http.Post(f.apiServer.URL+"/api/pairing/exchange",
|
||
"application/json", bytes.NewReader(reqBody))
|
||
require.NoError(t, err)
|
||
defer exchResp.Body.Close()
|
||
require.Equal(t, http.StatusOK, exchResp.StatusCode)
|
||
var exchBody map[string]any
|
||
require.NoError(t, json.NewDecoder(exchResp.Body).Decode(&exchBody))
|
||
sessionTok := exchBody["data"].(map[string]any)["session_token"].(string)
|
||
require.True(t, auth.IsValidSessionToken(sessionTok))
|
||
|
||
// 3) exchange 後:該 user 名下多了一筆自建 device
|
||
after, err := f.deviceRepo.List(context.Background(), sub)
|
||
require.NoError(t, err)
|
||
require.Len(t, after, 1, "exchange 應自建一筆 device")
|
||
assert.Equal(t, sub, after[0].OwnerUserID, "device owner 應為登入 user")
|
||
assert.NotNil(t, after[0].PairedAt, "自建 device 應設 paired_at")
|
||
}
|