test(service): add golden file validation and master key tests

- Update TESTING.md to reflect current testing status and future plans
- Add golden file comparison for provider snapshot validation in sync_test.go
- Introduce master_test.go for testing Master/Key functionality
- Add testdata directory for contract testing snapshots
This commit is contained in:
zenfun
2025-12-14 23:37:16 +08:00
parent d0011f3eb2
commit 71c183a480
4 changed files with 113 additions and 8 deletions

View File

@@ -26,18 +26,26 @@ go test ./...
## 分阶段计划(长期)
### 阶段 1已落地/优先级最高
### 阶段 1已落地
- provider 归一化Vertex 默认 `google_location=global` 的回归保护
- SyncServiceRedis snapshot 写入与 routing key 生成
- request_idGin middleware 透传/生成
### 阶段 2扩覆盖
### 阶段 2已落地一部分
- Master/Keytoken hash、epoch、scope、状态机分支
- LogWriter批处理/刷盘边界(可用 fake clock
- Master/KeyCreateMaster/ValidateMasterKey/IssueChildKey 的关键分支(含 child key 上限)
### 阶段 3契约测试
待补齐:
- 与 DP 的 snapshot schema 契约:用 `testdata` golden 校验字段/格式稳定
- Master/Keyepoch/禁用等更多状态分支
- LogWriter批处理/刷盘边界(可用 fake clock 或缩短 flush interval
### 阶段 3已落地一部分契约测试
- 与 DP 的 provider snapshot schema 契约:`internal/service/testdata/provider_snapshot.json` + SyncProvider 输出回归
待扩展:
- model snapshot 契约能力字段、max_output_tokens 等)
- token/master snapshot 契约(如果后续引入更多字段)

View File

@@ -0,0 +1,69 @@
package service
import (
"testing"
"github.com/ez-api/ez-api/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func newTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(&model.Master{}, &model.Key{}); err != nil {
t.Fatalf("migrate: %v", err)
}
return db
}
func TestMasterService_CreateAndValidateMasterKey(t *testing.T) {
db := newTestDB(t)
svc := NewMasterService(db)
m, raw, err := svc.CreateMaster("m1", "default", 2, 10)
if err != nil {
t.Fatalf("CreateMaster: %v", err)
}
if raw == "" {
t.Fatalf("expected raw master key")
}
if m.MasterKeyDigest == "" {
t.Fatalf("expected master key digest to be set")
}
got, err := svc.ValidateMasterKey(raw)
if err != nil {
t.Fatalf("ValidateMasterKey: %v", err)
}
if got.ID != m.ID {
t.Fatalf("expected master id %d, got %d", m.ID, got.ID)
}
}
func TestMasterService_IssueChildKey_RespectsLimit(t *testing.T) {
db := newTestDB(t)
svc := NewMasterService(db)
m, _, err := svc.CreateMaster("m1", "default", 1, 10)
if err != nil {
t.Fatalf("CreateMaster: %v", err)
}
_, raw1, err := svc.IssueChildKey(m.ID, "default", "chat:write")
if err != nil {
t.Fatalf("IssueChildKey #1: %v", err)
}
if raw1 == "" {
t.Fatalf("expected raw child key")
}
_, _, err = svc.IssueChildKey(m.ID, "default", "chat:write")
if err == nil {
t.Fatalf("expected child key limit error")
}
}

View File

@@ -2,6 +2,9 @@ package service
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/alicebob/miniredis/v2"
@@ -10,6 +13,16 @@ import (
)
func TestSyncProvider_WritesSnapshotAndRouting(t *testing.T) {
goldenPath := filepath.Join("testdata", "provider_snapshot.json")
goldenRaw, err := os.ReadFile(goldenPath)
if err != nil {
t.Fatalf("read golden %s: %v", goldenPath, err)
}
var golden map[string]any
if err := json.Unmarshal(goldenRaw, &golden); err != nil {
t.Fatalf("parse golden json: %v", err)
}
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
@@ -40,8 +53,10 @@ func TestSyncProvider_WritesSnapshotAndRouting(t *testing.T) {
if err := json.Unmarshal([]byte(raw), &snap); err != nil {
t.Fatalf("invalid snapshot json: %v", err)
}
if snap["google_location"] != "global" {
t.Fatalf("expected google_location=global, got %v", snap["google_location"])
for k, v := range golden {
if !reflect.DeepEqual(snap[k], v) {
t.Fatalf("snapshot mismatch for %q: got=%#v want=%#v", k, snap[k], v)
}
}
routeKey := "route:group:default:gemini-3-pro-preview"

View File

@@ -0,0 +1,13 @@
{
"id": 42,
"name": "p1",
"type": "vertex-express",
"base_url": "",
"api_key": "",
"google_location": "global",
"group": "default",
"models": ["gemini-3-pro-preview"],
"status": "active",
"auto_ban": true
}