From 798bc510bdd7bb6125ae9d9b35296187e1ae082a Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 20 Apr 2026 17:43:55 -0300 Subject: [PATCH] test(soteria): cover embedded ui renderer paths --- internal/server/ui_renderer_test.go | 90 +++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 internal/server/ui_renderer_test.go diff --git a/internal/server/ui_renderer_test.go b/internal/server/ui_renderer_test.go new file mode 100644 index 0000000..a115972 --- /dev/null +++ b/internal/server/ui_renderer_test.go @@ -0,0 +1,90 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNewUIRendererLoadsEmbeddedAssets(t *testing.T) { + renderer := newUIRenderer() + + if renderer == nil { + t.Fatalf("expected ui renderer instance") + } + if renderer.fsys == nil { + t.Fatalf("expected embedded UI filesystem to be available") + } +} + +func TestUIRendererServeIndexHandlesMissingAndEmbeddedAssets(t *testing.T) { + t.Run("missing assets", func(t *testing.T) { + renderer := &uiRenderer{} + req := httptest.NewRequest(http.MethodGet, "/", nil) + res := httptest.NewRecorder() + + err := renderer.ServeIndex(res, req) + if err == nil || !strings.Contains(err.Error(), "not available") { + t.Fatalf("expected missing UI asset error, got %v", err) + } + }) + + t.Run("embedded index", func(t *testing.T) { + renderer := newUIRenderer() + req := httptest.NewRequest(http.MethodGet, "/", nil) + res := httptest.NewRecorder() + + if err := renderer.ServeIndex(res, req); err != nil { + t.Fatalf("expected embedded index to render, got %v", err) + } + if got := res.Header().Get("Content-Type"); got != "text/html; charset=utf-8" { + t.Fatalf("expected html content type, got %q", got) + } + if got := res.Header().Get("Cache-Control"); got != "no-store" { + t.Fatalf("expected no-store cache control, got %q", got) + } + if !strings.Contains(res.Body.String(), "Soteria Backup Console") { + t.Fatalf("expected rendered index HTML, got %q", res.Body.String()) + } + }) +} + +func TestUIRendererServeAssetRejectsInvalidPathsAndServesRealAssets(t *testing.T) { + renderer := newUIRenderer() + + testCases := []struct { + name string + method string + path string + ok bool + }{ + {name: "missing filesystem", method: http.MethodGet, path: "/assets/index-C8vHBL9g.js", ok: false}, + {name: "invalid method", method: http.MethodPost, path: "/assets/index-C8vHBL9g.js", ok: false}, + {name: "empty path", method: http.MethodGet, path: "/", ok: false}, + {name: "api path", method: http.MethodGet, path: "/v1/backup", ok: false}, + {name: "parent traversal", method: http.MethodGet, path: "/../secret.txt", ok: false}, + {name: "missing file", method: http.MethodGet, path: "/assets/does-not-exist.js", ok: false}, + {name: "real asset", method: http.MethodGet, path: "/assets/index-C8vHBL9g.js", ok: true}, + {name: "head asset", method: http.MethodHead, path: "/assets/index-B24a4-XK.css", ok: true}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + activeRenderer := renderer + if tc.name == "missing filesystem" { + activeRenderer = &uiRenderer{} + } + + req := httptest.NewRequest(tc.method, tc.path, nil) + res := httptest.NewRecorder() + + if got := activeRenderer.ServeAsset(res, req); got != tc.ok { + t.Fatalf("expected ServeAsset=%v, got %v", tc.ok, got) + } + if tc.ok && res.Code != http.StatusOK { + t.Fatalf("expected successful asset response, got %d", res.Code) + } + }) + } +}