Files
ModelRouter/internal/core/billing_apply_test.go
root 0caa37ea6a fix(billing): 过期规则 URL 不再让整个 profile 失效(降级应用 + 点名 URL)
生产现网发现(issue #1 排查):billing.rules 里一条指向
https://opencode.ai/inference/v1 的规则,在该源 base_url 变成
.../inference/openai/v1 之后匹配不到任何源;严格编译因此让**整个** profile
不生效 —— 账单恒 0、unpriced_reqs 涨到 3.7 万,唯一痕迹是一行启动日志。

- 严格编译失败时降级为 lenient:应用能匹配的规则,其余按 profile default;
- 日志用 ERROR 点名**具体哪条 URL** 写错(新增 billing.UnmatchedRuleURLs),
  并说明未匹配流量的记账去向;
- 判据 TestBillingStaleRuleURLStillAppliesOtherRules:一条过期规则 + 一条有效
  规则 ⇒ 有效规则必须仍然生效。
2026-10-20 18:04:43 +08:00

61 lines
2.1 KiB
Go
Raw 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 core
import (
"path/filepath"
"testing"
"llmsproxy/internal/config"
)
// 一个过期的规则 URL 不得让**整个**计费 profile 失效。
//
// 真实事故(GitCode issue #1 排查中发现,生产现网):config.yaml 里
// `billing.rules[0].url` 是 `https://opencode.ai/inference/v1`,而该源的
// base_url 已是 `.../inference/openai/v1`。严格编译因此返回
// "rule url ... matches no configured source base_url",applyBillingDSL 直接
// return —— 于是**所有**计费规则都不生效:账单恒为 0、unpriced_reqs 涨到 3.7 万,
// 而唯一的痕迹是一行启动日志。这正是「静默少报」最严重的一种。
//
// 修法:严格编译失败时降级为 lenient(用能匹配的规则),并打 ERROR 说明后果。
// 一个 URL 写错不该把整个价目表一起葬送;但也不能装作成功。
func TestBillingStaleRuleURLStillAppliesOtherRules(t *testing.T) {
cfg := newTestConfig(t)
dir := t.TempDir()
cfg.PluginDir = filepath.Join(dir, "plugins")
cfg.Sources = []config.Source{
{Name: "localzen", BaseURL: "https://opencode.ai/inference/openai/v1", Adapter: "openai",
Models: []config.Model{{ID: "m"}}},
}
cfg.BillingDSL = &config.BillingDSL{
Active: "p",
Profiles: []config.BillingProfile{{
ID: "p",
Rules: []config.BillingRule{
// 过期 URL:没有任何源的 base_url 与它相等
{URL: "https://opencode.ai/inference/v1", Mode: "free"},
// 有效规则:必须仍然生效
{URL: "https://opencode.ai/inference/openai/v1", Mode: "free"},
},
}},
}
c := newTestCore(t, cfg)
c.applyBillingDSL()
raw := c.plugins.Prices("billing")
if raw == nil {
t.Fatal("billing has no price table (profile never applied)")
}
prices, ok := raw.(map[string]interface{})
if !ok {
t.Fatalf("price table shape: %#v", raw)
}
sources, ok := prices["sources"].(map[string]interface{})
if !ok {
t.Fatalf("prices has no sources: %#v", prices)
}
if _, ok := sources["localzen"]; !ok {
t.Fatalf("一条过期规则 URL 把整个 profile 弄失效了:有效的 free 规则也没有生效,"+
"结果是账单恒为 0 且没有告警。prices=%#v", prices)
}
}