Files
llmgw/internal/store/selfusage_test.go
T
Ilker ManapandClaude Opus 5.5 16a1e8b4d8 Show a person their own use and what is left of their budgets
The account page (was "Your keys") shows the last thirty days of the
person's own use - requests, tokens, spend and tokens per day - and, for
each place they can spend, what they used there and a bar for every
budget that applies: their own, a per-member one, or a project's, team's
or organization's shared with others, named as such. Bars turn amber past
four-fifths and red when spent. It sits under the keys now, above the
password form.

Spend is read from the usage table for the pool each limit counts
against, from the start of its period.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:51:13 +02:00

93 lines
2.5 KiB
Go

package store
import (
"context"
"path/filepath"
"testing"
"time"
)
func selfUsageFixture(t *testing.T, s *DB) time.Time {
t.Helper()
ctx := context.Background()
must := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
must(s.PutOrg(ctx, Org{ID: "o", Name: "Acme"}))
must(s.PutTeam(ctx, Team{ID: "t", OrgID: "o", Name: "ml"}))
must(s.PutProject(ctx, Project{ID: "p", TeamID: "t", Name: "training"}))
must(s.PutProject(ctx, Project{ID: "q", TeamID: "t", Name: "eval"}))
now := time.Now()
u := func(user, project string, cost int64) Usage {
return Usage{At: now, KeyID: "session:" + user, UserID: user, ProjectID: project,
Model: "m", ServedModel: "m", PromptTokens: 10, CompletionTokens: 5, CostMicros: cost, Status: 200}
}
must(s.WriteUsage(ctx, []Usage{
u("ada", "p", 100), u("ada", "p", 100), u("ada", "", 30), u("bob", "p", 1000), u("bob", "q", 7),
}))
return now
}
func checkSelfUsage(t *testing.T, s *DB) {
now := selfUsageFixture(t, s)
ctx := context.Background()
since := now.Add(-48 * time.Hour)
places, buckets, err := s.UsageOfUser(ctx, "ada", since, 24*time.Hour)
if err != nil {
t.Fatal(err)
}
got := map[string]PlaceUsage{}
for _, pl := range places {
got[pl.ProjectID] = pl
}
// Ada's own, and none of Bob's.
if len(places) != 2 || got["p"].Requests != 2 || got["p"].Path != "Acme / ml / training" ||
got[""].CostMicros != 30 || got["p"].Tokens != 30 {
t.Errorf("places = %+v", places)
}
var total int64
for _, b := range buckets {
total += b.Requests
}
if total != 3 || len(buckets) < 2 {
t.Errorf("%d buckets carrying %d requests", len(buckets), total)
}
for _, c := range []struct {
l Limits
want int64
}{
{Limits{Scope: ScopeUser, ScopeID: "ada"}, 230},
{Limits{Scope: ScopeProject, ScopeID: "p"}, 1200},
{Limits{Scope: ScopeTeam, ScopeID: "t"}, 1207},
{Limits{Scope: ScopeOrg, ScopeID: "o"}, 1207},
{Limits{Scope: ScopeProjectMember, ScopeID: "p/ada"}, 200},
{Limits{Scope: ScopeTeamMember, ScopeID: "t/bob"}, 1007},
} {
n, err := s.SpendOfPool(ctx, c.l, since)
if err != nil {
t.Fatal(err)
}
if n != c.want {
t.Errorf("%s %s spent %d, want %d", c.l.Scope, c.l.ScopeID, n, c.want)
}
}
}
func TestAPersonSeesTheirOwnUse(t *testing.T) {
s, err := OpenSQLite(context.Background(), filepath.Join(t.TempDir(), "u.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = s.Close() })
checkSelfUsage(t, s)
}
func TestPostgresAPersonSeesTheirOwnUse(t *testing.T) {
checkSelfUsage(t, openPG(t))
}