滑动续期、网络错误归类、API Key 激活乐观锁与生效提示
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user