后台配置与用户端展示的套餐价格统一使用 $ 符号,而实际扣款币种随 支付渠道配置漂移(如 Stripe 配置 NZD 时按新西兰元扣款),用户易 误认为美元。本次为套餐新增可选的展示性币种标注: - SubscriptionPlan 新增 currency 字段(ISO 4217 三字母码,默认空) - 空值不展示任何标注,存量套餐行为完全不变 - 非空值复用 payment.NormalizePaymentCurrency 校验并规范化为大写 - 后台套餐编辑弹窗新增可选币种输入,列表价格列展示币种小字 - 用户端套餐卡片价格与划线原价旁展示币种小字 - 纯展示性质,不影响任何支付/扣款逻辑 Closes #4315 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
240 lines
7.0 KiB
Go
240 lines
7.0 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestValidatePlanRequired_AllValid(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, "days", nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanRequired_EmptyName(t *testing.T) {
|
|
err := validatePlanRequired("", 1, 9.99, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "plan name")
|
|
}
|
|
|
|
func TestValidatePlanRequired_WhitespaceName(t *testing.T) {
|
|
err := validatePlanRequired(" ", 1, 9.99, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "plan name")
|
|
}
|
|
|
|
func TestValidatePlanRequired_ZeroGroupID(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 0, 9.99, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "group")
|
|
}
|
|
|
|
func TestValidatePlanRequired_NegativeGroupID(t *testing.T) {
|
|
err := validatePlanRequired("Pro", -1, 9.99, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "group")
|
|
}
|
|
|
|
func TestValidatePlanRequired_ZeroPrice(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 0, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "price")
|
|
}
|
|
|
|
func TestValidatePlanRequired_NegativePrice(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, -5, 30, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "price")
|
|
}
|
|
|
|
func TestValidatePlanRequired_ZeroValidityDays(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 9.99, 0, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity days")
|
|
}
|
|
|
|
func TestValidatePlanRequired_NegativeValidityDays(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 9.99, -7, "days", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity days")
|
|
}
|
|
|
|
func TestValidatePlanRequired_EmptyValidityUnit(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, "", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity unit")
|
|
}
|
|
|
|
func TestValidatePlanRequired_WhitespaceValidityUnit(t *testing.T) {
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, " ", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity unit")
|
|
}
|
|
|
|
func TestValidatePlanRequired_NameValidatedFirst(t *testing.T) {
|
|
err := validatePlanRequired("", 0, 0, 0, "", nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "plan name")
|
|
}
|
|
|
|
func TestValidatePlanRequired_TrimmedValidName(t *testing.T) {
|
|
err := validatePlanRequired(" Pro ", 1, 9.99, 30, "days", nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanRequired_NegativeOriginalPrice(t *testing.T) {
|
|
neg := -10.0
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, "days", &neg)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "original price")
|
|
}
|
|
|
|
func TestValidatePlanRequired_ZeroOriginalPrice(t *testing.T) {
|
|
zero := 0.0
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, "days", &zero)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanRequired_ValidOriginalPrice(t *testing.T) {
|
|
op := 19.99
|
|
err := validatePlanRequired("Pro", 1, 9.99, 30, "days", &op)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// --- validatePlanPatch tests ---
|
|
|
|
func TestValidatePlanPatch_NegativeOriginalPrice(t *testing.T) {
|
|
neg := -5.0
|
|
err := validatePlanPatch(UpdatePlanRequest{OriginalPrice: &neg})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "original price")
|
|
}
|
|
|
|
func TestValidatePlanPatch_ZeroOriginalPrice(t *testing.T) {
|
|
zero := 0.0
|
|
err := validatePlanPatch(UpdatePlanRequest{OriginalPrice: &zero})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanPatch_ValidOriginalPrice(t *testing.T) {
|
|
op := 29.99
|
|
err := validatePlanPatch(UpdatePlanRequest{OriginalPrice: &op})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanPatch_NilOriginalPrice(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{OriginalPrice: nil})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// --- validatePlanPatch: other fields ---
|
|
|
|
func ptrStr(s string) *string { return &s }
|
|
func ptrInt(i int) *int { return &i }
|
|
func ptrInt64(i int64) *int64 { return &i }
|
|
func ptrFloat(f float64) *float64 { return &f }
|
|
|
|
func TestValidatePlanPatch_EmptyName(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{Name: ptrStr("")})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "plan name")
|
|
}
|
|
|
|
func TestValidatePlanPatch_ValidName(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{Name: ptrStr("Basic")})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanPatch_ZeroGroupID(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{GroupID: ptrInt64(0)})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "group")
|
|
}
|
|
|
|
func TestValidatePlanPatch_NegativePrice(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{Price: ptrFloat(-1)})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "price")
|
|
}
|
|
|
|
func TestValidatePlanPatch_ZeroPrice(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{Price: ptrFloat(0)})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "price")
|
|
}
|
|
|
|
func TestValidatePlanPatch_ValidPrice(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{Price: ptrFloat(9.99)})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanPatch_ZeroValidityDays(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{ValidityDays: ptrInt(0)})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity days")
|
|
}
|
|
|
|
func TestValidatePlanPatch_EmptyValidityUnit(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{ValidityUnit: ptrStr("")})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "validity unit")
|
|
}
|
|
|
|
func TestValidatePlanPatch_ValidValidityUnit(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{ValidityUnit: ptrStr("days")})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestValidatePlanPatch_AllNil(t *testing.T) {
|
|
err := validatePlanPatch(UpdatePlanRequest{})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// --- normalizePlanCurrency tests ---
|
|
// Empty must stay empty (not coerced to the default payment currency),
|
|
// so existing plans keep rendering without any currency label.
|
|
|
|
func TestNormalizePlanCurrency_EmptyKeepsEmpty(t *testing.T) {
|
|
currency, err := normalizePlanCurrency("")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", currency)
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_WhitespaceKeepsEmpty(t *testing.T) {
|
|
currency, err := normalizePlanCurrency(" ")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", currency)
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_LowercaseNormalized(t *testing.T) {
|
|
currency, err := normalizePlanCurrency("nzd")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "NZD", currency)
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_ValidUppercase(t *testing.T) {
|
|
currency, err := normalizePlanCurrency("USD")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "USD", currency)
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_TooShort(t *testing.T) {
|
|
_, err := normalizePlanCurrency("NZ")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "currency")
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_TooLong(t *testing.T) {
|
|
_, err := normalizePlanCurrency("NZDD")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "currency")
|
|
}
|
|
|
|
func TestNormalizePlanCurrency_NonLetter(t *testing.T) {
|
|
_, err := normalizePlanCurrency("N2D")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "currency")
|
|
}
|