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

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,58 +0,0 @@
package idempotency_test
import (
"testing"
"github.com/break/junhong_cmp_fiber/pkg/idempotency"
)
func TestFingerprintIgnoresTransportFieldsAndNormalizesObjectOrder(t *testing.T) {
t.Parallel()
left, err := idempotency.Fingerprint(map[string]any{
"amount": 100,
"remark": " 月度充值 ",
"token": "token-a",
"nested": map[string]any{"enabled": true, "count": 2},
})
if err != nil {
t.Fatalf("计算第一个请求指纹失败:%v", err)
}
right, err := idempotency.Fingerprint(map[string]any{
"nested": map[string]any{"count": 2, "enabled": true},
"timestamp": 1720000000,
"remark": "月度充值",
"amount": 100,
})
if err != nil {
t.Fatalf("计算第二个请求指纹失败:%v", err)
}
if left.Algorithm != idempotency.FingerprintAlgorithmV1 {
t.Fatalf("算法版本不正确:%s", left.Algorithm)
}
if left.Value != right.Value {
t.Fatalf("仅传输字段或对象顺序变化不应改变指纹:%s != %s", left.Value, right.Value)
}
}
func TestClassifyReplayDistinguishesScopeAndConflict(t *testing.T) {
t.Parallel()
original := idempotency.Record{
Scope: idempotency.Scope{Subject: "account:7", Operation: "agent-recharge.create"},
RequestID: "request-1",
Fingerprint: idempotency.FingerprintValue{Algorithm: idempotency.FingerprintAlgorithmV1, Value: "abc"},
}
if got := idempotency.Classify(original, original.Scope, "request-1", original.Fingerprint); got != idempotency.ReplaySame {
t.Fatalf("相同命令应识别为重放,得到:%s", got)
}
if got := idempotency.Classify(original, original.Scope, "request-1", idempotency.FingerprintValue{Algorithm: idempotency.FingerprintAlgorithmV1, Value: "def"}); got != idempotency.ReplayConflict {
t.Fatalf("同作用域同请求 ID 的不同内容应冲突,得到:%s", got)
}
otherScope := idempotency.Scope{Subject: "account:8", Operation: original.Scope.Operation}
if got := idempotency.Classify(original, otherScope, "request-1", original.Fingerprint); got != idempotency.ReplayUnrelated {
t.Fatalf("不同主体不应相互污染,得到:%s", got)
}
}

View File

@@ -1,86 +0,0 @@
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 仅可由业务作为削峰优化,不能参与最终裁决。
}