api: stats partial-update payload + ConfigUpdate.Slot + CommandRun.RequiresAdminCreds
Reshape RepoStatsPayload into pointer-field partial-update form matching store.HostRepoStats semantics; add Slot discriminator to ConfigUpdatePayload for admin vs repo credential routing; add RequiresAdminCreds flag to CommandRunPayload for prune/unlock jobs that need delete authority.
This commit is contained in:
@@ -138,6 +138,85 @@ func TestJobProgressShapeStable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoStatsPayloadRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Nil pointer fields must be omitted from JSON output.
|
||||
empty := RepoStatsPayload{}
|
||||
raw, err := json.Marshal(empty)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal empty: %v", err)
|
||||
}
|
||||
if string(raw) != "{}" {
|
||||
t.Errorf("empty payload should marshal to {}, got %s", raw)
|
||||
}
|
||||
|
||||
// Populated fields must survive a round trip.
|
||||
total := int64(123456)
|
||||
rawSize := int64(200000)
|
||||
files := int64(42)
|
||||
snaps := int64(7)
|
||||
lockPresent := true
|
||||
now := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
pruneAt := time.Date(2026, 1, 3, 0, 0, 0, 0, time.UTC)
|
||||
freed := int64(8192)
|
||||
|
||||
p := RepoStatsPayload{
|
||||
TotalSizeBytes: &total,
|
||||
RawSizeBytes: &rawSize,
|
||||
UniqueFiles: &files,
|
||||
SnapshotCount: &snaps,
|
||||
LastCheckAt: &now,
|
||||
LastCheckStatus: "ok",
|
||||
LockPresent: &lockPresent,
|
||||
LastPruneAt: &pruneAt,
|
||||
LastPruneFreedBytes: &freed,
|
||||
}
|
||||
raw2, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal full: %v", err)
|
||||
}
|
||||
var got RepoStatsPayload
|
||||
if err := json.Unmarshal(raw2, &got); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if got.TotalSizeBytes == nil || *got.TotalSizeBytes != total {
|
||||
t.Errorf("TotalSizeBytes: got %v, want %d", got.TotalSizeBytes, total)
|
||||
}
|
||||
if got.RawSizeBytes == nil || *got.RawSizeBytes != rawSize {
|
||||
t.Errorf("RawSizeBytes: got %v, want %d", got.RawSizeBytes, rawSize)
|
||||
}
|
||||
if got.UniqueFiles == nil || *got.UniqueFiles != files {
|
||||
t.Errorf("UniqueFiles: got %v, want %d", got.UniqueFiles, files)
|
||||
}
|
||||
if got.SnapshotCount == nil || *got.SnapshotCount != snaps {
|
||||
t.Errorf("SnapshotCount: got %v, want %d", got.SnapshotCount, snaps)
|
||||
}
|
||||
if got.LastCheckAt == nil || !got.LastCheckAt.Equal(now) {
|
||||
t.Errorf("LastCheckAt: got %v, want %v", got.LastCheckAt, now)
|
||||
}
|
||||
if got.LastCheckStatus != "ok" {
|
||||
t.Errorf("LastCheckStatus: got %q, want %q", got.LastCheckStatus, "ok")
|
||||
}
|
||||
if got.LockPresent == nil || *got.LockPresent != lockPresent {
|
||||
t.Errorf("LockPresent: got %v, want %v", got.LockPresent, lockPresent)
|
||||
}
|
||||
if got.LastPruneAt == nil || !got.LastPruneAt.Equal(pruneAt) {
|
||||
t.Errorf("LastPruneAt: got %v, want %v", got.LastPruneAt, pruneAt)
|
||||
}
|
||||
if got.LastPruneFreedBytes == nil || *got.LastPruneFreedBytes != freed {
|
||||
t.Errorf("LastPruneFreedBytes: got %v, want %d", got.LastPruneFreedBytes, freed)
|
||||
}
|
||||
|
||||
// Partial update: only set LockPresent.
|
||||
lockFalse := false
|
||||
partial := RepoStatsPayload{LockPresent: &lockFalse}
|
||||
rawPartial, _ := json.Marshal(partial)
|
||||
if string(rawPartial) != `{"lock_present":false}` {
|
||||
t.Errorf("partial marshal: got %s, want {\"lock_present\":false}", rawPartial)
|
||||
}
|
||||
}
|
||||
|
||||
// touch time so the import is used by other tests in this file when
|
||||
// they grow over time.
|
||||
var _ = time.Now
|
||||
|
||||
Reference in New Issue
Block a user