增加数据库生命周期回归测试

加入保存失败、单个回收失败、过期回收失败和批量回收失败恢复的回归覆盖,当前实现仍待修正测试发现的状态顺序问题。
This commit is contained in:
xuans 2026-09-30 12:57:40 +08:00
parent 14ab07c709
commit 42e03242a9
2 changed files with 136 additions and 2 deletions

View File

@ -379,7 +379,6 @@ func (m *DatabaseVerificationManager) deleteExpiredSessions(entries []expiredDat
reference := strings.TrimSpace(entry.Session.CredentialRef)
if err := m.deleteCredential(reference); err != nil {
m.restoreExpiredSession(entry)
m.rememberPendingCleanup(reference)
continue
}
m.removeExpiredSession(entry)
@ -392,7 +391,6 @@ func (m *DatabaseVerificationManager) deleteDiscardedSessions(entries []expiredD
reference := strings.TrimSpace(entry.Session.CredentialRef)
if err := m.deleteCredential(reference); err != nil {
m.restoreSession(entry.Token, entry.Session)
m.rememberPendingCleanup(reference)
continue
}
m.forgetPendingCleanup(reference)

View File

@ -314,3 +314,139 @@ func TestDatabaseVerificationConsumesTokenOnlyOnceUnderConcurrency(t *testing.T)
t.Fatalf("并发消费数据库验证令牌成功次数为 %d,预期为 1", successes)
}
}
func TestDatabaseVerificationPutFailureQueuesCredentialCleanup(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{putFailure: true, deleteFailures: 1}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
_, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "scert_user", Password: "database-password", TLSMode: "true",
})
if err == nil {
t.Fatal("expected credential persistence failure")
}
store.mu.Lock()
retainedAfterFailure := len(store.values)
store.mu.Unlock()
if retainedAfterFailure != 1 {
t.Fatalf("expected failed persistence cleanup to retain one credential temporarily, got %d", retainedAfterFailure)
}
manager.retryPendingCredentialCleanup()
store.mu.Lock()
remaining := len(store.values)
store.mu.Unlock()
if remaining != 0 {
t.Fatalf("expected pending credential cleanup to succeed on retry, got %d remaining", remaining)
}
}
func TestDatabaseVerificationDiscardFailureRestoresTokenForRetry(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{deleteFailures: 1}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
result, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "scert_user", Password: "database-password", TLSMode: "true",
})
if err != nil {
t.Fatal(err)
}
if err := manager.Discard(result.VerificationToken); err == nil {
t.Fatal("expected first credential discard to fail")
}
if _, err := manager.Resolve(result.VerificationToken); err != nil {
t.Fatalf("expected failed discard to restore the verification token: %v", err)
}
store.mu.Lock()
_, retained := store.values[result.CredentialRef]
store.mu.Unlock()
if !retained {
t.Fatal("credential must remain available while the token is restored")
}
if err := manager.Discard(result.VerificationToken); err != nil {
t.Fatalf("expected retry discard to succeed: %v", err)
}
if _, err := manager.Resolve(result.VerificationToken); err == nil {
t.Fatal("discarded token must not remain usable")
}
store.mu.Lock()
_, retained = store.values[result.CredentialRef]
store.mu.Unlock()
if retained {
t.Fatal("successfully discarded credential must be deleted")
}
}
func TestDatabaseVerificationExpiredCleanupFailureRestoresTokenForRetry(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{deleteFailures: 1}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
result, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "postgres", Host: "db.example.test", Database: "scert",
Username: "scert_user", Password: "database-password", TLSMode: "verify-full",
})
if err != nil {
t.Fatal(err)
}
manager.now = func() time.Time { return result.ExpiresAt }
if _, err := manager.Check(result.VerificationToken, result.CredentialRef); err != nil {
t.Fatalf("expected failed expiry cleanup to preserve the session for retry: %v", err)
}
store.mu.Lock()
_, retained := store.values[result.CredentialRef]
store.mu.Unlock()
if !retained {
t.Fatal("credential must remain available after failed expiry cleanup")
}
if _, err := manager.Check(result.VerificationToken, result.CredentialRef); err == nil {
t.Fatal("expired token must be invalid after retry cleanup succeeds")
}
store.mu.Lock()
_, retained = store.values[result.CredentialRef]
store.mu.Unlock()
if retained {
t.Fatal("successfully cleaned expired credential must be deleted")
}
}
func TestDatabaseVerificationDiscardAllRestoresOnlyFailedSession(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{deleteFailures: 1}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
first, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "first", Password: "first-password", TLSMode: "true",
})
if err != nil {
t.Fatal(err)
}
second, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "second", Password: "second-password", TLSMode: "true",
})
if err != nil {
t.Fatal(err)
}
manager.DiscardAll()
valid := 0
for _, token := range []string{first.VerificationToken, second.VerificationToken} {
if _, err := manager.Resolve(token); err == nil {
valid++
}
}
if valid != 1 {
t.Fatalf("expected exactly one failed batch cleanup to restore its token, got %d", valid)
}
manager.DiscardAll()
store.mu.Lock()
remaining := len(store.values)
store.mu.Unlock()
if remaining != 0 {
t.Fatalf("expected second batch cleanup to remove the restored credential, got %d remaining", remaining)
}
}