0ae7769abe
- gateway: TestSettleConcurrentSameOrder 真并发钉住 MarkAttemptPaid 行锁不变量 (文件型 sqlite,规避 in-memory cache=shared 的 SQLITE_LOCKED_SHAREDCACHE)。 - gateway: e2e_fullchain_test.go 补下单→回调→settle→webhook 实际 HTTP 投递→ delivered 整链(httptest server + 真实 store.WebhookStore/webhook.Notifier)。 - reconcile: main.go 装配抽到 reconcile.Assemble(+Runner.TaskNames 访问器), 补 assembly_test.go 钉住 7 个后台任务全部注册 + crypto 缺渠道/interval=0 分支。 - alipay: 补验签健壮性(未知多余字段/字段乱序/中文unicode/空值字段),意外定位 vendor Encoder 对空值字段的真实语义与官方文档描述不同,已记录在测试注释。 - money: 补 TestZeroDecimalCurrencyJPYNotSupported,记录 JPY/KRW 当前未注册进 exponents(接入会先踩 ErrUnknownCurrency,不是乘除法坑)。
123 lines
4.6 KiB
Go
123 lines
4.6 KiB
Go
package money_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/wangjia/pay/internal/money"
|
|
)
|
|
|
|
func TestParseFormatRoundTrip(t *testing.T) {
|
|
cases := []struct {
|
|
s, cur string
|
|
minor int64
|
|
}{
|
|
{"12.3417", "USDT", 12341700},
|
|
{"0.01", "CNY", 1},
|
|
{"29.99", "USD", 2999},
|
|
{"199.99", "CNY", 19999},
|
|
{"1", "USDT", 1000000},
|
|
}
|
|
for _, c := range cases {
|
|
got, err := money.Parse(c.s, c.cur)
|
|
if err != nil || got != c.minor {
|
|
t.Fatalf("Parse(%q,%s)=%d,%v want %d", c.s, c.cur, got, err, c.minor)
|
|
}
|
|
back, err := money.Format(c.minor, c.cur)
|
|
if err != nil {
|
|
t.Fatalf("Format(%d,%s): %v", c.minor, c.cur, err)
|
|
}
|
|
// round-trip 回同一 minor(去尾零后可能字符串不同,再 Parse 校验)
|
|
again, _ := money.Parse(back, c.cur)
|
|
if again != c.minor {
|
|
t.Fatalf("round-trip %d → %q → %d", c.minor, back, again)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseErrors(t *testing.T) {
|
|
if _, err := money.Parse("1.00", "JPYX"); err == nil {
|
|
t.Fatal("未知币种应报错")
|
|
}
|
|
if _, err := money.Parse("0.001", "CNY"); err == nil {
|
|
t.Fatal("超精度(CNY 3 位小数)应报错")
|
|
}
|
|
if _, err := money.Parse("abc", "CNY"); err == nil {
|
|
t.Fatal("非数字应报错")
|
|
}
|
|
}
|
|
|
|
func TestFormatUnknownCurrency(t *testing.T) {
|
|
_, err := money.Format(123, "JPYX")
|
|
if err == nil {
|
|
t.Fatal("未知币种应报错")
|
|
}
|
|
if !errors.Is(err, money.ErrUnknownCurrency) {
|
|
t.Fatalf("want ErrUnknownCurrency, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseOverflow(t *testing.T) {
|
|
// whole part fits int64 on its own, but whole*mult(+frac) overflows int64.
|
|
if _, err := money.Parse("92233720368547759", "CNY"); err == nil {
|
|
t.Fatal("整数部分过大导致 minor 溢出 int64 应报错")
|
|
}
|
|
if _, err := money.Parse("92233720368547758.07", "CNY"); err != nil {
|
|
t.Fatalf("恰好等于 int64 上限不应报错: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestZeroDecimalCurrencyJPYNotSupported 覆盖补测项 ⑩a:先读 internal/money/money.go
|
|
// 确认现状——exponents 只注册了 CNY/USD/USDT(2/2/6 位小数),不含任何 0 位小数币种
|
|
// (JPY/KRW 等)。money.Format 内部虽然写了 `if exp == 0 { return strconv.FormatInt(...) }`
|
|
// 分支(不做除法/拼小数点),说明"按币种可变小数位"这个架构本身具备支持零小数位
|
|
// 的能力,Parse 侧 pow10Int64(0)==1 也天然不做 *100/÷100 那种隐式换算;但因为
|
|
// exponents 表里从未注册过任何 exp=0 的币种,这条分支在生产/测试代码里从未被真正
|
|
// 触发过——**JPY 本身当前不支持**,接入会在 Exponent()/Parse()/Format() 三处直接拿到
|
|
// ErrUnknownCurrency(是"未知币种"硬拒,不是更隐蔽的"用错乘除法"那种坑)。
|
|
//
|
|
// 待改进记录(供后续接入 JPY/KRW 时参考,未经验证的推测,不代表已确认可行):
|
|
// 读代码看,大概率只需在 money.go 的 exponents map 里补一行 "JPY": 0 / "KRW": 0,
|
|
// Format 的 exp==0 分支、Parse 的 pow10Int64(0)=1 应该不用改——但这只是读源码后的
|
|
// 推断,本测试的黑盒范围(money_test 外部包,不碰 money.go)验证不了"补一行就真的
|
|
// 够了",只钉住"今天 JPY/KRW 确实不支持"这一现状,防止有人以为已经支持了。
|
|
func TestZeroDecimalCurrencyJPYNotSupported(t *testing.T) {
|
|
if _, ok := money.Exponent("JPY"); ok {
|
|
t.Fatal("JPY 已被注册进 exponents——本测试钉的是「未注册」旧行为,请更新/删除,并按其真实小数位数补 Parse/Format round-trip 用例")
|
|
}
|
|
if _, err := money.Parse("100", "JPY"); !errors.Is(err, money.ErrUnknownCurrency) {
|
|
t.Fatalf("JPY 未注册,Parse 应报 ErrUnknownCurrency, got %v", err)
|
|
}
|
|
if _, err := money.Format(100, "JPY"); !errors.Is(err, money.ErrUnknownCurrency) {
|
|
t.Fatalf("JPY 未注册,Format 应报 ErrUnknownCurrency, got %v", err)
|
|
}
|
|
// KRW(韩元)同为真实零小数位币种,同样未注册——顺带钉住,避免误以为只有 JPY 漏了。
|
|
if _, ok := money.Exponent("KRW"); ok {
|
|
t.Fatal("KRW 已被注册进 exponents——本测试钉的是「未注册」旧行为,请更新/删除")
|
|
}
|
|
if _, err := money.Parse("100", "KRW"); !errors.Is(err, money.ErrUnknownCurrency) {
|
|
t.Fatalf("KRW 未注册,Parse 应报 ErrUnknownCurrency, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseFormatNegative(t *testing.T) {
|
|
minor, err := money.Parse("-12.34", "CNY")
|
|
if err != nil {
|
|
t.Fatalf("Parse(-12.34): %v", err)
|
|
}
|
|
if minor != -1234 {
|
|
t.Fatalf("Parse(-12.34)=%d want -1234", minor)
|
|
}
|
|
back, err := money.Format(-1234, "CNY")
|
|
if err != nil {
|
|
t.Fatalf("Format(-1234): %v", err)
|
|
}
|
|
again, err := money.Parse(back, "CNY")
|
|
if err != nil {
|
|
t.Fatalf("Parse(%q): %v", back, err)
|
|
}
|
|
if again != -1234 {
|
|
t.Fatalf("round-trip -1234 → %q → %d", back, again)
|
|
}
|
|
}
|