重置项目上下文与规范文档
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
package asynctask_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
|
||||
)
|
||||
|
||||
func TestCompletedTaskUsesCountsForPartialAndAllItemFailures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
|
||||
cases := []struct {
|
||||
name string
|
||||
success int
|
||||
failed int
|
||||
}{
|
||||
{name: "部分成功", success: 7, failed: 3},
|
||||
{name: "业务项全部失败", success: 0, failed: 10},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
projection, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
|
||||
TaskID: "task-1", Status: asynctask.StatusCompleted,
|
||||
TotalCount: 10, SuccessCount: tc.success, FailedCount: tc.failed,
|
||||
StartedAt: &now, CompletedAt: &now, UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("构造任务投影失败:%v", err)
|
||||
}
|
||||
if projection.Status != asynctask.StatusCompleted || projection.StatusName != "已完成" {
|
||||
t.Fatalf("业务项处理到终点必须为已完成:%+v", projection)
|
||||
}
|
||||
if projection.Progress != 100 {
|
||||
t.Fatalf("终态进度必须为 100,得到:%d", projection.Progress)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminalProjectionRejectsBrokenCountInvariant(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
|
||||
TaskID: "task-2", Status: asynctask.StatusCompleted,
|
||||
TotalCount: 10, SuccessCount: 5, FailedCount: 4,
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("终态计数不守恒时应拒绝构造公开投影")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructuredPayloadRejectsBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if err := asynctask.ValidatePayload([]byte(`{"task_id":1}`)); err == nil {
|
||||
t.Fatal("预序列化 []byte 载荷必须被拒绝")
|
||||
}
|
||||
if err := asynctask.ValidatePayload(struct {
|
||||
TaskID uint `json:"task_id"`
|
||||
}{TaskID: 1}); err != nil {
|
||||
t.Fatalf("结构化载荷应通过校验:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimRecoversOnlyExpiredProcessingTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
|
||||
activeExpiry := now.Add(time.Minute)
|
||||
active, err := asynctask.Claim(asynctask.LeaseState{
|
||||
Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &activeExpiry,
|
||||
}, "worker-b", now, time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("检查有效租约失败:%v", err)
|
||||
}
|
||||
if active.Claimed {
|
||||
t.Fatal("其他 Worker 不得抢占有效租约")
|
||||
}
|
||||
|
||||
expiredAt := now.Add(-time.Second)
|
||||
recovered, err := asynctask.Claim(asynctask.LeaseState{
|
||||
Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &expiredAt,
|
||||
}, "worker-b", now, time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("恢复过期任务失败:%v", err)
|
||||
}
|
||||
if !recovered.Claimed || recovered.State.LeaseOwner != "worker-b" {
|
||||
t.Fatalf("过期任务应由新 Worker 恢复:%+v", recovered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminalTaskCannotBeClaimedOrCancelledAgain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result, err := asynctask.Claim(asynctask.LeaseState{Status: asynctask.StatusCompleted}, "worker-a", time.Now(), time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("检查终态任务失败:%v", err)
|
||||
}
|
||||
if result.Claimed || asynctask.CanCancel(asynctask.StatusCompleted) {
|
||||
t.Fatal("终态任务不得被重复消费或取消")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 仅可由业务作为削峰优化,不能参与最终裁决。
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func TestSanitizeJSONRecursivelyMasksRequestAndResponseSecrets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
raw := []byte(`{"Password":"p@ss","items":[{"access_token":"token-value","count":3}],"profile":{"private_url":"https://secret.example/a"}}`)
|
||||
result := sanitizeBody(raw, BodyPolicyJSON)
|
||||
if strings.Contains(result.Content, "p@ss") || strings.Contains(result.Content, "token-value") || strings.Contains(result.Content, "secret.example") {
|
||||
t.Fatalf("访问日志仍包含敏感值:%s", result.Content)
|
||||
}
|
||||
if !strings.Contains(result.Content, redactedValue) {
|
||||
t.Fatalf("访问日志未输出统一脱敏占位:%s", result.Content)
|
||||
}
|
||||
if result.Truncated {
|
||||
t.Fatal("短载荷不应标记为截断")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFiberAccessLogSanitizesFinalRequestAndResponseJSON(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
log := zap.New(zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(&output), zapcore.InfoLevel,
|
||||
))
|
||||
app := fiber.New()
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("requestid", "request-test-1")
|
||||
return c.Next()
|
||||
})
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Post("/tokens", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"access_token": "response-token", "data": fiber.Map{"ok": true}})
|
||||
})
|
||||
|
||||
request := httptest.NewRequest("POST", "/tokens?Authorization=query-secret&page=1", strings.NewReader(`{"password":"request-secret"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response, err := app.Test(request)
|
||||
if err != nil {
|
||||
t.Fatalf("执行 Fiber 请求失败:%v", err)
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, response.Body)
|
||||
_ = response.Body.Close()
|
||||
|
||||
logged := output.String()
|
||||
for _, secret := range []string{"request-secret", "response-token", "query-secret"} {
|
||||
if strings.Contains(logged, secret) {
|
||||
t.Fatalf("最终 JSON 访问日志泄露测试凭证 %q:%s", secret, logged)
|
||||
}
|
||||
}
|
||||
for _, field := range []string{"request_id", "status", "duration_ms", "request_body_truncated", "response_body_truncated"} {
|
||||
if !strings.Contains(logged, field) {
|
||||
t.Fatalf("最终 JSON 访问日志缺少字段 %q:%s", field, logged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSensitiveRouteMatrixNeverLogsRawPayload(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
contentType string
|
||||
body string
|
||||
secret string
|
||||
}{
|
||||
{name: "登录JSON", path: "/api/auth/login", contentType: "application/json", body: `{"username":"safe-user","password":"json-secret"}`, secret: "json-secret"},
|
||||
{name: "企微XML", path: "/api/callback/wecom", contentType: "application/xml", body: `<xml><Encrypt>xml-secret</Encrypt></xml>`, secret: "xml-secret"},
|
||||
{name: "支付表单", path: "/api/callback/alipay", contentType: "application/x-www-form-urlencoded", body: "order_no=SAFE-1&sign=form-secret", secret: "form-secret"},
|
||||
{name: "文件multipart", path: "/api/admin/storage/upload", contentType: "multipart/form-data; boundary=x", body: "--x\r\nContent-Disposition: form-data; name=\"file\"; filename=\"secret.bin\"\r\nContent-Type: application/octet-stream\r\n\r\nfile-bytes-secret\r\n--x--\r\n", secret: "file-bytes-secret"},
|
||||
{name: "导出二进制", path: "/api/admin/export-tasks/1/download", contentType: "application/octet-stream", body: "binary-secret", secret: "binary-secret"},
|
||||
{name: "支付不可解析", path: "/api/callback/wechat-pay", contentType: "application/json", body: `{"sign":"broken-secret"`, secret: "broken-secret"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||||
app := fiber.New()
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Post(tc.path, func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusOK).Type("json").SendString(`{"access_token":"response-secret","status":"ok"}`)
|
||||
})
|
||||
request := httptest.NewRequest("POST", tc.path+"?signature=query-secret", strings.NewReader(tc.body))
|
||||
request.Header.Set("Content-Type", tc.contentType)
|
||||
request.Header.Set("Authorization", "Bearer header-secret")
|
||||
request.Header.Set("Cookie", "session=cookie-secret")
|
||||
response, err := app.Test(request)
|
||||
if err != nil {
|
||||
t.Fatalf("执行敏感路由请求失败:%v", err)
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, response.Body)
|
||||
_ = response.Body.Close()
|
||||
logged := output.String()
|
||||
for _, secret := range []string{tc.secret, "query-secret", "response-secret", "header-secret", "cookie-secret"} {
|
||||
if strings.Contains(logged, secret) {
|
||||
t.Fatalf("敏感路由日志泄露 %q:%s", secret, logged)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(logged, "body_policy") || !strings.Contains(logged, "sha256") {
|
||||
t.Fatalf("敏感路由日志缺少策略或摘要:%s", logged)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSensitiveRouteLongBodyRecordsTruncationWithoutRawSecret(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||||
app := fiber.New()
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Post("/api/admin/storage/upload", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) })
|
||||
secret := strings.Repeat("long-file-secret", MaxBodyLogSize)
|
||||
request := httptest.NewRequest("POST", "/api/admin/storage/upload", strings.NewReader(secret))
|
||||
request.Header.Set("Content-Type", "application/octet-stream")
|
||||
response, err := app.Test(request, 10000)
|
||||
if err != nil {
|
||||
t.Fatalf("执行超长敏感请求失败:%v", err)
|
||||
}
|
||||
_ = response.Body.Close()
|
||||
logged := output.String()
|
||||
if strings.Contains(logged, "long-file-secret") || !strings.Contains(logged, `"request_body_truncated":true`) {
|
||||
t.Fatalf("超长敏感载荷未安全摘要或未标记截断:%s", logged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealConfigurationRoutesOnlyLogSafeSummary(t *testing.T) {
|
||||
for _, path := range []string{
|
||||
"/api/admin/system-configs/payment.private_key",
|
||||
"/api/admin/wechat-configs/1",
|
||||
} {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||||
app := fiber.New()
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Put(path, func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"value": "response-private-material", "status": "ok"})
|
||||
})
|
||||
request := httptest.NewRequest("PUT", path, strings.NewReader(`{"value":"request-private-material","config_key":"payment.private_key"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response, err := app.Test(request)
|
||||
if err != nil {
|
||||
t.Fatalf("执行配置路由请求失败:%v", err)
|
||||
}
|
||||
_ = response.Body.Close()
|
||||
logged := output.String()
|
||||
for _, secret := range []string{"request-private-material", "response-private-material"} {
|
||||
if strings.Contains(logged, secret) {
|
||||
t.Fatalf("配置路由日志泄露 %q:%s", secret, logged)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(logged, `"body_policy":"sensitive_config"`) {
|
||||
t.Fatalf("配置路由未应用安全摘要策略:%s", logged)
|
||||
}
|
||||
if !strings.Contains(logged, `\"present\":true`) || !strings.Contains(logged, `\"length\":`) {
|
||||
t.Fatalf("配置路由未记录字段存在性和长度:%s", logged)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginRouteDoesNotLogUsernameAndOnlyKeepsPresenceLength(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||||
app := fiber.New()
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Post("/api/auth/login", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"username": "visible-user", "success": true})
|
||||
})
|
||||
request := httptest.NewRequest("POST", "/api/auth/login", strings.NewReader(`{"username":"visible-user","password":"login-secret"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response, err := app.Test(request)
|
||||
if err != nil {
|
||||
t.Fatalf("执行登录请求失败:%v", err)
|
||||
}
|
||||
_ = response.Body.Close()
|
||||
logged := output.String()
|
||||
if strings.Contains(logged, "visible-user") || strings.Contains(logged, "login-secret") {
|
||||
t.Fatalf("登录路由泄露账号或凭证:%s", logged)
|
||||
}
|
||||
if !strings.Contains(logged, `\"present\":true`) || !strings.Contains(logged, `\"length\":`) {
|
||||
t.Fatalf("登录路由未记录字段存在性和长度:%s", logged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentRouteOnlyLogsPresenceLengthAndSafeResult(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||||
app := fiber.New()
|
||||
app.Use(MiddlewareWithLogger(log))
|
||||
app.Post("/api/callback/alipay", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"payment_no": "PAY-SECRET-1", "amount": 12345, "status": "success"})
|
||||
})
|
||||
request := httptest.NewRequest("POST", "/api/callback/alipay", strings.NewReader("order_no=ORDER-SECRET-1&amount=12345&sign=signature-secret"))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
response, err := app.Test(request)
|
||||
if err != nil {
|
||||
t.Fatalf("执行支付路由请求失败:%v", err)
|
||||
}
|
||||
_ = response.Body.Close()
|
||||
logged := output.String()
|
||||
for _, value := range []string{"PAY-SECRET-1", "ORDER-SECRET-1", "signature-secret"} {
|
||||
if strings.Contains(logged, value) {
|
||||
t.Fatalf("支付路由泄露原值 %q:%s", value, logged)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(logged, `\"present\":true`) || !strings.Contains(logged, `\"status\":\"success\"`) {
|
||||
t.Fatalf("支付路由缺少存在性摘要或安全结果:%s", logged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeInvalidJSONNeverFallsBackToRawBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
raw := []byte(`{"password":"should-never-appear"`)
|
||||
result := sanitizeBody(raw, BodyPolicyJSON)
|
||||
if strings.Contains(result.Content, "should-never-appear") {
|
||||
t.Fatalf("解析失败时不得回退原文:%s", result.Content)
|
||||
}
|
||||
if result.SHA256 == "" || result.Size != len(raw) {
|
||||
t.Fatalf("解析失败摘要缺少大小或哈希:%+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeQueryUsesSameSensitiveFieldRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
query := sanitizeQuery("page=1&Authorization=Bearer-secret&nonce=abc")
|
||||
if strings.Contains(query.Content, "Bearer-secret") || strings.Contains(query.Content, "abc") {
|
||||
t.Fatalf("query 敏感值未脱敏:%s", query.Content)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestEnqueueTaskRejectsPreSerializedBytesBeforeQueueAccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &Client{logger: zap.NewNop()}
|
||||
if err := client.EnqueueTask(context.Background(), "test:bytes", []byte(`{"task_id":1}`)); err == nil {
|
||||
t.Fatal("预序列化 []byte 载荷必须在访问 Asynq 前被拒绝")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user