- antigravity 기반 CLI status adapter 구현 (gemini 대체) - milestone 파일명 통일 (接두어 제거) - agent-ops roadmap/current.md 갱신 - edge/node README 및 설정 문서 업데이트 - e2e smoke 스크립트 개선
499 lines
20 KiB
Go
499 lines
20 KiB
Go
package status
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestParseStatusOutput_CodexLimits(t *testing.T) {
|
|
rawOutput := "5h limit: [████████████████████] 98% left (resets 18:38)\n" +
|
|
"Weekly limit: [████░░░░░░░░░░░░░░░░] 22% left (resets 10:20 on 8 May)"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
|
|
if status.DailyLimit != "98%" {
|
|
t.Errorf("expected daily limit 98%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "18:38" {
|
|
t.Errorf("expected daily reset 18:38, got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "22%" {
|
|
t.Errorf("expected weekly limit 22%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "10:20 on 8 May" {
|
|
t.Errorf("expected weekly reset 10:20 on 8 May, got %q", status.WeeklyResetTime)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsage(t *testing.T) {
|
|
rawOutput := "Current session: [███░░░░░░░░░░░░░░░░░] 2% used\nResets 10am (Asia/Seoul)\n\n" +
|
|
"Current week (all models): [#░░░░░░░░░░░░░░░░░░░░] 0% used\nResets May 16, 6pm (Asia/Seoul)"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
|
|
if status.DailyLimit != "98%" {
|
|
t.Errorf("expected daily limit 98%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("expected daily reset 10am (Asia/Seoul), got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("expected weekly limit 100%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("expected weekly reset May 16, 6pm (Asia/Seoul), got %q", status.WeeklyResetTime)
|
|
}
|
|
if status.Metadata["daily_label"] != "Current session" {
|
|
t.Errorf("expected daily_label Current session, got %q", status.Metadata["daily_label"])
|
|
}
|
|
if status.Metadata["weekly_label"] != "Current week" {
|
|
t.Errorf("expected weekly_label Current week, got %q", status.Metadata["weekly_label"])
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeStatusPanel(t *testing.T) {
|
|
rawOutput := "ClaudeCode v2.1.144\n\n" +
|
|
"Version: 2.1.144\n" +
|
|
"SessionID: e26e764d-dd46-4c7e-9509-5a9b964ce993\n" +
|
|
"cwd: /config/workspace/iop\n" +
|
|
"Loginmethod: Claude Pro account\n" +
|
|
"Model: opus (claude-opus-4-7)\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.Metadata["status_kind"] != "claude" {
|
|
t.Fatalf("status_kind: got %q", status.Metadata["status_kind"])
|
|
}
|
|
if status.Metadata["claude_status_version"] != "2.1.144" {
|
|
t.Errorf("version: got %q", status.Metadata["claude_status_version"])
|
|
}
|
|
if status.Metadata["claude_status_login_method"] != "Claude Pro account" {
|
|
t.Errorf("login_method: got %q", status.Metadata["claude_status_login_method"])
|
|
}
|
|
if status.Metadata["claude_status_model"] != "opus (claude-opus-4-7)" {
|
|
t.Errorf("model: got %q", status.Metadata["claude_status_model"])
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageWithANSICursorSpacing(t *testing.T) {
|
|
rawOutput := "Current\x1b[1C session: [███░░░░░░░░░░░░░░░░░] 2%\x1b[1C used\nResets 10am (Asia/Seoul)\n\n" +
|
|
"Current week (all models): [#░░░░░░░░░░░░░░░░░░░░] 0% used\nResets May 16, 6pm (Asia/Seoul)"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
|
|
if status.DailyLimit != "98%" {
|
|
t.Errorf("expected daily limit 98%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("expected daily reset 10am (Asia/Seoul), got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("expected weekly limit 100%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("expected weekly reset, got %q", status.WeeklyResetTime)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageWithCarriageReturns(t *testing.T) {
|
|
rawOutput := "Current session: [███░░░░░░░░░░░░░░░░░] 2% used\rResets 10am (Asia/Seoul)\r\n\n" +
|
|
"Current week (all models): [#░░░░░░░░░░░░░░░░░░░░] 0% used\rResets May 16, 6pm (Asia/Seoul)\r"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
|
|
if status.DailyLimit != "98%" {
|
|
t.Errorf("expected daily limit 98%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("expected daily reset 10am (Asia/Seoul), got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("expected weekly limit 100%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("expected weekly reset May 16, 6pm (Asia/Seoul), got %q", status.WeeklyResetTime)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageFromRepaintedScreen(t *testing.T) {
|
|
// Mirrors the actual Claude /usage panel raw stream: an initial loading
|
|
// repaint ("Scanning local sessions...", "Esc to cancel") followed by a
|
|
// clear-screen sequence and the real data block. The parser must still
|
|
// extract Current session / Current week from the combined stream.
|
|
rawOutput := "\x1b[2J\x1b[H" +
|
|
" Usage\n" +
|
|
" Scanning local sessions...\n" +
|
|
" Refreshing...\n" +
|
|
" Esc to cancel\n" +
|
|
"\x1b[2J\x1b[H" +
|
|
" Settings Status Config Usage Stats\n\n" +
|
|
" Session\n\n" +
|
|
" Current session\n" +
|
|
" \x1b[48;5;102m\x1b[38;5;153m## \x1b[49m\x1b[39m 4% used\n" +
|
|
" Resets 10am (Asia/Seoul)\n\n" +
|
|
" Current week (all models)\n" +
|
|
" 0% used\n" +
|
|
" Resets May 16, 6pm (Asia/Seoul)\n\n" +
|
|
" Esc to cancel\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "96%" {
|
|
t.Errorf("expected daily limit 96%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("expected daily reset 10am (Asia/Seoul), got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("expected weekly limit 100%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("expected weekly reset May 16, 6pm (Asia/Seoul), got %q", status.WeeklyResetTime)
|
|
}
|
|
if status.Metadata["daily_label"] != "Current session" {
|
|
t.Errorf("expected daily_label Current session, got %q", status.Metadata["daily_label"])
|
|
}
|
|
if status.Metadata["weekly_label"] != "Current week" {
|
|
t.Errorf("expected weekly_label Current week, got %q", status.Metadata["weekly_label"])
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageCurrentRealState(t *testing.T) {
|
|
// Reproduces the exact failure state observed in the previous review run:
|
|
// the /usage panel is fully open with the trailing "Last 24h", "d to day · w
|
|
// to week", "Extra usage", "Esc to cancel" sections rendered, AND the raw
|
|
// stream interleaves the week's "% used / Resets" payload before the
|
|
// session's payload (this happens when the TUI renders the bars via cursor
|
|
// positioning rather than sequentially). The previous lazy-regex parser
|
|
// paired the session header with the week's reset time on this input —
|
|
// the new section-aware parser must keep them separated.
|
|
rawOutput := "" +
|
|
" Settings Status Config Usage Stats\n\n" +
|
|
" Session\n\n" +
|
|
" Current session\n" +
|
|
" Current week (all models)\n" +
|
|
" ############## 95% used\n" +
|
|
" Resets May 16, 6pm (Asia/Seoul)\n" +
|
|
" ### 7% used\n" +
|
|
" Resets 10am (Asia/Seoul)\n\n" +
|
|
" What's contributing to your limits usage?\n" +
|
|
" Last 24h · these are independent characteristics of your usage, not a breakdown\n" +
|
|
" Nothing over 10% in this period — try the other window.\n" +
|
|
" d to day · w to week\n\n" +
|
|
" Extra usage\n" +
|
|
" Extra usage not enabled · /extra-usage to enable\n\n" +
|
|
" Esc to cancel\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
// First payload after "Current session" header (before "Current week") is
|
|
// the week's data because of the cursor-interleaved render order. The
|
|
// section-aware parser must NOT capture it for the session — it must leave
|
|
// the session payload empty since the session block has no payload of its
|
|
// own before the week header.
|
|
if status.DailyResetTime == "May 16, 6pm (Asia/Seoul)" {
|
|
t.Fatalf("session reset time leaked from week section: %q", status.DailyResetTime)
|
|
}
|
|
// The week section, starting at its header, gets the next available
|
|
// "% used / Resets" pair, which is the week's own.
|
|
if status.WeeklyLimit != "5%" {
|
|
t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "5%")
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
|
|
}
|
|
if status.Metadata["weekly_label"] != "Current week" {
|
|
t.Fatalf("weekly_label: got %q want %q", status.Metadata["weekly_label"], "Current week")
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint(t *testing.T) {
|
|
// Reproduces the cursor-addressed repaint that caused the G08 review run-2
|
|
// failure: the panel writes a loading state ("Last 24h", "Esc to cancel"),
|
|
// then uses CSI cursor positioning to overwrite specific rows with the
|
|
// session/week payload. In the raw append stream the corrupted loading
|
|
// text remains interleaved with the payload runes ("No hing ov r",
|
|
// "21% of y ur usage..."), so a line-oriented cleanANSI parser cannot
|
|
// reassemble a coherent "Current session ... X% used\nResets Y". A
|
|
// terminal screen model must apply the cursor moves and erases to recover
|
|
// the actually-visible text.
|
|
//
|
|
// Sequence:
|
|
// 1. Clear screen, write garbage loading lines on rows 5..10.
|
|
// 2. CUP back to row 5 col 1, EL (erase line), write "Current session".
|
|
// 3. Same for row 6 with the bar + "4% used".
|
|
// 4. Same for row 7 with "Resets 10am (Asia/Seoul)".
|
|
// 5. Same for rows 9..11 with the week block.
|
|
const ESC = "\x1b"
|
|
// First write a "Current session" header and a misleading early payload
|
|
// ("21% of your usage Resets garbage time") that — if left intact in the
|
|
// stream — would be picked up by a section-aware regex as the session's
|
|
// payload. Then cursor back, erase the misleading lines, and write the
|
|
// real " 4% used / Resets 10am" payload at those same rows. The same
|
|
// pattern is applied for the week block.
|
|
raw := ESC + "[2J" + ESC + "[H" +
|
|
ESC + "[5;1H" + " Current session" +
|
|
ESC + "[6;1H" + " 21% of your usage in last 24h" +
|
|
ESC + "[7;1H" + " Resets garbage time" +
|
|
ESC + "[8;1H" + "" +
|
|
ESC + "[9;1H" + " Current week (all models)" +
|
|
ESC + "[10;1H" + " 47% of week things here" +
|
|
ESC + "[11;1H" + " Resets garbage week time" +
|
|
// Repaint rows 6,7 with real session payload.
|
|
ESC + "[6;1H" + ESC + "[2K" + " #### 4% used" +
|
|
ESC + "[7;1H" + ESC + "[2K" + " Resets 10am (Asia/Seoul)" +
|
|
// Repaint rows 10,11 with real week payload.
|
|
ESC + "[10;1H" + ESC + "[2K" + " 0% used" +
|
|
ESC + "[11;1H" + ESC + "[2K" + " Resets May 16, 6pm (Asia/Seoul)" +
|
|
// Trailing panel chrome that timeout tails would otherwise reveal.
|
|
ESC + "[13;1H" + " Last 24h" +
|
|
ESC + "[14;1H" + " d to day · w to week" +
|
|
ESC + "[15;1H" + " Extra usage" +
|
|
ESC + "[16;1H" + " Esc to cancel"
|
|
|
|
// Sanity check: the line-oriented cleanANSI alone must NOT be able to find
|
|
// a coherent payload — corrupt text leaks through and a section-aware
|
|
// regex on raw-clean ends up with empty or wrong fields. We assert that
|
|
// directly so the regression catches anyone reverting to a line-only
|
|
// parser.
|
|
lineOnly := &UsageStatus{}
|
|
lineOnly.parseClaudeUsage(cleanANSI(raw))
|
|
if lineOnly.DailyResetTime == "10am (Asia/Seoul)" && lineOnly.WeeklyResetTime == "May 16, 6pm (Asia/Seoul)" {
|
|
t.Fatalf("line-only parser must fail on cursor-repainted raw; got daily=%q weekly=%q", lineOnly.DailyResetTime, lineOnly.WeeklyResetTime)
|
|
}
|
|
|
|
status, err := ParseStatusOutput(raw)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "96%" {
|
|
t.Errorf("DailyLimit: got %q want 96%%", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("WeeklyLimit: got %q want 100%%", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
|
|
}
|
|
if status.Metadata["daily_label"] != "Current session" {
|
|
t.Errorf("daily_label: got %q want %q", status.Metadata["daily_label"], "Current session")
|
|
}
|
|
if status.Metadata["weekly_label"] != "Current week" {
|
|
t.Errorf("weekly_label: got %q want %q", status.Metadata["weekly_label"], "Current week")
|
|
}
|
|
}
|
|
|
|
func TestRenderVisibleScreenTreatsHangulAsWide(t *testing.T) {
|
|
const ESC = "\x1b"
|
|
|
|
got := RenderVisibleScreen("한글"+ESC+"[2DX", 1, 20)
|
|
want := "한X\n"
|
|
if got != want {
|
|
t.Fatalf("RenderVisibleScreen() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRenderVisibleScreenClearsWideContinuation(t *testing.T) {
|
|
const ESC = "\x1b"
|
|
|
|
got := RenderVisibleScreen("한글"+ESC+"[2D"+ESC+"[K", 1, 20)
|
|
want := "한\n"
|
|
if got != want {
|
|
t.Fatalf("RenderVisibleScreen() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_AntigravityStatsQuota(t *testing.T) {
|
|
rawOutput := "Auto (Antigravity) Stats For Nerds\n" +
|
|
"0% used (Limit resets in 24h)\n" +
|
|
"Usage limit: 200\n" +
|
|
"Usage limits span all sessions and reset daily.\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "100%" {
|
|
t.Errorf("DailyLimit: got %q want %q", status.DailyLimit, "100%")
|
|
}
|
|
if status.DailyResetTime != "24h" {
|
|
t.Errorf("DailyResetTime: got %q want %q", status.DailyResetTime, "24h")
|
|
}
|
|
if status.WeeklyLimit != "" {
|
|
t.Errorf("WeeklyLimit should be empty, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.Metadata["daily_label"] != "Daily quota" {
|
|
t.Errorf("daily_label: got %q want %q", status.Metadata["daily_label"], "Daily quota")
|
|
}
|
|
if status.Metadata["used_percent"] != "0%" {
|
|
t.Errorf("used_percent: got %q want %q", status.Metadata["used_percent"], "0%")
|
|
}
|
|
if status.Metadata["usage_limit"] != "200" {
|
|
t.Errorf("usage_limit: got %q want %q", status.Metadata["usage_limit"], "200")
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_AntigravityLimitReached(t *testing.T) {
|
|
rawOutput := "Auto (Antigravity) Stats For Nerds\n" +
|
|
"Limit reached, resets in 5h\n" +
|
|
"Usage limit: 200\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "0%" {
|
|
t.Errorf("DailyLimit: got %q want %q", status.DailyLimit, "0%")
|
|
}
|
|
if status.DailyResetTime != "5h" {
|
|
t.Errorf("DailyResetTime: got %q want %q", status.DailyResetTime, "5h")
|
|
}
|
|
if status.Metadata["used_percent"] != "100%" {
|
|
t.Errorf("used_percent: got %q want %q", status.Metadata["used_percent"], "100%")
|
|
}
|
|
if status.Metadata["usage_limit"] != "200" {
|
|
t.Errorf("usage_limit: got %q want %q", status.Metadata["usage_limit"], "200")
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_AntigravityScreenReaderQuotaWithoutReset(t *testing.T) {
|
|
rawOutput := "workspace (/directory)\n" +
|
|
"~/workspace/iop branch\n" +
|
|
"main sandbox\n" +
|
|
"no sandbox /model\n" +
|
|
"Auto (Antigravity) quota\n" +
|
|
"0% used\n" +
|
|
"No API calls have been made in this session.\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "100%" {
|
|
t.Errorf("DailyLimit: got %q want %q", status.DailyLimit, "100%")
|
|
}
|
|
if status.DailyResetTime != "" {
|
|
t.Errorf("DailyResetTime should be empty, got %q", status.DailyResetTime)
|
|
}
|
|
if status.Metadata["daily_label"] != "Daily quota" {
|
|
t.Errorf("daily_label: got %q want %q", status.Metadata["daily_label"], "Daily quota")
|
|
}
|
|
if status.Metadata["used_percent"] != "0%" {
|
|
t.Errorf("used_percent: got %q want %q", status.Metadata["used_percent"], "0%")
|
|
}
|
|
if status.Metadata["usage_limit"] != "" {
|
|
t.Errorf("usage_limit should be empty, got %q", status.Metadata["usage_limit"])
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_AntigravityModelUsage(t *testing.T) {
|
|
rawOutput := "Select Model\n" +
|
|
"(checked) 1. Auto (Antigravity)\n" +
|
|
"Model usage\n" +
|
|
"Flash ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 0% Resets: 2:15 PM (24h)\n" +
|
|
"Flash Lite ▬ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 1% Resets: 12:48 PM (22h 34m)\n" +
|
|
"\n" +
|
|
"Pro ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 2% Resets: 2:15 PM (24h)\n" +
|
|
"agy-3.1-flash-lite ▬ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 3% Resets: 12:48 PM\n" +
|
|
" (22h 34m)\n" +
|
|
"(Press Esc to close)\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.Metadata["model_usage_count"] != "4" {
|
|
t.Fatalf("model_usage_count: got %q want 4", status.Metadata["model_usage_count"])
|
|
}
|
|
tests := []struct {
|
|
idx string
|
|
name string
|
|
used string
|
|
reset string
|
|
}{
|
|
{"0", "Flash", "0%", "2:15 PM (24h)"},
|
|
{"1", "Flash Lite", "1%", "12:48 PM (22h 34m)"},
|
|
{"2", "Pro", "2%", "2:15 PM (24h)"},
|
|
{"3", "agy-3.1-flash-lite", "3%", "12:48 PM (22h 34m)"},
|
|
}
|
|
for _, tt := range tests {
|
|
prefix := "model_usage_" + tt.idx
|
|
if status.Metadata[prefix+"_name"] != tt.name {
|
|
t.Errorf("%s_name: got %q want %q", prefix, status.Metadata[prefix+"_name"], tt.name)
|
|
}
|
|
if status.Metadata[prefix+"_used_percent"] != tt.used {
|
|
t.Errorf("%s_used_percent: got %q want %q", prefix, status.Metadata[prefix+"_used_percent"], tt.used)
|
|
}
|
|
if status.Metadata[prefix+"_reset"] != tt.reset {
|
|
t.Errorf("%s_reset: got %q want %q", prefix, status.Metadata[prefix+"_reset"], tt.reset)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_AntigravityNoAPICallsLeavesRawOnly(t *testing.T) {
|
|
rawOutput := "Auto (Antigravity) Stats For Nerds\n" +
|
|
"No API calls have been made in this session.\n"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
if status.DailyLimit != "" {
|
|
t.Errorf("DailyLimit should be empty, got %q", status.DailyLimit)
|
|
}
|
|
if status.WeeklyLimit != "" {
|
|
t.Errorf("WeeklyLimit should be empty, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.RawOutput == "" {
|
|
t.Error("RawOutput should not be empty")
|
|
}
|
|
}
|
|
|
|
func TestParseStatusOutput_ClaudeUsageWithTerminalControls(t *testing.T) {
|
|
rawOutput := "Current \x1b[1C \x1b[1Csession:\x1b[10C [#\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\xb8\x1b[5C\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80] \x1b[1C2%\x1b[1C used\rResets \x1b[1C10am (Asia/Seoul)\r\n\n" +
|
|
"Current week (all models): [#\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\xb8\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80\xe2\x96\x80] 0% used\rResets May 16, 6pm (Asia/Seoul)"
|
|
|
|
status, err := ParseStatusOutput(rawOutput)
|
|
if err != nil {
|
|
t.Fatalf("ParseStatusOutput failed: %v", err)
|
|
}
|
|
|
|
if status.DailyLimit != "98%" {
|
|
t.Errorf("expected daily limit 98%%, got %q", status.DailyLimit)
|
|
}
|
|
if status.DailyResetTime != "10am (Asia/Seoul)" {
|
|
t.Errorf("expected daily reset 10am (Asia/Seoul), got %q", status.DailyResetTime)
|
|
}
|
|
if status.WeeklyLimit != "100%" {
|
|
t.Errorf("expected weekly limit 100%%, got %q", status.WeeklyLimit)
|
|
}
|
|
if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
|
|
t.Errorf("expected weekly reset May 16, 6pm (Asia/Seoul), got %q", status.WeeklyResetTime)
|
|
}
|
|
if status.Metadata["daily_label"] != "Current session" {
|
|
t.Errorf("expected daily_label Current session, got %q", status.Metadata["daily_label"])
|
|
}
|
|
if status.Metadata["weekly_label"] != "Current week" {
|
|
t.Errorf("expected weekly_label Current week, got %q", status.Metadata["weekly_label"])
|
|
}
|
|
}
|