From 97a985efb84d7a1c6fe657331762e489a1004e38 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 2 Feb 2026 22:01:46 +0000 Subject: [PATCH] Test: improve frontend embedding coverage Enhance tests for frontend embedding to cover filesystem overrides, dev proxy configuration, and SPA header handling. --- internal/api/frontend_embed_test.go | 128 ++++++++++++++++++---------- 1 file changed, 84 insertions(+), 44 deletions(-) diff --git a/internal/api/frontend_embed_test.go b/internal/api/frontend_embed_test.go index 56c554cab..b3b875d77 100644 --- a/internal/api/frontend_embed_test.go +++ b/internal/api/frontend_embed_test.go @@ -5,7 +5,6 @@ import ( "net/http" "net/http/httptest" "os" - "path/filepath" "strings" "sync" "testing" @@ -17,81 +16,122 @@ func resetDevProxy() { devProxyErr = nil } -func writeFile(t *testing.T, dir, name, content string) { - t.Helper() - path := filepath.Join(dir, name) - if err := os.WriteFile(path, []byte(content), 0644); err != nil { - t.Fatalf("write file: %v", err) +func TestGetFrontendDevProxy(t *testing.T) { + resetDevProxy() + t.Setenv("FRONTEND_DEV_SERVER", "http://localhost:1234") + + proxy, err := getFrontendDevProxy() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if proxy == nil { + t.Fatalf("expected proxy to be initialized") + } + + resetDevProxy() + t.Setenv("FRONTEND_DEV_SERVER", "://bad-url") + if _, err := getFrontendDevProxy(); err == nil { + t.Fatalf("expected error for invalid URL") } } -func TestGetFrontendFSOverride(t *testing.T) { - resetDevProxy() - dir := t.TempDir() - writeFile(t, dir, "index.html", "ok") - t.Setenv("PULSE_FRONTEND_DIR", dir) - - fsys, err := getFrontendFS() - if err != nil { - t.Fatalf("getFrontendFS error: %v", err) +func TestGetFrontendFS_Override(t *testing.T) { + tmp := t.TempDir() + indexPath := tmp + "/index.html" + if err := os.WriteFile(indexPath, []byte("hello"), 0o644); err != nil { + t.Fatalf("write index: %v", err) } - f, err := fsys.Open("index.html") + t.Setenv("PULSE_FRONTEND_DIR", tmp) + fs, err := getFrontendFS() + if err != nil { + t.Fatalf("getFrontendFS: %v", err) + } + + f, err := fs.Open("index.html") if err != nil { t.Fatalf("open index: %v", err) } defer f.Close() - - data, err := io.ReadAll(f) + content, err := io.ReadAll(f) if err != nil { t.Fatalf("read index: %v", err) } - if string(data) != "ok" { - t.Fatalf("unexpected content: %s", string(data)) + if string(content) != "hello" { + t.Fatalf("unexpected content: %q", string(content)) } } -func TestServeFrontendHandler(t *testing.T) { +func TestServeFrontendHandler_StaticAndSPA(t *testing.T) { resetDevProxy() - dir := t.TempDir() - writeFile(t, dir, "index.html", "index") - writeFile(t, dir, "app-123.js", "console.log('ok');") - t.Setenv("PULSE_FRONTEND_DIR", dir) t.Setenv("FRONTEND_DEV_SERVER", "") + tmp := t.TempDir() + if err := os.MkdirAll(tmp+"/assets", 0o755); err != nil { + t.Fatalf("mkdir assets: %v", err) + } + if err := os.WriteFile(tmp+"/index.html", []byte("ok"), 0o644); err != nil { + t.Fatalf("write index: %v", err) + } + if err := os.WriteFile(tmp+"/logo.svg", []byte(""), 0o644); err != nil { + t.Fatalf("write svg: %v", err) + } + if err := os.WriteFile(tmp+"/assets/index-abc123.css", []byte("body{}"), 0o644); err != nil { + t.Fatalf("write css: %v", err) + } + t.Setenv("PULSE_FRONTEND_DIR", tmp) handler := serveFrontendHandler() - rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) - handler(rec, req) - if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "index") { - t.Fatalf("unexpected root response: %d %s", rec.Code, rec.Body.String()) - } - if rec.Header().Get("Cache-Control") == "" { - t.Fatal("expected cache headers for index") - } - - rec = httptest.NewRecorder() - req = httptest.NewRequest(http.MethodGet, "/app-123.js", nil) + rec := httptest.NewRecorder() handler(rec, req) if rec.Code != http.StatusOK { - t.Fatalf("unexpected asset response: %d", rec.Code) + t.Fatalf("root status = %d", rec.Code) } - if !strings.Contains(rec.Header().Get("Cache-Control"), "immutable") { - t.Fatalf("expected immutable cache header, got %s", rec.Header().Get("Cache-Control")) + if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") { + t.Fatalf("expected html content type, got %q", ct) + } + if cc := rec.Header().Get("Cache-Control"); cc == "" { + t.Fatalf("expected cache control headers") } + req = httptest.NewRequest(http.MethodGet, "/assets/index-abc123.css", nil) rec = httptest.NewRecorder() - req = httptest.NewRequest(http.MethodGet, "/missing", nil) handler(rec, req) - if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "index") { - t.Fatalf("expected SPA fallback, got %d %s", rec.Code, rec.Body.String()) + if rec.Code != http.StatusOK { + t.Fatalf("hashed css status = %d", rec.Code) + } + if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "immutable") { + t.Fatalf("expected immutable cache control, got %q", cc) } + req = httptest.NewRequest(http.MethodGet, "/logo.svg", nil) + rec = httptest.NewRecorder() + handler(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("svg status = %d", rec.Code) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/svg+xml" { + t.Fatalf("expected svg content type, got %q", ct) + } + if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "no-cache") { + t.Fatalf("expected no-cache headers, got %q", cc) + } + + req = httptest.NewRequest(http.MethodGet, "/app/settings", nil) + rec = httptest.NewRecorder() + handler(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("spa route status = %d", rec.Code) + } + if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") { + t.Fatalf("expected html content type for spa, got %q", ct) + } + + req = httptest.NewRequest(http.MethodGet, "/api/unknown", nil) rec = httptest.NewRecorder() - req = httptest.NewRequest(http.MethodGet, "/api/test", nil) handler(rec, req) if rec.Code != http.StatusNotFound { - t.Fatalf("expected 404 for api path, got %d", rec.Code) + t.Fatalf("api route status = %d", rec.Code) } }