test: strengthen concurrent seed coverage

This commit is contained in:
2026-07-21 20:58:17 +08:00
parent d9440b5334
commit 7f71c971eb
2 changed files with 152 additions and 66 deletions

View File

@@ -2,6 +2,7 @@ package seed
import (
"fmt"
"strings"
"sync/atomic"
"testing"
"time"
@@ -134,27 +135,65 @@ func TestDemoSeedAcceptsAConcurrentWinnerWithoutOverwritingUser(t *testing.T) {
}
func TestDemoSeedRetriesSerializationFailureOutsideTheTransaction(t *testing.T) {
database := newTestDB(t)
var failures atomic.Int32
callbackName := "test:fail_first_seed_user_create"
require.NoError(t, database.Callback().Create().Before("gorm:create").Register(callbackName, func(tx *gorm.DB) {
if tx.Statement.Schema == nil || tx.Statement.Schema.Table != (models.SenlinAgentUser{}).TableName() {
return
}
if failures.Add(1) == 1 {
tx.AddError(testSQLStateError{state: "40001"})
}
}))
t.Cleanup(func() { database.Callback().Create().Remove(callbackName) })
for _, sqlState := range []string{"40001", "40P01"} {
t.Run(sqlState, func(t *testing.T) {
database := newTestDB(t)
email := "retry-" + strings.ToLower(sqlState) + "@senlin.ai"
var injected atomic.Bool
var sawProjectWrite atomic.Bool
var failedOwnerIdentity string
var failedProjectIdentity string
callbackName := "test:fail_first_seed_project_" + sqlState
require.NoError(t, database.Callback().Create().After("gorm:create").Register(callbackName, func(tx *gorm.DB) {
if tx.Error != nil || tx.Statement.Schema == nil || tx.Statement.Schema.Table != (models.SenlinAgentProject{}).TableName() {
return
}
if !injected.CompareAndSwap(false, true) {
return
}
project, ok := tx.Statement.Dest.(*models.SenlinAgentProject)
if !ok {
tx.AddError(fmt.Errorf("unexpected project create destination %T", tx.Statement.Dest))
return
}
if tx.RowsAffected == 1 && project.ID != 0 && project.Identity != "" && project.OwnerIdentity != "" {
sawProjectWrite.Store(true)
failedOwnerIdentity = project.OwnerIdentity
failedProjectIdentity = project.Identity
}
tx.AddError(testSQLStateError{state: sqlState})
}))
t.Cleanup(func() { database.Callback().Create().Remove(callbackName) })
result, err := Demo(database, DemoOptions{Email: "retry@senlin.ai", DisplayName: "重试用户", Password: "password123"})
result, err := Demo(database, DemoOptions{Email: email, DisplayName: "重试用户", Password: "password123"})
require.NoError(t, err)
require.Equal(t, int32(2), failures.Load())
require.Equal(t, "retry@senlin.ai", result.User.Email)
var userCount int64
require.NoError(t, database.Model(&models.SenlinAgentUser{}).Where("email = ?", result.User.Email).Count(&userCount).Error)
require.Equal(t, int64(1), userCount)
require.NoError(t, err)
require.True(t, injected.Load())
require.True(t, sawProjectWrite.Load(), "failed transaction must contain the first inserted project")
require.NotEqual(t, failedOwnerIdentity, result.User.Identity, "retry must recreate the rolled-back user")
require.NotEqual(t, failedProjectIdentity, result.Projects[0].Identity, "retry must recreate the rolled-back project")
require.Equal(t, email, result.User.Email)
require.Len(t, result.Projects, 4)
projectIDs := []uint{result.Projects[0].ID, result.Projects[1].ID, result.Projects[2].ID, result.Projects[3].ID}
requireDemoModelCount(t, database, &models.SenlinAgentUser{}, "email = ?", []any{email}, 1)
requireDemoModelCount(t, database, &models.SenlinAgentProject{}, "owner_id = ?", []any{result.User.ID}, 4)
requireDemoModelCount(t, database, &models.SenlinAgentTag{}, "project_id = ?", []any{result.Projects[0].ID}, 4)
requireDemoModelCount(t, database, &models.SenlinAgentTask{}, "project_id IN ?", []any{projectIDs}, 6)
requireDemoModelCount(t, database, &models.SenlinAgentInboxItem{}, "project_id IN ?", []any{projectIDs}, 7)
requireDemoModelCount(t, database, &models.SenlinAgentAISession{}, "project_id = ?", []any{result.Projects[0].ID}, 4)
requireDemoModelCount(t, database, &models.SenlinAgentNote{}, "project_id = ?", []any{result.Projects[0].ID}, 2)
requireDemoModelCount(t, database, &models.SenlinAgentSource{}, "project_id = ?", []any{result.Projects[0].ID}, 2)
requireDemoModelCount(t, database, &models.SenlinAgentCronPlan{}, "project_id = ?", []any{result.Projects[0].ID}, 3)
requireDemoModelCount(t, database, &models.SenlinAgentProjectChannel{}, "project_id = ?", []any{result.Projects[0].ID}, 2)
})
}
}
func requireDemoModelCount(t *testing.T, database *gorm.DB, model any, where string, args []any, expected int64) {
t.Helper()
var count int64
require.NoError(t, database.Model(model).Where(where, args...).Count(&count).Error)
require.Equal(t, expected, count)
}
type testSQLStateError struct {