Skip to content

Commit 98076bc

Browse files
committed
test: add unit tests for embedded default asset fallback
Cover five scenarios: embedded index.html served when root is empty, embedded style.css with correct content-type, embedded 404.html served by serveNotFound when no custom page is configured, sub-path URLs correctly bypassing the embed fallback, and custom not_found config taking priority over the embedded 404.
1 parent dcafc8d commit 98076bc

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

internal/handler/file_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,142 @@ func makeCfgWithRoot(t *testing.T, root string) *config.Config {
564564
return cfg
565565
}
566566

567+
// ---------------------------------------------------------------------------
568+
// Embedded default asset fallback tests
569+
// ---------------------------------------------------------------------------
570+
571+
// setupEmptyRootCfg creates a config whose files.root is an empty temp dir,
572+
// so every disk lookup will miss and trigger the embedded fallback path.
573+
func setupEmptyRootCfg(t *testing.T) *config.Config {
574+
t.Helper()
575+
root := t.TempDir() // intentionally empty
576+
cfg := makeCfgWithRoot(t, root)
577+
cfg.Compression.Enabled = false // keep responses simple for content checks
578+
return cfg
579+
}
580+
581+
// TestEmbedFallback_IndexHTML verifies that a GET / against an empty root
582+
// returns the embedded index.html with status 200 and HTML content.
583+
func TestEmbedFallback_IndexHTML(t *testing.T) {
584+
cfg := setupEmptyRootCfg(t)
585+
c := cache.NewCache(cfg.Cache.MaxBytes)
586+
h := handler.BuildHandler(cfg, c)
587+
588+
req := httptest.NewRequest(http.MethodGet, "/", nil)
589+
rr := httptest.NewRecorder()
590+
h.ServeHTTP(rr, req)
591+
592+
if rr.Code != http.StatusOK {
593+
t.Errorf("status = %d, want 200 for embedded index.html", rr.Code)
594+
}
595+
if ct := rr.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") {
596+
t.Errorf("Content-Type = %q, want text/html for embedded index.html", ct)
597+
}
598+
if body := rr.Body.String(); !strings.Contains(body, "<html") {
599+
t.Errorf("embedded index.html body does not look like HTML: %q", body[:min(len(body), 120)])
600+
}
601+
}
602+
603+
// TestEmbedFallback_StyleCSS verifies that /style.css is served from the
604+
// embedded FS when the file is absent from files.root.
605+
func TestEmbedFallback_StyleCSS(t *testing.T) {
606+
cfg := setupEmptyRootCfg(t)
607+
c := cache.NewCache(cfg.Cache.MaxBytes)
608+
h := handler.BuildHandler(cfg, c)
609+
610+
req := httptest.NewRequest(http.MethodGet, "/style.css", nil)
611+
rr := httptest.NewRecorder()
612+
h.ServeHTTP(rr, req)
613+
614+
if rr.Code != http.StatusOK {
615+
t.Errorf("status = %d, want 200 for embedded style.css", rr.Code)
616+
}
617+
if ct := rr.Header().Get("Content-Type"); !strings.Contains(ct, "text/css") {
618+
t.Errorf("Content-Type = %q, want text/css for embedded style.css", ct)
619+
}
620+
if rr.Body.Len() == 0 {
621+
t.Error("embedded style.css response body must not be empty")
622+
}
623+
}
624+
625+
// TestEmbedFallback_404HTML verifies that a truly unknown file (not in the
626+
// embedded FS either) falls all the way through to serveNotFound, which itself
627+
// serves the embedded 404.html with status 404.
628+
func TestEmbedFallback_404HTML(t *testing.T) {
629+
cfg := setupEmptyRootCfg(t)
630+
cfg.Files.NotFound = "" // no custom 404 configured
631+
c := cache.NewCache(cfg.Cache.MaxBytes)
632+
h := handler.BuildHandler(cfg, c)
633+
634+
req := httptest.NewRequest(http.MethodGet, "/totally-unknown-file.xyz", nil)
635+
rr := httptest.NewRecorder()
636+
h.ServeHTTP(rr, req)
637+
638+
if rr.Code != http.StatusNotFound {
639+
t.Errorf("status = %d, want 404 for unknown file", rr.Code)
640+
}
641+
if ct := rr.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") {
642+
t.Errorf("Content-Type = %q, want text/html for embedded 404 page", ct)
643+
}
644+
if body := rr.Body.String(); !strings.Contains(body, "<html") {
645+
t.Errorf("embedded 404.html body does not look like HTML: %q", body[:min(len(body), 120)])
646+
}
647+
}
648+
649+
// TestEmbedFallback_SubpathNotServed verifies that the embed fallback only
650+
// handles flat filenames. A URL like /sub/index.html must NOT be served from
651+
// the embedded FS (guard against sub-path traversal) and must return 404.
652+
func TestEmbedFallback_SubpathNotServed(t *testing.T) {
653+
cfg := setupEmptyRootCfg(t)
654+
cfg.Files.NotFound = ""
655+
c := cache.NewCache(cfg.Cache.MaxBytes)
656+
h := handler.BuildHandler(cfg, c)
657+
658+
req := httptest.NewRequest(http.MethodGet, "/sub/index.html", nil)
659+
rr := httptest.NewRecorder()
660+
h.ServeHTTP(rr, req)
661+
662+
if rr.Code != http.StatusNotFound {
663+
t.Errorf("status = %d, want 404: embed fallback must not serve sub-path URLs", rr.Code)
664+
}
665+
}
666+
667+
// TestEmbedFallback_CustomNotFoundTakesPriority verifies that a configured
668+
// files.not_found disk file is still preferred over the embedded 404.html.
669+
func TestEmbedFallback_CustomNotFoundTakesPriority(t *testing.T) {
670+
root := t.TempDir()
671+
// Write a custom 404 page to disk (but no other files).
672+
custom404 := "<h1>My Custom 404</h1>"
673+
if err := os.WriteFile(filepath.Join(root, "my404.html"), []byte(custom404), 0644); err != nil {
674+
t.Fatal(err)
675+
}
676+
677+
cfg := makeCfgWithRoot(t, root)
678+
cfg.Files.NotFound = "my404.html"
679+
cfg.Compression.Enabled = false
680+
c := cache.NewCache(cfg.Cache.MaxBytes)
681+
h := handler.BuildHandler(cfg, c)
682+
683+
req := httptest.NewRequest(http.MethodGet, "/missing.html", nil)
684+
rr := httptest.NewRecorder()
685+
h.ServeHTTP(rr, req)
686+
687+
if rr.Code != http.StatusNotFound {
688+
t.Errorf("status = %d, want 404", rr.Code)
689+
}
690+
if !strings.Contains(rr.Body.String(), "My Custom 404") {
691+
t.Errorf("expected custom 404 page to take priority over embedded one, got: %q", rr.Body.String())
692+
}
693+
}
694+
695+
// min is a local helper for Go versions that lack the built-in min (pre-1.21).
696+
func min(a, b int) int {
697+
if a < b {
698+
return a
699+
}
700+
return b
701+
}
702+
567703
// ---------------------------------------------------------------------------
568704
// Benchmarks
569705
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)