All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
package idempotency_test
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
"testing"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/testutil"
|
||
"github.com/break/junhong_cmp_fiber/pkg/idempotency"
|
||
)
|
||
|
||
func TestPostgresUniqueConstraintArbitratesConcurrentFirstSubmission(t *testing.T) {
|
||
db := testutil.NewPostgresTransaction(t)
|
||
if err := db.Exec(`
|
||
CREATE TEMP TABLE test_command_idempotency (
|
||
subject varchar(100) NOT NULL,
|
||
operation varchar(100) NOT NULL,
|
||
request_id varchar(100) NOT NULL,
|
||
fingerprint varchar(128) NOT NULL,
|
||
result_id bigint NOT NULL,
|
||
UNIQUE (subject, operation, request_id)
|
||
) ON COMMIT DROP`).Error; err != nil {
|
||
t.Fatalf("创建幂等契约测试表失败:%v", err)
|
||
}
|
||
|
||
fingerprint, err := idempotency.Fingerprint(map[string]any{"amount": 100})
|
||
if err != nil {
|
||
t.Fatalf("计算指纹失败:%v", err)
|
||
}
|
||
|
||
start := make(chan struct{})
|
||
results := make(chan int64, 2)
|
||
errs := make(chan error, 2)
|
||
var wait sync.WaitGroup
|
||
for _, resultID := range []int64{101, 102} {
|
||
wait.Add(1)
|
||
go func(id int64) {
|
||
defer wait.Done()
|
||
<-start
|
||
result := db.WithContext(context.Background()).Exec(`
|
||
INSERT INTO test_command_idempotency
|
||
(subject, operation, request_id, fingerprint, result_id)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
ON CONFLICT (subject, operation, request_id) DO NOTHING`,
|
||
"account:7", "agent-recharge.create", "request-1", fingerprint.Value, id,
|
||
)
|
||
results <- result.RowsAffected
|
||
errs <- result.Error
|
||
}(resultID)
|
||
}
|
||
close(start)
|
||
wait.Wait()
|
||
close(results)
|
||
close(errs)
|
||
|
||
var inserted int64
|
||
for resultErr := range errs {
|
||
if resultErr != nil {
|
||
t.Fatalf("并发写入失败:%v", resultErr)
|
||
}
|
||
}
|
||
for affected := range results {
|
||
inserted += affected
|
||
}
|
||
if inserted != 1 {
|
||
t.Fatalf("并发首写必须只有一个数据库事实,实际插入:%d", inserted)
|
||
}
|
||
|
||
var count int64
|
||
if err := db.Table("test_command_idempotency").Count(&count).Error; err != nil {
|
||
t.Fatalf("统计幂等事实失败:%v", err)
|
||
}
|
||
if count != 1 {
|
||
t.Fatalf("幂等事实数量错误:%d", count)
|
||
}
|
||
}
|
||
|
||
func TestIdempotencyScopeDoesNotDependOnRedis(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
scope := idempotency.Scope{Subject: "account:7", Operation: "example.create"}
|
||
if !idempotency.ValidateScope(scope, "request-1") {
|
||
t.Fatal("完整作用域和请求 ID 应通过校验")
|
||
}
|
||
// 公共幂等契约没有 Redis 参数;Redis 仅可由业务作为削峰优化,不能参与最终裁决。
|
||
}
|