feat(limits): unconditional usage accounting

buildLimitScopes previously omitted a user or group scope entirely
whenever that entity had no configured Limits, so a limit-less
user/group never accumulated ANY usage counters. Root-caused live:
the admin dashboard's total kept climbing while every per-user row
stayed zero until an operator added phantom, unreachable
requestsPerDay limits everywhere just to make counters build at all.

buildLimitScopes now always builds a user and a group scope. Limits
still gate enforcement only: evaluateScope already no-ops on a nil
Limits field, so a limit-less scope is counted but never violates.

Update stale doc comments coupling accounting to configured limits
(evaluateScope, the integration alice fixture, routes_media_test.go,
routes_unified_test.go, admin_test.go's admin1 assertion) and add:
- TestBuildLimitScopes_AlwaysBuildsBothScopes: table-driven over
  every nil/non-nil user/group limits combination.
- TestAdminUsage_LimitlessUserAndGroup_AccumulateRealTraffic:
  end-to-end regression proving a limit-less user and group
  accumulate real request/token counters visible via
  GET /admin/api/usage.
This commit is contained in:
2026-08-21 18:13:46 +01:00
parent 90545df588
commit 1183b6fa3c
6 changed files with 178 additions and 41 deletions
+7 -5
View File
@@ -76,11 +76,13 @@ func newMediaTestConfig(openaiURL string, models ...string) *Config {
"openai": {Type: "openai", BaseURL: openaiURL, APIKey: "sk-up", Models: models},
}
cfg.Groups = map[string]*GroupConfig{"default": {}}
// Limits is a non-nil (if all-zero, i.e. unlimited) *LimitsConfig so
// buildLimitScopes (routes_media.go/routes_unified.go) includes a
// "user" scope for alice — matching routes_unified_test.go's own
// happy-path fixture — otherwise checkAndCount has no scope to
// increment a counter for at all.
// Limits is a non-nil (if all-zero, i.e. unlimited) *LimitsConfig —
// matching routes_unified_test.go's own happy-path fixture. Since the
// v0.21 accounting fix, buildLimitScopes (routes_media.go/
// routes_unified.go) always builds a "user" scope for alice regardless
// of whether Limits is set at all; this fixture keeps a non-nil value
// only for parity with the other unified-route fixtures, not because it
// is required for checkAndCount to have a scope to increment.
cfg.Users = &UsersConfig{Inline: []*UserConfig{{Name: "alice", Group: "default", APIKey: "sk-alice", Limits: &LimitsConfig{}}}}
return cfg
}