Files
llmgw/internal/web/admin_quotas_test.go
T
Ilker ManapandClaude Opus 5 5726252825 Open the quota form on the branch, instead of asking which branch again
The tree was rendered and then, underneath it, a form with a dropdown asking
which of those rows you meant. The tree already said. Picking a branch, reading
its current numbers, scrolling past the tree and selecting the same branch from
a list is three steps to do one thing, and two of them are a chance to pick the
wrong row.

A quota belongs to a branch, so the form opens on it: each row links to
?edit=scope:id, that row renders its form inline, pre-filled with what is set
there now and naming the branch in full.

By link rather than by disclosure on every row: a collapsed form per branch
would put a form in the markup for every group on a page whose job is to be
read as a tree. One is open at a time, and the rest keep their link.

Clearing moved into that form as a second submit, so a row that is not being
edited has one control and not two.

Saving closes it. Re-rendering the form over the row it just changed hides the
result and invites submitting the same thing twice.

The note about a quota being shared came out of the footer: the open form says
it, at the point where somebody is about to do it.

The dropdown's parsing went with it rather than being left unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-27 14:54:02 +02:00

303 lines
9.9 KiB
Go

package web
import (
"context"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
type fakeQuotas struct {
orgs []store.Org
teams []store.TeamPath
projects []store.ProjectPath
limits map[string]store.Limits
usage map[store.Scope][]store.ScopeUsage
put []store.Limits
cleared [][2]string
}
func (f *fakeQuotas) AllOrgs(context.Context) ([]store.Org, error) { return f.orgs, nil }
func (f *fakeQuotas) AllTeams(context.Context) ([]store.TeamPath, error) {
return f.teams, nil
}
func (f *fakeQuotas) AllProjects(context.Context) ([]store.ProjectPath, error) {
return f.projects, nil
}
func (f *fakeQuotas) AllLimits(context.Context) (map[string]store.Limits, error) {
if f.limits == nil {
f.limits = map[string]store.Limits{}
}
return f.limits, nil
}
func (f *fakeQuotas) PutLimits(_ context.Context, l store.Limits) error {
f.put = append(f.put, l)
return nil
}
func (f *fakeQuotas) ClearLimits(_ context.Context, s store.Scope, id string) error {
f.cleared = append(f.cleared, [2]string{string(s), id})
return nil
}
func (f *fakeQuotas) UsageByScope(_ context.Context, s store.Scope, _ time.Time) ([]store.ScopeUsage, error) {
return f.usage[s], nil
}
func quotasPage(t *testing.T, s *fakeQuotas, admin bool) *http.ServeMux {
t.Helper()
a := fakeAccounts{user: store.User{ID: "u1", Email: "a@example.com", IsAdmin: admin}}
p := NewAccountPages(a, nil, nil, "v0", slog.New(slog.NewTextHandler(io.Discard, nil)))
mux := http.NewServeMux()
p.RegisterAdminQuotas(mux, s)
return mux
}
func i64(v int64) *int64 { return &v }
// oneTeam is an org with a team and a project under it.
func oneTeam() *fakeQuotas {
return &fakeQuotas{
orgs: []store.Org{{ID: "org-1", Name: "Scania"}},
teams: []store.TeamPath{{ID: "team-1", Name: "platform", OrgID: "org-1", OrgName: "Scania"}},
projects: []store.ProjectPath{{ID: "proj-1", Path: "Scania / platform / gateway"}},
limits: map[string]store.Limits{},
}
}
func TestTheWholeTreeIsListedWhetherOrNotItHasAQuota(t *testing.T) {
body := mirrorGet(t, quotasPage(t, oneTeam(), true), "/admin/quotas").Body.String()
// A group with no quota is the one most likely to need one, so it cannot
// be the one the page leaves out.
for _, want := range []string{"Scania", "platform", "gateway"} {
if !strings.Contains(body, want) {
t.Errorf("%q is missing from the page", want)
}
}
}
func TestSpendIsShownAgainstTheBudget(t *testing.T) {
s := oneTeam()
s.limits["team:team-1"] = store.Limits{
Scope: store.ScopeTeam, ScopeID: "team-1",
Budget: i64(2_000_000), BudgetPeriod: store.PeriodMonth,
}
s.usage = map[store.Scope][]store.ScopeUsage{
store.ScopeTeam: {{ID: "team-1", CostMicros: 1_000_000}},
}
body := mirrorGet(t, quotasPage(t, s, true), "/admin/quotas").Body.String()
if !strings.Contains(body, "50%") {
t.Errorf("expected half the budget used to be shown; got %s", body)
}
}
func TestALimitAnAncestorAlreadyCapsIsMarked(t *testing.T) {
s := oneTeam()
// The team is given more than its organization allows. Resolution takes the
// most restrictive, so this is not a bigger budget — it is a number that
// will never be reached.
s.limits["org:org-1"] = store.Limits{
Scope: store.ScopeOrg, ScopeID: "org-1", Budget: i64(1_000_000),
BudgetPeriod: store.PeriodMonth,
}
s.limits["team:team-1"] = store.Limits{
Scope: store.ScopeTeam, ScopeID: "team-1", Budget: i64(5_000_000),
BudgetPeriod: store.PeriodMonth,
}
body := mirrorGet(t, quotasPage(t, s, true), "/admin/quotas").Body.String()
if !strings.Contains(body, "Scania caps this at") {
t.Errorf("a shadowed limit should say so; got %s", body)
}
}
func TestALimitNothingCapsIsNotMarked(t *testing.T) {
s := oneTeam()
s.limits["org:org-1"] = store.Limits{
Scope: store.ScopeOrg, ScopeID: "org-1", Budget: i64(9_000_000),
BudgetPeriod: store.PeriodMonth,
}
s.limits["team:team-1"] = store.Limits{
Scope: store.ScopeTeam, ScopeID: "team-1", Budget: i64(1_000_000),
BudgetPeriod: store.PeriodMonth,
}
body := mirrorGet(t, quotasPage(t, s, true), "/admin/quotas").Body.String()
if strings.Contains(body, "caps this at") {
t.Error("a limit stricter than its parent's is in force and must not be flagged")
}
}
func TestABudgetIsTakenInWholeUnitsAndStoredInMicros(t *testing.T) {
s := oneTeam()
mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"},
"budget": {"1000"}, "period": {"month"},
})
if len(s.put) != 1 || s.put[0].Budget == nil || *s.put[0].Budget != 1_000_000_000 {
t.Fatalf("expected 1000 units stored as micro-units, got %+v", s.put)
}
}
func TestABudgetWithoutAPeriodIsRefused(t *testing.T) {
s := oneTeam()
body := mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"}, "budget": {"1000"},
}).Body.String()
// A monthly budget credited to a daily counter is a budget that never
// binds, so the period cannot be left to a default.
if len(s.put) != 0 {
t.Error("nothing should have been written")
}
if !strings.Contains(body, "period") {
t.Errorf("the error should name what is missing; got %s", body)
}
}
func TestABlankFieldMeansNoLimitRatherThanZero(t *testing.T) {
s := oneTeam()
mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"}, "rpm": {"60"},
})
// Zero would be a limit of nothing — every request refused — which is the
// opposite of what an empty box means.
if len(s.put) != 1 {
t.Fatalf("expected one write, got %+v", s.put)
}
if s.put[0].TPM != nil {
t.Errorf("a blank field should leave no limit, got %d", *s.put[0].TPM)
}
if s.put[0].RPM == nil || *s.put[0].RPM != 60 {
t.Errorf("the value given should be written, got %+v", s.put[0].RPM)
}
}
func TestRubbishInANumberIsRefused(t *testing.T) {
s := oneTeam()
mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"}, "rpm": {"lots"},
})
if len(s.put) != 0 {
t.Error("nothing should have been written")
}
}
func TestAQuotaCanBeRemovedEntirely(t *testing.T) {
s := oneTeam()
mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"}, "clear": {"1"},
})
// A row of nulls and no row resolve the same, but only one of them tells
// somebody reading the table that nobody has set a limit here.
if len(s.cleared) != 1 || s.cleared[0] != [2]string{"team", "team-1"} {
t.Errorf("expected the limits to be cleared, got %v", s.cleared)
}
if len(s.put) != 0 {
t.Error("clearing must not also write a row")
}
}
func TestAQuotaOnSomethingThatIsNotAGroupIsRefused(t *testing.T) {
s := oneTeam()
body := mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"key"}, "id": {"key-1"}, "rpm": {"1"},
}).Body.String()
// Per-key limits stay on the CLI: an admin session reaching individual keys
// would put every caller's throughput one click away.
if len(s.put) != 0 {
t.Error("a key limit should not be settable here")
}
if !strings.Contains(body, "organization") {
t.Errorf("the refusal should say what is allowed; got %s", body)
}
}
func TestChangingAQuotaNeedsTheCSRFToken(t *testing.T) {
s := oneTeam()
w := mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"scope": {"team"}, "id": {"team-1"}, "rpm": {"60"},
})
if w.Code != http.StatusBadRequest || len(s.put) != 0 {
t.Errorf("expected 400 and no write, got %d %+v", w.Code, s.put)
}
}
func TestSomebodyWhoIsNotAnAdminSeesNoQuotas(t *testing.T) {
if w := mirrorGet(t, quotasPage(t, oneTeam(), false), "/admin/quotas"); w.Code != http.StatusNotFound {
t.Errorf("expected 404 for a non-admin, got %d", w.Code)
}
}
func TestAFormOpensOnTheBranchThatWasClicked(t *testing.T) {
body := mirrorGet(t, quotasPage(t, oneTeam(), true),
"/admin/quotas?edit=team:team-1").Body.String()
// The tree is the interface: a quota belongs to a branch, so the form opens
// on that branch rather than asking for its id again. Ids are random — two
// organizations can both have a "platform" team — so typing one is a typo
// waiting to write a quota onto nothing and report success.
if !strings.Contains(body, `name="id" value="team-1"`) {
t.Errorf("the open form should carry the branch it belongs to; got %s", body)
}
if !strings.Contains(body, "Scania / platform") {
t.Error("the form should name the branch in full")
}
}
func TestOnlyTheClickedBranchOpens(t *testing.T) {
body := mirrorGet(t, quotasPage(t, oneTeam(), true),
"/admin/quotas?edit=team:team-1").Body.String()
// One form at a time. A disclosure on every row would put a form in the
// markup for each branch of a page whose job is to be read as a tree.
if n := strings.Count(body, `name="csrf"`); n != 1 {
t.Errorf("expected exactly one form, found %d", n)
}
if !strings.Contains(body, `href="/admin/quotas?edit=project:proj-1"`) {
t.Error("every other branch should still offer to open")
}
}
func TestABranchWithNoQuotaSaysSoOnItsLink(t *testing.T) {
body := mirrorGet(t, quotasPage(t, oneTeam(), true), "/admin/quotas").Body.String()
// "change" on a branch that has nothing set would suggest there is
// something to change.
if !strings.Contains(body, "set quota") {
t.Errorf("an unlimited branch should invite setting one; got %s", body)
}
}
func TestSavingClosesTheFormSoTheResultIsVisible(t *testing.T) {
s := oneTeam()
body := mirrorPost(t, quotasPage(t, s, true), "/admin/quotas", url.Values{
"csrf": {"tok"}, "scope": {"team"}, "id": {"team-1"}, "rpm": {"60"},
}).Body.String()
// Re-rendering the form you just submitted hides the row you changed behind
// it, and invites a second submission of the same thing.
if strings.Contains(body, `name="rpm"`) {
t.Error("the form should be closed after a save")
}
if !strings.Contains(body, "rpm 60") {
t.Errorf("the page should confirm what was written; got %s", body)
}
}