iop/packages/go/agentprovider/cli/status/quota_test.go
toki 3c48879c44 feat(agent-runtime): 독립 Agent CLI 런타임 기반을 구현한다
독립 호스트에서 안전한 작업 실행과 복구를 제공하기 위해 런타임 설정, 정책, 상태 저장소, 워크스페이스 격리 및 AgentTask 오케스트레이션을 확장한다.
2026-07-29 13:12:21 +09:00

239 lines
8.2 KiB
Go

package status
import (
"encoding/json"
"errors"
"reflect"
"testing"
"time"
)
var quotaCheckedAt = time.Date(2026, 7, 25, 8, 0, 0, 0, time.UTC)
func TestNormalizeQuotaSnapshotExhaustedWinsMissing(t *testing.T) {
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"overall", "model:missing"}, quotaCheckedAt, &UsageStatus{DailyLimit: "0%"}, nil)
if got := snapshot.Targets[0].Status; got != "exhausted" {
t.Fatalf("status = %q, want exhausted", got)
}
if snapshot.RequiredCaps[1].Status != "unknown" {
t.Fatalf("missing model status = %q, want unknown", snapshot.RequiredCaps[1].Status)
}
}
func TestNormalizeQuotaSnapshotAvailableWhenAllPositive(t *testing.T) {
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"overall", "model:Flash"}, quotaCheckedAt, &UsageStatus{
DailyLimit: "70%",
Metadata: map[string]string{
"model_usage_count": "1",
"model_usage_0_name": "flash",
"model_usage_0_used_percent": "20%",
},
}, nil)
if got := snapshot.Targets[0].Status; got != "available" {
t.Fatalf("status = %q, want available", got)
}
for _, cap := range snapshot.RequiredCaps {
if cap.Status != "available" || cap.RemainingPercent == nil || *cap.RemainingPercent <= 0 {
t.Fatalf("cap = %#v, want positive available evidence", cap)
}
}
}
func TestNormalizeQuotaSnapshotUnknownForMissingConflictAndParseFailure(t *testing.T) {
for name, tc := range map[string]struct {
requiredCaps []string
usage *UsageStatus
}{
"missing": {
requiredCaps: []string{"overall", "model:missing"},
usage: &UsageStatus{DailyLimit: "50%"},
},
"conflict": {
requiredCaps: []string{"model:flash"},
usage: &UsageStatus{Metadata: map[string]string{
"model_usage_count": "2",
"model_usage_0_name": "Flash",
"model_usage_0_used_percent": "10%",
"model_usage_1_name": "flash",
"model_usage_1_used_percent": "20%",
}},
},
"parse": {
requiredCaps: []string{"overall"},
usage: &UsageStatus{DailyLimit: "not-a-percent"},
},
} {
t.Run(name, func(t *testing.T) {
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", tc.requiredCaps, quotaCheckedAt, tc.usage, nil)
if got := snapshot.Targets[0].Status; got != "unknown" {
t.Fatalf("status = %q, want unknown", got)
}
})
}
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"overall"}, quotaCheckedAt, nil, errors.New("provider output must not escape"))
if got := snapshot.Targets[0].Status; got != "unknown" {
t.Fatalf("checker error status = %q, want unknown", got)
}
if len(snapshot.ReasonCodes) != 1 || snapshot.ReasonCodes[0] != "checker_error" {
t.Fatalf("reason codes = %#v", snapshot.ReasonCodes)
}
}
func TestNormalizeQuotaSnapshotUnknownForNonFinitePercent(t *testing.T) {
for _, value := range []string{"NaN%", "Inf%", "-Inf%"} {
t.Run("overall_"+value, func(t *testing.T) {
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"overall"}, quotaCheckedAt, &UsageStatus{DailyLimit: value}, nil)
if got := snapshot.Targets[0].Status; got != "unknown" {
t.Fatalf("status = %q, want unknown", got)
}
if _, err := json.Marshal(snapshot); err != nil {
t.Fatalf("snapshot must be JSON encodable: %v", err)
}
})
t.Run("model_"+value, func(t *testing.T) {
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"model:flash"}, quotaCheckedAt, &UsageStatus{Metadata: map[string]string{
"model_usage_count": "1",
"model_usage_0_name": "flash",
"model_usage_0_used_percent": value,
}}, nil)
if got := snapshot.Targets[0].Status; got != "unknown" {
t.Fatalf("status = %q, want unknown", got)
}
if _, err := json.Marshal(snapshot); err != nil {
t.Fatalf("snapshot must be JSON encodable: %v", err)
}
})
}
}
func TestNormalizeQuotaSnapshotUsesAntigravityModelMetadata(t *testing.T) {
usage, err := ParseStatusOutput("Auto (Antigravity) quota\n0% used (Limit resets in 24h)\nModel usage\nFlash ▬▬▬ 2% Resets: tomorrow\n(Press Esc to close)\n")
if err != nil {
t.Fatalf("parse status: %v", err)
}
snapshot := NormalizeQuotaSnapshot("agy", "Gemini", []string{"overall", "model:FLASH"}, quotaCheckedAt, usage, nil)
if got := snapshot.Targets[0].Status; got != "available" {
t.Fatalf("status = %q, want available", got)
}
}
func TestNormalizeQuotaSnapshotNotApplicable(t *testing.T) {
snapshot := NormalizeQuotaSnapshot(
"provider",
"profile",
nil,
quotaCheckedAt,
nil,
errors.New("diagnostic must not escape"),
)
if got := snapshot.Targets[0].Status; got != "not_applicable" {
t.Fatalf("status = %q, want not_applicable", got)
}
if len(snapshot.RequiredCaps) != 0 {
t.Fatalf("required caps = %#v, want empty", snapshot.RequiredCaps)
}
if !reflect.DeepEqual(snapshot.ReasonCodes, []string{"quota_not_applicable"}) {
t.Fatalf("reason codes = %#v", snapshot.ReasonCodes)
}
if err := ValidateQuotaSnapshot(snapshot); err != nil {
t.Fatalf("ValidateQuotaSnapshot: %v", err)
}
encoded, err := json.Marshal(snapshot)
if err != nil {
t.Fatalf("Marshal: %v", err)
}
if string(encoded) == "" || containsAny(string(encoded), "diagnostic", "escape") {
t.Fatalf("snapshot retained checker diagnostics: %s", encoded)
}
}
func TestValidateQuotaSnapshotRejectsTampering(t *testing.T) {
base := NormalizeQuotaSnapshot(
"provider",
"profile",
[]string{"overall", "model:flash"},
quotaCheckedAt,
&UsageStatus{
DailyLimit: "80%",
Metadata: map[string]string{
"model_usage_count": "1",
"model_usage_0_name": "flash",
"model_usage_0_used_percent": "25%",
},
},
nil,
)
if err := ValidateQuotaSnapshot(base); err != nil {
t.Fatalf("base snapshot is invalid: %v", err)
}
tests := []struct {
name string
mutate func(*QuotaSnapshot)
}{
{"schema", func(snapshot *QuotaSnapshot) { snapshot.SchemaVersion = "2.0" }},
{"source", func(snapshot *QuotaSnapshot) { snapshot.Source = "other" }},
{"checked time", func(snapshot *QuotaSnapshot) {
snapshot.CheckedAt = quotaCheckedAt.Add(time.Second).Format(time.RFC3339Nano)
}},
{"adapter", func(snapshot *QuotaSnapshot) { snapshot.Targets[0].Adapter = "other" }},
{"target", func(snapshot *QuotaSnapshot) { snapshot.Targets[0].Target = "other" }},
{"target status", func(snapshot *QuotaSnapshot) { snapshot.Targets[0].Status = "exhausted" }},
{"cap name", func(snapshot *QuotaSnapshot) { snapshot.RequiredCaps[0].Name = "other" }},
{"cap status", func(snapshot *QuotaSnapshot) { snapshot.RequiredCaps[0].Status = "unknown" }},
{"cap remaining", func(snapshot *QuotaSnapshot) {
remaining := 79.0
snapshot.RequiredCaps[0].RemainingPercent = &remaining
}},
{"allowlisted reason", func(snapshot *QuotaSnapshot) {
snapshot.ReasonCodes = []string{"cap_evidence_unknown"}
}},
{"unsafe reason", func(snapshot *QuotaSnapshot) {
snapshot.ReasonCodes = []string{"token=provider-secret"}
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
snapshot := cloneQuotaSnapshot(base)
test.mutate(&snapshot)
if err := ValidateQuotaSnapshot(snapshot); err == nil {
t.Fatalf("ValidateQuotaSnapshot accepted tampered snapshot: %#v", snapshot)
}
})
}
reordered := cloneQuotaSnapshot(base)
reordered.RequiredCaps[0], reordered.RequiredCaps[1] =
reordered.RequiredCaps[1], reordered.RequiredCaps[0]
if err := ValidateQuotaSnapshot(reordered); err != nil {
t.Fatalf("normalized cap order should not affect identity: %v", err)
}
}
func cloneQuotaSnapshot(snapshot QuotaSnapshot) QuotaSnapshot {
out := snapshot
out.Targets = append([]QuotaTargetView(nil), snapshot.Targets...)
out.RequiredCaps = make([]QuotaCapView, len(snapshot.RequiredCaps))
for index, cap := range snapshot.RequiredCaps {
out.RequiredCaps[index] = cap
if cap.RemainingPercent != nil {
remaining := *cap.RemainingPercent
out.RequiredCaps[index].RemainingPercent = &remaining
}
}
out.ReasonCodes = append([]string(nil), snapshot.ReasonCodes...)
return out
}
func containsAny(value string, candidates ...string) bool {
for _, candidate := range candidates {
if len(candidate) > 0 && len(value) >= len(candidate) {
for index := 0; index+len(candidate) <= len(value); index++ {
if value[index:index+len(candidate)] == candidate {
return true
}
}
}
}
return false
}