From 349957360db0cddaf4bdc0e052e11aec37364f81 Mon Sep 17 00:00:00 2001 From: chenchen Date: Mon, 8 Jun 2026 17:34:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(heicode-self):=20quota=5Fdisplay=5Ftype=20?= =?UTF-8?q?=E8=A1=A5=20CUSTOM=20+=20=E7=BB=9F=E4=B8=80=E5=A4=A7=E5=86=99?= =?UTF-8?q?=20+=20=E5=8A=A0=20displayQuota=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 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 --- heicode/controller/heicode_self.go | 23 +++++++---- heicode/controller/heicode_self_test.go | 55 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 heicode/controller/heicode_self_test.go diff --git a/heicode/controller/heicode_self.go b/heicode/controller/heicode_self.go index 52f51b9a..e2a4f267 100644 --- a/heicode/controller/heicode_self.go +++ b/heicode/controller/heicode_self.go @@ -11,16 +11,23 @@ import ( "github.com/heicode/manager/setting/operation_setting" ) -// quotaDisplayUnitLabel names the unit the raw quota is denominated in. -// Returns USD / CNY / tokens (mirrors the web dashboard's quota display type). +// quotaDisplayUnitLabel names the unit the raw quota is denominated in. Returns +// 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 { switch operation_setting.GetQuotaDisplayType() { case operation_setting.QuotaDisplayTypeCNY: - return "CNY" + return operation_setting.QuotaDisplayTypeCNY case operation_setting.QuotaDisplayTypeTokens: - return "tokens" + return operation_setting.QuotaDisplayTypeTokens + case operation_setting.QuotaDisplayTypeCustom: + return operation_setting.QuotaDisplayTypeCustom default: - return "USD" + return operation_setting.QuotaDisplayTypeUSD } } @@ -31,12 +38,12 @@ func quotaDisplayUnitLabel() string { // USD : raw / quota_per_unit // CNY : raw / quota_per_unit * usd_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, // so clients that prefer raw can recompute the same value. 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) / perUnit * rate @@ -93,7 +100,7 @@ func GetHeicodeSelf(c *gin.Context) { "quota": user.Quota, "used_quota": user.UsedQuota, "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) // ready-to-show values (server applies the display-type formula above). "display_quota": displayQuota(user.Quota, label, perUnit, rate), diff --git a/heicode/controller/heicode_self_test.go b/heicode/controller/heicode_self_test.go new file mode 100644 index 00000000..427bedcd --- /dev/null +++ b/heicode/controller/heicode_self_test.go @@ -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()) + } +}