zai-proxy/dashboard/main_test.go
jedarden dee82a76a3 chore: update module paths and add evaluation package
- proxy/go.mod: github.com/ardenone/zai-proxy → git.ardenone.com/jedarden/zai-proxy
- dashboard/go.mod: github.com/ardenone/ardenone-cluster/containers/zai-proxy-dashboard → git.ardenone.com/jedarden/zai-proxy/dashboard
- Update all Go import paths in proxy/ and dashboard/ to match new module paths
- Add proxy/evaluation/ package (was missing from initial commit)
- Add docs/plan/plan.md with architecture, security model, telemetry design, and migration checklist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:03:50 -04:00

150 lines
4.2 KiB
Go

// Regression test for index.html redirect loop bug (bd-m6ah)
package main
import (
"io/fs"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.ardenone.com/jedarden/zai-proxy/dashboard/api"
"git.ardenone.com/jedarden/zai-proxy/dashboard/logger"
"git.ardenone.com/jedarden/zai-proxy/dashboard/storage"
)
// TestIndexRedirectLoopBug verifies that / and /index.html return 200 OK
// instead of redirecting infinitely (bd-m6ah).
func TestIndexRedirectLoopBug(t *testing.T) {
// Initialize logger
logger.Init(logger.DefaultConfig())
// Create test storage
store, err := storage.NewStorage(storage.DefaultConfig())
if err != nil {
t.Fatalf("failed to create storage: %v", err)
}
defer store.Close()
// Initialize hub
hub := api.NewSSEHub(api.DefaultConfig())
// Create router
router := api.NewRouter(hub, store, api.DefaultConfig())
// Create test server with the same setup as main.go
mux := http.NewServeMux()
router.SetupRoutes(mux)
// Set up static file handlers (copied from main.go)
frontendSub, err := fs.Sub(frontendFS, "frontend/dist")
if err != nil {
t.Fatalf("failed to create frontend sub FS: %v", err)
}
frontendHandler := http.FileServer(http.FS(frontendSub))
mux.Handle("/assets/", frontendHandler)
// Serve index.html for root path - directly read to avoid redirect loop
indexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
content, err := frontendFS.ReadFile("frontend/dist/index.html")
if err != nil {
http.Error(w, "index.html not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(content)
})
// Serve index.html for all other routes (SPA)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
indexHandler.ServeHTTP(w, r)
return
}
// Try static files first, then fallback to index.html for SPA routing
if _, err := frontendFS.Open("frontend/dist" + r.URL.Path); err == nil {
frontendHandler.ServeHTTP(w, r)
return
}
// SPA fallback - serve index.html for client-side routes
indexHandler.ServeHTTP(w, r)
})
// Apply middleware
handler := api.Chain(mux,
api.RecoveryMiddleware,
api.LoggingMiddleware,
api.CORSMiddleware,
)
server := httptest.NewServer(handler)
defer server.Close()
t.Run("root path returns 200", func(t *testing.T) {
resp, err := http.Get(server.URL + "/")
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
t.Errorf("expected Content-Type to contain text/html, got %s", ct)
}
})
t.Run("index.html returns 200 not 301", func(t *testing.T) {
// Don't follow redirects - we want to catch any 301 redirect
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
t.Errorf("got redirect to %s, expected direct 200 response", req.URL.String())
return http.ErrUseLastResponse
},
}
resp, err := client.Get(server.URL + "/index.html")
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
// This is the key test - should be 200, NOT 301
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
t.Errorf("expected Content-Type to contain text/html, got %s", ct)
}
// Verify it's actually HTML content
body := make([]byte, 1024)
n, _ := resp.Body.Read(body)
bodyStr := string(body[:n])
if !strings.Contains(bodyStr, "<!doctype html>") {
t.Errorf("response body doesn't appear to be HTML")
}
})
t.Run("SPA route fallback returns index.html", func(t *testing.T) {
resp, err := http.Get(server.URL + "/dashboard")
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
t.Errorf("expected Content-Type to contain text/html, got %s", ct)
}
})
}