Files
Qianandchengqian be9e707bfd 修复异步图片导致的多个问题 (#12)
* fix:修复响应污染问题

* fix:修改存储相关文件为中文注释

* fix:修复二次退款问题

* fix:修复多个考虑不全面问题
1.误删用户数据
2.R2存储默认区域
3.删除失败导致孤儿记录
4.网络抖动事变时重试
5.前端无限次请求获取图片
6.文件管理权限

* fix:修复字符串截断异常问题和文件下载超时问题

* fix:修复多图问题

* fix:异步生成图片时未配置oss提示错误信息

* fix:修复关闭异步后同步生成出错问题

* feat:新增文件存储测试功能,防止设置后不生效

* fix:修复同步图片转异步后计费错误

* fix:修复无下限扣款问题

* fix:修复结算重复扣款问题

* fix:修复计费出错导致的0费用结算

* fix:修复回调双重履约

* fix:修复预扣事务

* fix:修复费用字段类型问题

* fix:修复缓存问题

* fix:修复安全问题

* fix:修复数据库操作事务问题

* refactor:重构事务使用方式,改为官方推荐的dao形式

* fix:修复余额更新隐患

* fix:修复N+1查询问题

* fix:修复密钥安全问题

* fix:结算失败时打印错误日志

* improve:优化扣款

* fix:修复用户接口越权

---------

Co-authored-by: chengqian <wuchengqian@kkidc.com>
2026-07-22 17:53:18 +08:00

93 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"testing"
"time"
)
// TestBuildIdempotencyKey 验证幂等键按认证主体隔离(对应安全修复 #7)。
// 核心不变量:不同租户/用户即便复用同一客户端 Idempotency-Key,也必须得到不同的存储键,
// 否则会命中彼此缓存的响应体(跨租户数据泄漏)或互相误触 409。
func TestBuildIdempotencyKey(t *testing.T) {
const clientKey = "client-supplied-uuid"
t.Run("相同主体+相同客户端key -> 相同存储键(幂等生效)", func(t *testing.T) {
a := buildIdempotencyKey("tenant", 1, 10, clientKey)
b := buildIdempotencyKey("tenant", 1, 10, clientKey)
if a != b {
t.Fatalf("same principal must yield same key: %q != %q", a, b)
}
})
t.Run("不同租户复用同一客户端key -> 不同存储键(防跨租户泄漏)", func(t *testing.T) {
tenantA := buildIdempotencyKey("tenant", 1, 10, clientKey)
tenantB := buildIdempotencyKey("tenant", 2, 10, clientKey)
if tenantA == tenantB {
t.Fatalf("different tenants must not collide on the same client key: %q", tenantA)
}
})
t.Run("同租户不同用户 -> 不同存储键", func(t *testing.T) {
userA := buildIdempotencyKey("tenant", 1, 10, clientKey)
userB := buildIdempotencyKey("tenant", 1, 11, clientKey)
if userA == userB {
t.Fatalf("different users must not collide: %q", userA)
}
})
t.Run("不同用户类型 -> 不同存储键(admin 与 tenant 不混)", func(t *testing.T) {
admin := buildIdempotencyKey("admin", 1, 10, clientKey)
tenant := buildIdempotencyKey("tenant", 1, 10, clientKey)
if admin == tenant {
t.Fatalf("different user types must not collide: %q", admin)
}
})
t.Run("不同客户端key -> 不同存储键", func(t *testing.T) {
k1 := buildIdempotencyKey("tenant", 1, 10, "key-1")
k2 := buildIdempotencyKey("tenant", 1, 10, "key-2")
if k1 == k2 {
t.Fatalf("different client keys must differ: %q", k1)
}
})
}
// TestOpenNonceKey 验证 nonce 去重键按应用隔离(对应安全修复 #4)。
func TestOpenNonceKey(t *testing.T) {
t.Run("相同 app+nonce -> 相同键(确定性)", func(t *testing.T) {
k1 := openNonceKey(7, "abc")
k2 := openNonceKey(7, "abc")
if k1 != k2 {
t.Fatal("nonce key must be deterministic")
}
})
t.Run("不同 app 相同 nonce -> 不同键(按应用隔离)", func(t *testing.T) {
if openNonceKey(7, "abc") == openNonceKey(8, "abc") {
t.Fatal("same nonce under different apps must not collide")
}
})
t.Run("相同 app 不同 nonce -> 不同键", func(t *testing.T) {
if openNonceKey(7, "abc") == openNonceKey(7, "xyz") {
t.Fatal("different nonces must differ")
}
})
}
// TestOpenNonceTTL 验证 nonce 记录存活时间足以覆盖整个可重放窗口(对应安全修复 #4)。
// 请求在 [ts-skew, ts+skew] 内均可能通过时间戳校验,因此 nonce 至少要记住 2×skew,
// 才能保证同一 nonce 在其所有可被接受的时刻都已存在于 Redis。
func TestOpenNonceTTL(t *testing.T) {
ttl := openNonceTTLSeconds()
minRequired := int(2 * openPlatformMaxSkew / time.Second)
if ttl < minRequired {
t.Fatalf("nonce TTL %ds must cover the full replay window (>= %ds)", ttl, minRequired)
}
// 当前配置:skew=5min → TTL=600s
if ttl != 600 {
t.Fatalf("expected TTL 600s for a 5min skew window, got %ds", ttl)
}
}