visionA/visionA-backend/cmd/api-server/dbon_fk_fix_test.go
jim800121chen cabbdde495 feat(visionA-backend): DB 接入後續 — OIDC/pairing FK 收尾 + B4 metadata + nginx healthz + 補測試
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>
2026-06-21 06:36:35 +08:00

90 lines
3.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.

// 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驗證
// - 問題 #1OIDC callback 成功後 user 被 provision 進 UserStore。
// - 問題 #2pairing 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 驗證問題 #1OIDC 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 flowcallback 會 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 驗證問題 #2pairing exchange 後自建 deviceowner 對齊)。
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")
}