Files
claude-code/dashboard/internal/api/claude_handlers_test.go
OpenCode Test ae958528a6 Add Claude integration to dashboard
Add comprehensive Claude Code monitoring and realtime streaming to the K8s dashboard.
Includes API endpoints for health, stats, summary, inventory, and live event streaming.
Frontend provides overview, usage, inventory, debug, and live feed views.
2026-01-03 10:54:48 -08:00

111 lines
2.6 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/will/k8s-agent-dashboard/internal/claude"
)
type fakeLoader struct{}
func (f fakeLoader) ClaudeDir() string { return "/tmp/claude" }
func (f fakeLoader) LoadStatsCache() (*claude.StatsCache, error) {
return &claude.StatsCache{TotalSessions: 3}, nil
}
func (f fakeLoader) ListDir(name string) ([]claude.DirEntry, error) { return nil, nil }
func (f fakeLoader) FileMeta(relPath string) (claude.FileMeta, error) { return claude.FileMeta{}, nil }
func (f fakeLoader) PathExists(relPath string) bool { return true }
func TestGetClaudeStats(t *testing.T) {
r := chi.NewRouter()
r.Get("/api/claude/stats", GetClaudeStats(fakeLoader{}))
req := httptest.NewRequest(http.MethodGet, "/api/claude/stats", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
}
func TestGetClaudeSummary_IncludesDerivedCostSignals(t *testing.T) {
r := chi.NewRouter()
r.Get("/api/claude/summary", GetClaudeSummary(fakeSummaryLoader{}))
req := httptest.NewRequest(http.MethodGet, "/api/claude/summary", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
// Smoke-check that derived fields exist.
body := w.Body.String()
for _, want := range []string{"cacheHitRatioEstimate", "topModelByOutputTokens"} {
if !jsonContainsKey(body, want) {
t.Fatalf("expected response to include key %s, body=%s", want, body)
}
}
}
type fakeSummaryLoader struct{ fakeLoader }
func (f fakeSummaryLoader) LoadStatsCache() (*claude.StatsCache, error) {
return &claude.StatsCache{
TotalSessions: 3,
TotalMessages: 10,
ModelUsage: map[string]claude.ModelUsage{
"claude-3-5-sonnet": {
InputTokens: 100,
OutputTokens: 250,
CacheReadInputTokens: 50,
CacheCreationInputTokens: 25,
},
"claude-3-5-haiku": {
InputTokens: 80,
OutputTokens: 300,
CacheReadInputTokens: 20,
},
},
}, nil
}
func jsonContainsKey(body, key string) bool {
var m map[string]any
if err := json.Unmarshal([]byte(body), &m); err != nil {
return false
}
return mapContainsKey(m, key)
}
func mapContainsKey(v any, key string) bool {
switch vv := v.(type) {
case map[string]any:
if _, ok := vv[key]; ok {
return true
}
for _, child := range vv {
if mapContainsKey(child, key) {
return true
}
}
case []any:
for _, child := range vv {
if mapContainsKey(child, key) {
return true
}
}
}
return false
}