重置项目上下文与规范文档

This commit is contained in:
2026-08-07 16:18:07 +08:00
parent 6611ca5226
commit 79e2d9ff92
1900 changed files with 1552 additions and 348365 deletions

View File

@@ -1,129 +0,0 @@
package outbox_test
import (
"context"
stderrors "errors"
"testing"
"time"
"gorm.io/gorm"
outboxapp "github.com/break/junhong_cmp_fiber/internal/application/outbox"
infraoutbox "github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/testutil"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
type databaseAuditWriter struct {
fail bool
}
func (w *databaseAuditWriter) WriteRecovery(_ context.Context, tx *gorm.DB, audit outboxapp.RecoveryAudit) error {
if w.fail {
return stderrors.New("测试审计不可用")
}
return tx.Exec(`INSERT INTO test_outbox_recovery_audit (batch_id, operation_type, event_count)
VALUES (?, ?, ?)`, audit.BatchID, audit.OperationType, len(audit.EventIDs)).Error
}
func TestSelectiveReplayPreservesIdentityAndWritesAudit(t *testing.T) {
db := testutil.NewPostgresTransaction(t)
testutil.CreateTemporaryOutboxTable(t, db)
createTemporaryRecoveryAuditTable(t, db)
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
repository := infraoutbox.NewRepository()
event, err := repository.Append(context.Background(), db, infraoutbox.Envelope{
EventID: "recovery-stable", EventType: "example.recovery", AggregateType: "example", AggregateID: "1",
ResourceType: "example", ResourceID: "1", CorrelationID: "correlation-stable",
Payload: struct {
Secret string `json:"secret"`
}{Secret: "preserved-in-database-only"},
})
if err != nil {
t.Fatalf("准备恢复事件失败:%v", err)
}
if err := db.Model(&model.OutboxEvent{}).Where("id = ?", event.ID).Updates(map[string]any{
"status": constants.OutboxStatusFailed, "retry_count": 10, "last_error_code": "FINAL",
}).Error; err != nil {
t.Fatalf("设置最终失败状态失败:%v", err)
}
var storedBefore model.OutboxEvent
if err := db.First(&storedBefore, event.ID).Error; err != nil {
t.Fatalf("读取重放前事件失败:%v", err)
}
service, err := outboxapp.NewRecoveryService(db, &databaseAuditWriter{}, func() time.Time { return now })
if err != nil {
t.Fatalf("创建恢复服务失败:%v", err)
}
batchID, err := service.Replay(context.Background(), outboxapp.Operator{
ID: 7, SuperAdmin: true, RequestID: "request-recovery", CorrelationID: "correlation-recovery",
}, []uint{event.ID}, "确认消费者已具备幂等保护")
if err != nil || batchID == "" {
t.Fatalf("选择性重放失败:%v", err)
}
var recovered model.OutboxEvent
if err := db.First(&recovered, event.ID).Error; err != nil {
t.Fatalf("读取重放事件失败:%v", err)
}
if recovered.Status != constants.OutboxStatusPending || recovered.EventID != event.EventID ||
recovered.CorrelationID != event.CorrelationID || string(recovered.Payload) != string(storedBefore.Payload) {
t.Fatalf("重放改变了事件身份或内容:%+v", recovered)
}
var auditCount int64
if err := db.Table("test_outbox_recovery_audit").Where("batch_id = ?", batchID).Count(&auditCount).Error; err != nil || auditCount != 1 {
t.Fatalf("恢复审计事实缺失:%v数量%d", err, auditCount)
}
}
func TestRecoveryRejectsActiveLeaseUnauthorizedOperatorAndAuditFailure(t *testing.T) {
db := testutil.NewPostgresTransaction(t)
testutil.CreateTemporaryOutboxTable(t, db)
createTemporaryRecoveryAuditTable(t, db)
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
repository := infraoutbox.NewRepository()
event, err := repository.Append(context.Background(), db, infraoutbox.Envelope{
EventID: "active-lease", EventType: "example.recovery", AggregateType: "example", AggregateID: "2",
ResourceType: "example", ResourceID: "2", Payload: struct {
Visible bool `json:"visible"`
}{Visible: true},
})
if err != nil {
t.Fatalf("准备有效租约事件失败:%v", err)
}
if err := db.Model(&model.OutboxEvent{}).Where("id = ?", event.ID).Updates(map[string]any{
"status": constants.OutboxStatusDelivering, "lease_owner": "active-worker", "lease_expires_at": now.Add(time.Minute),
}).Error; err != nil {
t.Fatalf("设置有效租约失败:%v", err)
}
service, _ := outboxapp.NewRecoveryService(db, &databaseAuditWriter{}, func() time.Time { return now })
if _, err := service.ReleaseExpiredLeases(context.Background(), outboxapp.Operator{ID: 7, SuperAdmin: true}, []uint{event.ID}, "测试释放"); err == nil {
t.Fatal("不得释放仍然有效的租约")
}
if _, err := service.Replay(context.Background(), outboxapp.Operator{ID: 8, SuperAdmin: false}, []uint{event.ID}, "越权测试"); err == nil {
t.Fatal("非超级管理员不得执行人工恢复")
}
if err := db.Model(&model.OutboxEvent{}).Where("id = ?", event.ID).Update("status", constants.OutboxStatusFailed).Error; err != nil {
t.Fatalf("设置失败事件失败:%v", err)
}
failClosed, _ := outboxapp.NewRecoveryService(db, &databaseAuditWriter{fail: true}, func() time.Time { return now })
if _, err := failClosed.Replay(context.Background(), outboxapp.Operator{ID: 7, SuperAdmin: true}, []uint{event.ID}, "审计失败测试"); err == nil {
t.Fatal("统一审计不可用时恢复事务必须失败")
}
var unchanged model.OutboxEvent
if err := db.First(&unchanged, event.ID).Error; err != nil || unchanged.Status != constants.OutboxStatusFailed {
t.Fatalf("审计失败后事件状态未回滚:%v状态%d", err, unchanged.Status)
}
}
func createTemporaryRecoveryAuditTable(t *testing.T, db *gorm.DB) {
t.Helper()
if err := db.Exec(`CREATE TEMP TABLE test_outbox_recovery_audit (
id bigserial PRIMARY KEY, batch_id varchar(64) NOT NULL,
operation_type varchar(100) NOT NULL, event_count integer NOT NULL
) ON COMMIT DROP`).Error; err != nil {
t.Fatalf("创建恢复审计测试表失败:%v", err)
}
}

View File

@@ -1,141 +0,0 @@
package systemconfig_test
import (
"context"
"fmt"
"os"
"strings"
"sync"
"testing"
"github.com/bytedance/sonic"
"github.com/google/uuid"
"gorm.io/driver/postgres"
"gorm.io/gorm"
systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
systemconfiginfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/config"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
type transactionAuditWriter struct{}
func (transactionAuditWriter) WriteConfigChange(_ context.Context, tx *gorm.DB, audit systemconfigapp.ChangeAudit) error {
before, _ := sonic.Marshal(audit.BeforeData)
after, _ := sonic.Marshal(audit.AfterData)
return tx.Exec(`INSERT INTO test_system_config_audit
(config_key, operator_id, before_data, after_data) VALUES (?, ?, ?::jsonb, ?::jsonb)`,
audit.ConfigKey, audit.OperatorID, string(before), string(after)).Error
}
func TestConcurrentFirstSystemConfigUpdatesAreSerializedByPostgres(t *testing.T) {
if os.Getenv("JUNHONG_DATABASE_HOST") == "" {
t.Skip("未加载 .env.local跳过依赖真实 PostgreSQL 的并发集成测试")
}
cfg, err := config.Load()
if err != nil {
t.Fatalf("加载测试配置失败:%v", err)
}
schema := "test_system_config_" + strings.ReplaceAll(uuid.NewString(), "-", "")
adminDB := openSchemaDatabase(t, &cfg.Database, "public")
if err := adminDB.Exec("CREATE SCHEMA " + schema).Error; err != nil {
t.Fatalf("创建隔离 schema 失败:%v", err)
}
t.Cleanup(func() {
_ = adminDB.Exec("DROP SCHEMA " + schema + " CASCADE").Error
closeDatabase(adminDB)
})
firstDB := openSchemaDatabase(t, &cfg.Database, schema)
secondDB := openSchemaDatabase(t, &cfg.Database, schema)
t.Cleanup(func() {
closeDatabase(firstDB)
closeDatabase(secondDB)
})
createConcurrentConfigTables(t, firstDB)
registry := systemconfiginfra.NewRegistry()
definition := systemconfiginfra.Definition{
Key: "foundation.concurrent.value", Module: "foundation", ValueType: constants.SystemConfigTypeString,
DefaultValue: "default", Description: "并发配置测试值",
}
if err := registry.Register(definition); err != nil {
t.Fatalf("注册并发测试配置失败:%v", err)
}
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{UserID: 7, UserType: constants.UserTypeSuperAdmin})
services := []*systemconfigapp.UpdateService{
systemconfigapp.NewUpdateService(firstDB, registry, nil, transactionAuditWriter{}, nil, nil),
systemconfigapp.NewUpdateService(secondDB, registry, nil, transactionAuditWriter{}, nil, nil),
}
values := []string{"first", "second"}
start := make(chan struct{})
errorsFound := make(chan error, len(services))
var waitGroup sync.WaitGroup
for index := range services {
waitGroup.Add(1)
go func(index int) {
defer waitGroup.Done()
<-start
_, executeErr := services[index].Execute(ctx, definition.Key, dto.UpdateSystemConfigRequest{Value: values[index]})
errorsFound <- executeErr
}(index)
}
close(start)
waitGroup.Wait()
close(errorsFound)
for executeErr := range errorsFound {
if executeErr != nil {
t.Fatalf("并发更新不应产生唯一键冲突:%v", executeErr)
}
}
var stored model.SystemConfig
if err := firstDB.Where("config_key = ?", definition.Key).First(&stored).Error; err != nil {
t.Fatalf("读取并发更新结果失败:%v", err)
}
if stored.ConfigValue != values[0] && stored.ConfigValue != values[1] {
t.Fatalf("并发更新保存了非法结果:%q", stored.ConfigValue)
}
var auditCount int64
if err := firstDB.Table("test_system_config_audit").Where("config_key = ?", definition.Key).Count(&auditCount).Error; err != nil || auditCount != 2 {
t.Fatalf("并发更新审计事实不完整:错误=%v数量=%d", err, auditCount)
}
}
func openSchemaDatabase(t *testing.T, cfg *config.DatabaseConfig, schema string) *gorm.DB {
t.Helper()
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s search_path=%s TimeZone=Asia/Shanghai",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode, schema)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{SkipDefaultTransaction: true})
if err != nil {
t.Fatalf("连接隔离 PostgreSQL 失败:%v", err)
}
return db
}
func closeDatabase(db *gorm.DB) {
if sqlDB, err := db.DB(); err == nil {
_ = sqlDB.Close()
}
}
func createConcurrentConfigTables(t *testing.T, db *gorm.DB) {
t.Helper()
if err := db.Exec(`CREATE TABLE tb_system_config (
id bigserial PRIMARY KEY, config_key varchar(150) NOT NULL UNIQUE, config_value text NOT NULL,
value_type varchar(20) NOT NULL, module varchar(100) NOT NULL, description varchar(500) NOT NULL,
is_readonly boolean NOT NULL DEFAULT false, is_sensitive boolean NOT NULL DEFAULT false,
creator bigint NOT NULL DEFAULT 0, updater bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT NOW(), updated_at timestamptz NOT NULL DEFAULT NOW()
)`).Error; err != nil {
t.Fatalf("创建隔离配置表失败:%v", err)
}
if err := db.Exec(`CREATE TABLE test_system_config_audit (
id bigserial PRIMARY KEY, config_key varchar(150) NOT NULL, operator_id bigint NOT NULL,
before_data jsonb NOT NULL, after_data jsonb NOT NULL
)`).Error; err != nil {
t.Fatalf("创建隔离审计表失败:%v", err)
}
}