滑动续期、网络错误归类、API Key 激活乐观锁与生效提示

This commit is contained in:
2026-08-11 11:45:03 +08:00
parent 23b2820101
commit e63af49831
21 changed files with 679 additions and 15 deletions
+90
View File
@@ -9,6 +9,8 @@ import (
"testing"
"time"
"gorm.io/gorm"
"oci-portal/internal/model"
"oci-portal/internal/oci"
)
@@ -194,6 +196,9 @@ func TestActivateApiKey(t *testing.T) {
if err != nil || plain != newKey {
t.Fatalf("persisted key mismatch (err=%v)", err)
}
if got.KeyActivatedAt == nil || time.Since(*got.KeyActivatedAt) > time.Minute {
t.Fatalf("keyActivatedAt = %v, want 刚写入的时间", got.KeyActivatedAt)
}
if len(fc.validated) == 0 || fc.validated[0] != "11:22" {
t.Fatalf("validated = %v", fc.validated)
}
@@ -203,3 +208,88 @@ func TestActivateApiKey(t *testing.T) {
})
}
}
func TestPersistSigningKeyRejectsStaleSnapshot(t *testing.T) {
tests := []struct {
name string
deleted bool
}{
{name: "租户已删除", deleted: true},
{name: "凭据已被并发更新"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testStaleSigningKeyPersistence(t, tt.deleted)
})
}
}
func TestPersistSigningKeyAdvancesSnapshotVersion(t *testing.T) {
s := newTestService(t, &apiKeyClient{})
cfg := seedApiKeyConfig(t, s)
stale := *cfg
first := oci.Credentials{UserOCID: cfg.UserOCID, Fingerprint: "11:22", PrivateKey: "first-key"}
if err := s.persistSigningKey(context.Background(), cfg, first); err != nil {
t.Fatalf("first persist: %v", err)
}
second := oci.Credentials{UserOCID: cfg.UserOCID, Fingerprint: "33:44", PrivateKey: "second-key"}
if err := s.persistSigningKey(context.Background(), &stale, second); !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("stale persist err = %v, want ErrRecordNotFound", err)
}
var got model.OciConfig
if err := s.db.First(&got, cfg.ID).Error; err != nil || got.Fingerprint != "11:22" {
t.Fatalf("persisted config = %+v, err %v", got, err)
}
}
func testStaleSigningKeyPersistence(t *testing.T, deleted bool) {
t.Helper()
s := newTestService(t, &apiKeyClient{})
cfg := seedApiKeyConfig(t, s)
invalidateSigningSnapshot(t, s, cfg, deleted)
cred := oci.Credentials{UserOCID: cfg.UserOCID, Fingerprint: "11:22", PrivateKey: "new-key"}
err := s.persistSigningKey(context.Background(), cfg, cred)
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("err = %v, want ErrRecordNotFound", err)
}
assertSigningSnapshotPreserved(t, s, cfg.ID, deleted)
}
func invalidateSigningSnapshot(t *testing.T, s *OciConfigService, cfg *model.OciConfig, deleted bool) {
t.Helper()
if deleted {
if err := s.db.Delete(&model.OciConfig{}, cfg.ID).Error; err != nil {
t.Fatalf("delete config: %v", err)
}
return
}
enc, err := s.cipher.EncryptString("concurrent-key")
if err != nil {
t.Fatalf("encrypt concurrent key: %v", err)
}
res := s.db.Model(&model.OciConfig{}).Where("id = ?", cfg.ID).UpdateColumns(map[string]any{
"fingerprint": "cc:dd", "private_key_enc": enc, "updated_at": cfg.UpdatedAt.Add(time.Second),
})
if res.Error != nil || res.RowsAffected != 1 {
t.Fatalf("mutate config = rows %d, err %v", res.RowsAffected, res.Error)
}
}
func assertSigningSnapshotPreserved(t *testing.T, s *OciConfigService, id uint, deleted bool) {
t.Helper()
var got model.OciConfig
err := s.db.First(&got, id).Error
if deleted {
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("deleted config reload err = %v", err)
}
return
}
if err != nil || got.Fingerprint != "cc:dd" {
t.Fatalf("concurrent config = %+v, err %v", got, err)
}
plain, err := s.cipher.DecryptString(got.PrivateKeyEnc)
if err != nil || plain != "concurrent-key" {
t.Fatalf("concurrent key = %q, err %v", plain, err)
}
}