mesh/delivery merged: source.main moved to novox/mesh-catalog@6d000647de2d
mesh/check being carried to cff1005422c1 on 6d000647de2d | from f9dcbc295723
The one bundle that did everything becomes the manager bundle, which runs long and answers the seat's ten verbs and the three acting tools, and a tools bundle of its own that answers the four reading tools and writes nothing, so they answer while the manager fails to load or stops. Both import the module's one package for the layouts of the kept data and the log entries, the key form, the entries' names and the settings with their validity; the settings file and the account with its directory stay as they are. Builds: design 39 parts §1, §2, §3, §4, §5, §6, §7, §8
133 lines
5.9 KiB
Go
133 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
. "claude-licence-manager/internal/licences"
|
|
)
|
|
|
|
// vendorUsage is the vendor's usage answer as it reads: three windows, each with its reset time.
|
|
var vendorUsage = map[string]any{
|
|
"five_hour": map[string]any{"utilization": 41.0, "resets_at": "2026-11-11T23:00:00Z"},
|
|
"seven_day": map[string]any{"utilization": 97.0, "resets_at": "2026-11-14T09:00:00Z"},
|
|
"seven_day_sonnet": map[string]any{"utilization": 12.0, "resets_at": "2026-11-15T09:00:00Z"},
|
|
}
|
|
|
|
// design 39 bus-and-switch §24/AC1: a vendor answer is flattened with its three reset times, kept beside the licence
|
|
// with when it was read, and said as usage.read with those fields.
|
|
func TestAReadingIsFlattenedWithThreeResets(t *testing.T) {
|
|
mm := newMesh(t)
|
|
ctx := context.Background()
|
|
_, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-a", true, t0)})
|
|
mm.vendor.usage = vendorUsage
|
|
mm.events = nil
|
|
if err := mm.m.ReadUsage(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
l, _ := mm.store.Licence(ctx, licence1)
|
|
u := l.Usage
|
|
if u == nil || *u.SessionPct != 41 || u.SessionResetsAt != "2026-11-11T23:00:00Z" || *u.WeeklyPct != 97 ||
|
|
u.WeeklyResetsAt != "2026-11-14T09:00:00Z" || *u.SonnetPct != 12 || u.SonnetResetsAt != "2026-11-15T09:00:00Z" ||
|
|
u.ReadAt != t0.UTC().Format(time.RFC3339Nano) || u.ReadError != "" {
|
|
t.Fatalf("kept %+v", u)
|
|
}
|
|
said := strings.Join(mm.events, "\n")
|
|
for _, want := range []string{`"weeklyResetsAt":"2026-11-14T09:00:00Z"`, `"sonnetResetsAt":"2026-11-15T09:00:00Z"`,
|
|
`"sessionResetsAt":"2026-11-11T23:00:00Z"`} {
|
|
if !strings.Contains(said, "usage.read ") || !strings.Contains(said, want) {
|
|
t.Fatalf("said %s, lacking %s", said, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// design 39 bus-and-switch §24/AC2: a failed answer keeps the last percentages and their time, writes the vendor's
|
|
// status and reason, and appends no reading.
|
|
func TestAFailedReadingKeepsThePercentages(t *testing.T) {
|
|
mm := newMesh(t)
|
|
ctx := context.Background()
|
|
_, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-a", true, t0)})
|
|
mm.vendor.usage = vendorUsage
|
|
_ = mm.m.ReadUsage(ctx)
|
|
mm.now = t0.Add(5 * time.Minute)
|
|
mm.vendor.usageErr = errors.New(`the usage endpoint answered 429: {"error":"rate_limited"}`)
|
|
if err := mm.m.ReadUsage(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
l, _ := mm.store.Licence(ctx, licence1)
|
|
u := l.Usage
|
|
if u == nil || *u.WeeklyPct != 97 || u.ReadAt != t0.UTC().Format(time.RFC3339Nano) ||
|
|
u.ReadError != `the usage endpoint answered 429: {"error":"rate_limited"}` {
|
|
t.Fatalf("kept %+v", u)
|
|
}
|
|
if n := len(mm.usage.entries); n != 1 {
|
|
t.Fatalf("a failed reading appended: %d entries", n)
|
|
}
|
|
// §16, §31: a grant the vault does not answer is a failed reading, written and gone past, never the end of the round.
|
|
mm.vendor.usageErr = nil
|
|
mm.grants.mu.Lock()
|
|
delete(mm.grants.held, SecretName(KeyFor(licence1), PartGrant))
|
|
mm.grants.mu.Unlock()
|
|
if err := mm.m.ReadUsage(ctx); err != nil {
|
|
t.Fatalf("a grant the vault did not answer ended the round: %v", err)
|
|
}
|
|
l, _ = mm.store.Licence(ctx, licence1)
|
|
if !strings.Contains(l.Usage.ReadError, "cannot be read from the vault") || *l.Usage.WeeklyPct != 97 {
|
|
t.Fatalf("after the vault's refusal the reading is %+v", l.Usage)
|
|
}
|
|
// §4, §16: a reading is taken under the licence's lease; with another run holding it, nothing is read or written.
|
|
if ok, _ := mm.store.Lease(ctx, "licence."+KeyFor(licence1), "another run", time.Minute); !ok {
|
|
t.Fatal("the lease was not taken")
|
|
}
|
|
reads, err := mm.m.ReadUsageOf(ctx, licence1, true)
|
|
if err != nil || len(reads) != 1 || reads[0].Error != "another run holds its lease" {
|
|
t.Fatalf("a reading under another's lease answered %+v, %v", reads, err)
|
|
}
|
|
if again, _ := mm.store.Licence(ctx, licence1); again.Usage.ReadError != l.Usage.ReadError {
|
|
t.Fatalf("a reading under another's lease wrote %+v", again.Usage)
|
|
}
|
|
}
|
|
|
|
// design 39 bus-and-switch §25/AC1, §48, C4: a table of readings, ages and healths and the room each gives.
|
|
func TestTheRoomOfEachLicence(t *testing.T) {
|
|
now := t0
|
|
pct := func(v float64) *float64 { return &v }
|
|
read := func(session, weekly float64, ago time.Duration) *UsageReading {
|
|
return &UsageReading{SessionPct: pct(session), WeeklyPct: pct(weekly), ReadAt: now.Add(-ago).Format(time.RFC3339Nano)}
|
|
}
|
|
for _, c := range []struct {
|
|
name string
|
|
l Licence
|
|
room float64
|
|
known bool
|
|
}{
|
|
{"the weekly window counts", Licence{Kind: "subscription", Usage: read(41, 97, time.Minute)}, 3, true},
|
|
{"the session takes no part (C4)", Licence{Kind: "subscription", Usage: read(80, 20, time.Minute)}, 80, true},
|
|
{"no session window, weekly 33 (C4)", Licence{Kind: "subscription", Usage: &UsageReading{WeeklyPct: pct(33),
|
|
ReadAt: now.Format(time.RFC3339Nano)}}, 67, true},
|
|
{"read at the stale bound", Licence{Kind: "subscription", Usage: read(10, 10, 15*time.Minute)}, 90, true},
|
|
{"read before the stale bound", Licence{Kind: "subscription", Usage: read(10, 10, 16*time.Minute)}, 0, false},
|
|
{"a failure since keeps the room", Licence{Kind: "subscription", Usage: func() *UsageReading {
|
|
u := read(10, 30, 5*time.Minute)
|
|
u.ReadError = "the usage endpoint answered 500: x"
|
|
return u
|
|
}()}, 70, true},
|
|
{"never read", Licence{Kind: "subscription"}, 0, false},
|
|
{"no weekly window", Licence{Kind: "subscription", Usage: &UsageReading{SessionPct: pct(10),
|
|
ReadAt: now.Format(time.RFC3339Nano)}}, 0, false},
|
|
{"refresh failing", Licence{Kind: "subscription", Failures: 3, Usage: read(10, 10, time.Minute)}, 0, false},
|
|
{"refresh failed twice", Licence{Kind: "subscription", Failures: 2, Usage: read(10, 10, time.Minute)}, 90, true},
|
|
{"refresh token expired", Licence{Kind: "subscription", RefreshExpiresAt: now.UnixMilli(),
|
|
Usage: read(10, 10, time.Minute)}, 0, false},
|
|
{"an API key", Licence{Kind: "api-key", Usage: read(0, 0, 0)}, 0, false},
|
|
} {
|
|
room, known := Room(c.l, now, Defaults)
|
|
if room != c.room || known != c.known {
|
|
t.Errorf("%s: room %v known %v, want %v %v", c.name, room, known, c.room, c.known)
|
|
}
|
|
}
|
|
}
|