arnaucube
arnaucube/gitviewPublic Self-hosted repository
gitview/internal/routes/branding_test.go
internal/routes/branding_test.goRaw
 1package routes
 2
 3import (
 4	"fmt"
 5	"net/http"
 6	"net/http/httptest"
 7	"os"
 8	"path/filepath"
 9	"regexp"
10	"strings"
11	"testing"
12
13	"gitview/internal/config"
14)
15
16// Verify configured branding renders consistently and resolves to the served asset.
17func TestConfiguredBranding(t *testing.T) {
18	c, _ := testWorkspace(t)
19	static := t.TempDir()
20	if err := os.MkdirAll(filepath.Join(static, "branding"), 0755); err != nil {
21		t.Fatal(err)
22	}
23	const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"><circle r="1"/></svg>`
24	for _, name := range []string{"favicon.svg", "branding/owner mark.svg"} {
25		if err := os.WriteFile(filepath.Join(static, name), []byte(svg), 0644); err != nil {
26			t.Fatal(err)
27		}
28	}
29	favicon := regexp.MustCompile(`<link rel="icon" href="([^"]+)"`)
30	logo := regexp.MustCompile(`<img class="brand-mark" src="([^"]+)"`)
31	for _, tt := range []struct{ name, settings, asset string }{
32		{"logo overrides legacy favicon", "  logo: branding/owner mark.svg\n  favicon: nonexistent.svg\n", "/static/branding/owner%20mark.svg"},
33		{"legacy favicon", "  favicon: branding/owner mark.svg\n", "/static/branding/owner%20mark.svg"},
34		{"default logo", "", "/static/favicon.svg"},
35	} {
36		t.Run(tt.name, func(t *testing.T) {
37			path := filepath.Join(t.TempDir(), "config.yaml")
38			yaml := fmt.Sprintf("repo:\n  scanPath: %q\n  mainBranch: [main]\ndirs:\n  templates: %q\n  static: %q\nmeta:\n  username: arnaucube\n  title: browser-only-title\n%s", c.Repo.ScanPath, c.Dirs.Templates, static, tt.settings)
39			if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
40				t.Fatal(err)
41			}
42			loaded, err := config.Read(path)
43			if err != nil {
44				t.Fatal(err)
45			}
46			handler := Handlers(loaded)
47			for _, page := range []struct {
48				url    string
49				status int
50			}{{"/", 200}, {"/sample", 200}, {"/missing", 404}} {
51				w := httptest.NewRecorder()
52				handler.ServeHTTP(w, httptest.NewRequest("GET", page.url, nil))
53				if w.Code != page.status {
54					t.Fatalf("%s: status %d", page.url, w.Code)
55				}
56				body := w.Body.String()
57				icon := favicon.FindStringSubmatch(body)
58				brand := logo.FindStringSubmatch(body)
59				if len(icon) != 2 || len(brand) != 2 {
60					t.Fatalf("%s: missing favicon or logo", page.url)
61				}
62				if icon[1] != tt.asset || brand[1] != tt.asset {
63					t.Errorf("%s: favicon %q, logo %q; want %q", page.url, icon[1], brand[1], tt.asset)
64				}
65				_, header, found := strings.Cut(body, `<header class="topbar">`)
66				if !found {
67					t.Fatalf("%s: missing topbar", page.url)
68				}
69				topbar, _, _ := strings.Cut(header, "</header>")
70				if !strings.Contains(topbar, `class="brand-name">arnaucube</span>`) || strings.Contains(topbar, "browser-only-title") || strings.Contains(topbar, "top-context") {
71					t.Errorf("%s: topbar does not use only the configured username", page.url)
72				}
73				if !strings.Contains(body, "browser-only-title</title>") {
74					t.Errorf("%s: browser title was lost", page.url)
75				}
76			}
77			w := httptest.NewRecorder()
78			handler.ServeHTTP(w, httptest.NewRequest("GET", tt.asset, nil))
79			if w.Code != http.StatusOK || w.Body.String() != svg {
80				t.Fatalf("logo request: status %d, body %q", w.Code, w.Body.String())
81			}
82			if got := w.Header().Get("Content-Type"); !strings.HasPrefix(got, "image/svg+xml") {
83				t.Errorf("logo content type: %q", got)
84			}
85			loaded.Repo.ScanPath = filepath.Join(t.TempDir(), "missing")
86			w = httptest.NewRecorder()
87			handler.ServeHTTP(w, httptest.NewRequest("GET", "/", nil))
88			if w.Code != 500 || !strings.Contains(w.Body.String(), tt.asset) {
89				t.Errorf("server error page lost configured branding: status %d", w.Code)
90			}
91		})
92	}
93}