Files
silo-server/internal/api/handlers/theme_test.go

104 lines
3.4 KiB
Go

package handlers
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
type themeSettingsStub struct {
values map[string]string
}
func (s *themeSettingsStub) Get(_ context.Context, key string) (string, error) {
return s.values[key], nil
}
type themeRoundTripFunc func(*http.Request) (*http.Response, error)
func (f themeRoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
func TestThemeAdminCSSIsNotBrowserCached(t *testing.T) {
h := NewThemeHandler(&themeSettingsStub{values: map[string]string{
"ui.admin_theme_vars": `{"--primary":"red"}`,
"ui.admin_custom_css": `.shell { color: red; }`,
}})
rec := httptest.NewRecorder()
h.HandleAdminCSS(rec, httptest.NewRequest(http.MethodGet, "/theme/admin-css", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
if got := rec.Header().Get("Cache-Control"); got != "no-store" {
t.Fatalf("Cache-Control = %q, want no-store", got)
}
}
func TestThemeRemoteURLsRequireHTTPSApprovedHost(t *testing.T) {
h := NewThemeHandler(&themeSettingsStub{values: map[string]string{
"theme.catalog_url": "http://raw.githubusercontent.com/Silo-Server/silo-themes/main/catalog.json",
}})
catalog := httptest.NewRecorder()
h.HandleCatalog(catalog, httptest.NewRequest(http.MethodGet, "/theme/catalog", nil))
if catalog.Code != http.StatusBadRequest {
t.Fatalf("HTTP catalog status = %d, want 400; body=%s", catalog.Code, catalog.Body.String())
}
download := httptest.NewRecorder()
h.HandleDownload(download, httptest.NewRequest(
http.MethodGet,
"/theme/download?url=http%3A%2F%2Fraw.githubusercontent.com%2Ftheme.json",
nil,
))
if download.Code != http.StatusForbidden {
t.Fatalf("HTTP download status = %d, want 403; body=%s", download.Code, download.Body.String())
}
}
func TestThemeCatalogCacheIsScopedToConfiguredURL(t *testing.T) {
settings := &themeSettingsStub{values: map[string]string{
"theme.catalog_url": "https://raw.githubusercontent.com/example/themes/main/one.json",
}}
h := NewThemeHandler(settings)
requests := 0
h.httpClient = &http.Client{Transport: themeRoundTripFunc(func(req *http.Request) (*http.Response, error) {
requests++
body := `{"catalog":"one"}`
if strings.HasSuffix(req.URL.Path, "/two.json") {
body = `{"catalog":"two"}`
}
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(body)),
}, nil
})}
first := httptest.NewRecorder()
h.HandleCatalog(first, httptest.NewRequest(http.MethodGet, "/theme/catalog", nil))
if first.Code != http.StatusOK || !strings.Contains(first.Body.String(), `"one"`) {
t.Fatalf("first response = %d %s", first.Code, first.Body.String())
}
// Make the old cache look fresh, then change the saved origin. URL identity
// must still force a new fetch instead of serving the previous catalog.
h.catalogFetched = time.Now()
settings.values["theme.catalog_url"] = "https://raw.githubusercontent.com/example/themes/main/two.json"
second := httptest.NewRecorder()
h.HandleCatalog(second, httptest.NewRequest(http.MethodGet, "/theme/catalog", nil))
if second.Code != http.StatusOK || !strings.Contains(second.Body.String(), `"two"`) {
t.Fatalf("second response = %d %s", second.Code, second.Body.String())
}
if requests != 2 {
t.Fatalf("upstream requests = %d, want 2", requests)
}
}