package handlers import ( "bytes" "encoding/json" "net/http/httptest" "sync" "testing" "github.com/go-chi/chi/v5" "github.com/orchard9/rdev/internal/adapter/kubernetes" ) // setupBenchHandler creates a handler for benchmarking. func setupBenchHandler() (*ProjectsHandler, chi.Router) { repo := kubernetes.NewProjectRepository("test-namespace") exec := kubernetes.NewExecutor("test-namespace") h := NewProjectsHandler(repo, exec) router := chi.NewRouter() h.Mount(router) return h, router } // BenchmarkRunClaude benchmarks the RunClaude endpoint. // This measures the handler overhead excluding actual command execution. func BenchmarkRunClaude(b *testing.B) { _, router := setupBenchHandler() body := ClaudeRequest{Prompt: "test prompt"} bodyBytes, _ := json.Marshal(body) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { req := httptest.NewRequest("POST", "/projects/pantheon/claude", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkRunShell benchmarks the RunShell endpoint. func BenchmarkRunShell(b *testing.B) { _, router := setupBenchHandler() body := ShellRequest{Command: "ls -la"} bodyBytes, _ := json.Marshal(body) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { req := httptest.NewRequest("POST", "/projects/pantheon/shell", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkRunGit benchmarks the RunGit endpoint. func BenchmarkRunGit(b *testing.B) { _, router := setupBenchHandler() body := GitRequest{Args: []string{"status"}} bodyBytes, _ := json.Marshal(body) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { req := httptest.NewRequest("POST", "/projects/pantheon/git", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkList benchmarks the List endpoint. func BenchmarkList(b *testing.B) { _, router := setupBenchHandler() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { req := httptest.NewRequest("GET", "/projects", nil) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkGet benchmarks the Get endpoint. func BenchmarkGet(b *testing.B) { _, router := setupBenchHandler() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { req := httptest.NewRequest("GET", "/projects/pantheon", nil) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkSSEStreaming benchmarks the SSE event throughput. // This measures how fast events can be written through the stream manager. func BenchmarkSSEStreaming(b *testing.B) { h, _ := setupBenchHandler() // Subscribe to a stream streamID := "bench-stream" events := h.streams.Subscribe(streamID) defer h.streams.Unsubscribe(streamID, events) // Drain events in background done := make(chan struct{}) go func() { for range events { } close(done) }() eventData := map[string]any{ "line": "benchmark output line", "stream": "stdout", } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { h.streams.Send(streamID, "output", eventData) } // Cleanup h.streams.Close(streamID) <-done } // BenchmarkSSEParallelStreaming benchmarks concurrent SSE event throughput. func BenchmarkSSEParallelStreaming(b *testing.B) { h, _ := setupBenchHandler() streamID := "bench-parallel-stream" events := h.streams.Subscribe(streamID) defer h.streams.Unsubscribe(streamID, events) // Drain events in background done := make(chan struct{}) go func() { for range events { } close(done) }() eventData := map[string]any{ "line": "benchmark output line", "stream": "stdout", } b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { h.streams.Send(streamID, "output", eventData) } }) // Cleanup h.streams.Close(streamID) <-done } // BenchmarkJSONSerialization benchmarks response JSON serialization. func BenchmarkJSONSerialization(b *testing.B) { response := map[string]any{ "id": "cmd-test-001", "project": "pantheon", "type": "claude", "status": "running", "stream_url": "/projects/pantheon/events?stream_id=cmd-test-001", } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, _ = json.Marshal(response) } } // BenchmarkConcurrentRequests benchmarks concurrent request handling. func BenchmarkConcurrentRequests(b *testing.B) { _, router := setupBenchHandler() body := ClaudeRequest{Prompt: "test"} bodyBytes, _ := json.Marshal(body) b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { req := httptest.NewRequest("POST", "/projects/pantheon/claude", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } }) } // BenchmarkRouteMatching benchmarks chi router pattern matching. func BenchmarkRouteMatching(b *testing.B) { _, router := setupBenchHandler() paths := []string{ "/projects", "/projects/pantheon", "/projects/pantheon/claude", "/projects/pantheon/shell", "/projects/pantheon/git", "/projects/pantheon/events", } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { path := paths[i%len(paths)] req := httptest.NewRequest("GET", path, nil) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) } } // BenchmarkMultipleSubscribers benchmarks event fanout to multiple subscribers. func BenchmarkMultipleSubscribers(b *testing.B) { h, _ := setupBenchHandler() streamID := "bench-multi-stream" const numSubscribers = 10 // Create multiple subscribers subscribers := make([]chan streamEvent, numSubscribers) var wg sync.WaitGroup for i := 0; i < numSubscribers; i++ { subscribers[i] = h.streams.Subscribe(streamID) wg.Add(1) go func(ch chan streamEvent) { defer wg.Done() for range ch { } }(subscribers[i]) } eventData := map[string]any{ "line": "benchmark output line", "stream": "stdout", } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { h.streams.Send(streamID, "output", eventData) } // Cleanup h.streams.Close(streamID) wg.Wait() }