package httpserver import ( "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "github.com/tim/cairnquire/apps/server/internal/config" "github.com/tim/cairnquire/apps/server/internal/docs" ) func TestBuildBrowserUsesFolderIndex(t *testing.T) { browser := buildBrowser([]docs.DocumentRecord{ { Path: "guide/index.md", Title: "Guide", UpdatedAt: time.Now(), }, { Path: "guide/setup.md", Title: "Setup", UpdatedAt: time.Now(), }, }, "guide/index.md") if len(browser.Columns) != 2 { t.Fatalf("len(browser.Columns) = %d, want 2", len(browser.Columns)) } root := browser.Columns[0].Items if len(root) != 1 { t.Fatalf("len(root items) = %d, want 1", len(root)) } if !root[0].IsFolder || root[0].Name != "Guide" || root[0].URL != "/docs/guide" { t.Fatalf("root folder = %#v, want Guide folder linking to /docs/guide", root[0]) } child := browser.Columns[1].Items if len(child) != 1 { t.Fatalf("len(child items) = %d, want 1", len(child)) } if child[0].Path == "guide/index.md" { t.Fatalf("folder index file should be represented by the folder, not repeated as a child file") } } func TestTrimEditSuffix(t *testing.T) { tests := []struct { name string input string wantPath string wantOK bool }{ {name: "nested path", input: "guide/setup/edit", wantPath: "guide/setup", wantOK: true}, {name: "root edit sentinel", input: "edit", wantPath: "", wantOK: true}, {name: "document route", input: "guide/setup", wantPath: "", wantOK: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotPath, gotOK := trimEditSuffix(tt.input) if gotPath != tt.wantPath || gotOK != tt.wantOK { t.Fatalf("trimEditSuffix(%q) = (%q, %t), want (%q, %t)", tt.input, gotPath, gotOK, tt.wantPath, tt.wantOK) } }) } } func TestIsContentAssetPath(t *testing.T) { tests := []struct { path string want bool }{ {path: "logo.webp", want: true}, {path: "guide/logo.png", want: true}, {path: "guide/page", want: false}, {path: "guide/page.md", want: false}, {path: "guide/page/edit", want: false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { if got := isContentAssetPath(tt.path); got != tt.want { t.Fatalf("isContentAssetPath(%q) = %t, want %t", tt.path, got, tt.want) } }) } } func TestHandleContentAssetServesImageFromSourceDir(t *testing.T) { root := t.TempDir() image := []byte{ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, } if err := os.WriteFile(filepath.Join(root, "logo.png"), image, 0o644); err != nil { t.Fatalf("write fixture: %v", err) } server := &Server{ config: config.Config{ Content: config.ContentConfig{SourceDir: root}, }, } request := httptest.NewRequest(http.MethodGet, "/docs/logo.png", nil) recorder := httptest.NewRecorder() server.handleContentAsset(recorder, request, "logo.png") response := recorder.Result() if response.StatusCode != http.StatusOK { t.Fatalf("status = %d, want %d", response.StatusCode, http.StatusOK) } if got := response.Header.Get("Content-Type"); got != "image/png" { t.Fatalf("content-type = %q, want image/png", got) } }