fix(heicode-self): quota_display_type 补 CUSTOM + 统一大写 + 加 displayQuota 测试
按 Mem0ried 复审: - quotaDisplayUnitLabel 补 QuotaDisplayTypeCustom 分支(原落 default→"USD", 导致自定义币种站点 display_quota 算对但 type 误报 USD、客户端配错符号)。 - tokens 标签由小写 "tokens" 改为规范常量 TOKENS,displayQuota 的判断同步用常量, 对外 quota_display_type 统一大写(与 USD/CNY/CUSTOM 一致,客户端按常量比较不再 mismatch)。 - 新增表驱动测试 heicode_self_test.go:displayQuota 四模式(USD 除 / CNY·CUSTOM 乘 / TOKENS 原值)+ per_unit<=0 防除零;quotaDisplayUnitLabel 四模式(含 CUSTOM 回归)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,16 +11,23 @@ import (
|
|||||||
"github.com/heicode/manager/setting/operation_setting"
|
"github.com/heicode/manager/setting/operation_setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// quotaDisplayUnitLabel names the unit the raw quota is denominated in.
|
// quotaDisplayUnitLabel names the unit the raw quota is denominated in. Returns
|
||||||
// Returns USD / CNY / tokens (mirrors the web dashboard's quota display type).
|
// the canonical (UPPER-CASE) display-type constant — USD / CNY / TOKENS / CUSTOM
|
||||||
|
// — mirroring the web dashboard. The client uses this only to pick a currency
|
||||||
|
// symbol; the actual conversion is done server-side (display_quota). Returning the
|
||||||
|
// constants verbatim keeps the wire value byte-identical to what the client
|
||||||
|
// compares against, and covers CUSTOM (previously fell through to "USD", so a
|
||||||
|
// custom-currency site mislabelled its display_quota and the client showed "$").
|
||||||
func quotaDisplayUnitLabel() string {
|
func quotaDisplayUnitLabel() string {
|
||||||
switch operation_setting.GetQuotaDisplayType() {
|
switch operation_setting.GetQuotaDisplayType() {
|
||||||
case operation_setting.QuotaDisplayTypeCNY:
|
case operation_setting.QuotaDisplayTypeCNY:
|
||||||
return "CNY"
|
return operation_setting.QuotaDisplayTypeCNY
|
||||||
case operation_setting.QuotaDisplayTypeTokens:
|
case operation_setting.QuotaDisplayTypeTokens:
|
||||||
return "tokens"
|
return operation_setting.QuotaDisplayTypeTokens
|
||||||
|
case operation_setting.QuotaDisplayTypeCustom:
|
||||||
|
return operation_setting.QuotaDisplayTypeCustom
|
||||||
default:
|
default:
|
||||||
return "USD"
|
return operation_setting.QuotaDisplayTypeUSD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +38,12 @@ func quotaDisplayUnitLabel() string {
|
|||||||
// USD : raw / quota_per_unit
|
// USD : raw / quota_per_unit
|
||||||
// CNY : raw / quota_per_unit * usd_exchange_rate
|
// CNY : raw / quota_per_unit * usd_exchange_rate
|
||||||
// CUSTOM : raw / quota_per_unit * custom_currency_exchange_rate
|
// CUSTOM : raw / quota_per_unit * custom_currency_exchange_rate
|
||||||
// tokens : raw (no division)
|
// TOKENS : raw (no division)
|
||||||
//
|
//
|
||||||
// usd_exchange_rate is the USD→display-currency multiplier returned alongside,
|
// usd_exchange_rate is the USD→display-currency multiplier returned alongside,
|
||||||
// so clients that prefer raw can recompute the same value.
|
// so clients that prefer raw can recompute the same value.
|
||||||
func displayQuota(raw int, label string, perUnit, rate float64) float64 {
|
func displayQuota(raw int, label string, perUnit, rate float64) float64 {
|
||||||
if label == "tokens" || perUnit <= 0 {
|
if label == operation_setting.QuotaDisplayTypeTokens || perUnit <= 0 {
|
||||||
return float64(raw)
|
return float64(raw)
|
||||||
}
|
}
|
||||||
return float64(raw) / perUnit * rate
|
return float64(raw) / perUnit * rate
|
||||||
@@ -93,7 +100,7 @@ func GetHeicodeSelf(c *gin.Context) {
|
|||||||
"quota": user.Quota,
|
"quota": user.Quota,
|
||||||
"used_quota": user.UsedQuota,
|
"used_quota": user.UsedQuota,
|
||||||
"quota_per_unit": common.QuotaPerUnit,
|
"quota_per_unit": common.QuotaPerUnit,
|
||||||
"quota_display_type": label, // USD / CNY / tokens
|
"quota_display_type": label, // USD / CNY / TOKENS / CUSTOM (pick currency symbol only)
|
||||||
"usd_exchange_rate": rate, // USD→display-currency multiplier (1 for USD/tokens)
|
"usd_exchange_rate": rate, // USD→display-currency multiplier (1 for USD/tokens)
|
||||||
// ready-to-show values (server applies the display-type formula above).
|
// ready-to-show values (server applies the display-type formula above).
|
||||||
"display_quota": displayQuota(user.Quota, label, perUnit, rate),
|
"display_quota": displayQuota(user.Quota, label, perUnit, rate),
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/heicode/manager/setting/operation_setting"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// displayQuota is the /api/heicode/self conversion contract: the server applies
|
||||||
|
// the display-type formula so the client never special-cases a mode. Pin all
|
||||||
|
// four modes (USD divides, CNY/CUSTOM scale, TOKENS passes through) and the
|
||||||
|
// divide-by-zero guard.
|
||||||
|
func TestDisplayQuota_AllModes(t *testing.T) {
|
||||||
|
const perUnit = 500000.0
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
raw int
|
||||||
|
label string
|
||||||
|
rate float64
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{"USD divides by per-unit", 1_000_000, operation_setting.QuotaDisplayTypeUSD, 1, 2},
|
||||||
|
{"CNY multiplies by usd rate", 1_000_000, operation_setting.QuotaDisplayTypeCNY, 7.3, 14.6},
|
||||||
|
{"CUSTOM uses custom rate", 1_000_000, operation_setting.QuotaDisplayTypeCustom, 2.5, 5},
|
||||||
|
{"TOKENS returns raw, no division", 1_000_000, operation_setting.QuotaDisplayTypeTokens, 1, 1_000_000},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require.InDelta(t, tc.want, displayQuota(tc.raw, tc.label, perUnit, tc.rate), 1e-9)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// per_unit<=0 must never divide by zero — return raw unchanged.
|
||||||
|
require.Equal(t, float64(1_000_000), displayQuota(1_000_000, operation_setting.QuotaDisplayTypeUSD, 0, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// quotaDisplayUnitLabel must echo the canonical UPPER-CASE constant for every
|
||||||
|
// mode. The CUSTOM case is the regression guard: it previously fell through to
|
||||||
|
// "USD", mislabelling a custom-currency site's display_quota.
|
||||||
|
func TestQuotaDisplayUnitLabel_CanonicalAndCustom(t *testing.T) {
|
||||||
|
gs := operation_setting.GetGeneralSetting()
|
||||||
|
orig := gs.QuotaDisplayType
|
||||||
|
t.Cleanup(func() { gs.QuotaDisplayType = orig })
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
operation_setting.QuotaDisplayTypeUSD,
|
||||||
|
operation_setting.QuotaDisplayTypeCNY,
|
||||||
|
operation_setting.QuotaDisplayTypeTokens,
|
||||||
|
operation_setting.QuotaDisplayTypeCustom,
|
||||||
|
} {
|
||||||
|
gs.QuotaDisplayType = want
|
||||||
|
require.Equal(t, want, quotaDisplayUnitLabel())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user