package server import ( "io" "net/http" "net/http/httptest" "sync/atomic" "testing" "sneak.berlin/go/pixa/internal/imageproxy" "sneak.berlin/go/pixa/internal/imagerequest" "sneak.berlin/go/pixa/internal/magic" "sneak.berlin/go/pixa/internal/middleware" ) // testSVG is the SVG the upstream serves in the SVG tests below. const testSVG = `` + `` // startSVGUpstream starts an upstream that answers every request with // testSVG as image/svg+xml, and returns it with its count of requests. func startSVGUpstream(t *testing.T) (*httptest.Server, *atomic.Int64) { t.Helper() var requests atomic.Int64 upstream := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) w.Header().Set("Content-Type", "image/svg+xml") _, _ = io.WriteString(w, testSVG) })) t.Cleanup(upstream.Close) return upstream, &requests } // TestImageProxyServesSVGAsItIs requests an SVG source in its own format // (orig) at the orig size through pixa's router, upstream fetcher, image // processor and cache, twice. Both answers are the SVG itself, never another // format, the second from the cache; the upstream is asked once. Both carry // the Content-Security-Policy of an SVG response in place of the // middleware's. orig at another size is refused with 400. func TestImageProxyServesSVGAsItIs(t *testing.T) { t.Parallel() const wantPolicy = "default-src 'none'; style-src 'unsafe-inline'; " + "img-src data:; frame-ancestors 'none'; sandbox" upstream, requests := startSVGUpstream(t) s, _, _ := startImageProxy(t, upstream) target := "/v1/image/" + upstreamHost + "/drawing.svg/orig.orig" for _, wantCache := range []imageproxy.CacheStatus{ imageproxy.CacheMiss, imageproxy.CacheHit, } { rec := getImage(t, s, target) if rec.Header().Get("Content-Type") != "image/svg+xml" || rec.Body.String() != testSVG { t.Errorf("GET %s: %s %q, want image/svg+xml %q", target, rec.Header().Get("Content-Type"), rec.Body.String(), testSVG) } if got := rec.Header().Get("X-Pixa-Cache"); got != string(wantCache) { t.Errorf("GET %s: X-Pixa-Cache = %q, want %q", target, got, wantCache) } policy := rec.Header().Get("Content-Security-Policy") if policy != wantPolicy { t.Errorf("GET %s: Content-Security-Policy = %q, want %q", target, policy, wantPolicy) } } if requests.Load() != 1 { t.Errorf("upstream requests = %d, want 1", requests.Load()) } resized := "/v1/image/" + upstreamHost + "/drawing.svg/32x24.orig" rec := httptest.NewRecorder() s.ServeHTTP(rec, httptest.NewRequestWithContext( t.Context(), http.MethodGet, resized, nil)) t.Logf("GET %s: %d %s", resized, rec.Code, rec.Body.String()) if rec.Code != http.StatusBadRequest { t.Errorf("GET %s: %d %s, want 400", resized, rec.Code, rec.Body.String()) } } // TestImageProxyRendersSVG requests an SVG source as each raster format // through pixa's router, upstream fetcher and image processor. Each answer // is an image in that format, with the middleware's Content-Security-Policy. func TestImageProxyRendersSVG(t *testing.T) { t.Parallel() upstream, _ := startSVGUpstream(t) s, _, _ := startImageProxy(t, upstream) for _, format := range []imagerequest.Format{ imagerequest.FormatJPEG, imagerequest.FormatPNG, imagerequest.FormatGIF, imagerequest.FormatWebP, imagerequest.FormatAVIF, imagerequest.FormatJXL, } { target := "/v1/image/" + upstreamHost + "/drawing.svg/32x24." + string(format) rec := getImage(t, s, target) detected, err := magic.DetectFormat(rec.Body.Bytes()) t.Logf("GET %s: %s, body in %q (%v)", target, rec.Header().Get("Content-Type"), detected, err) if rec.Header().Get("Content-Type") != format.MIMEType() || err != nil || detected != format { t.Errorf("GET %s: %s, body in %q (%v); want %s", target, rec.Header().Get("Content-Type"), detected, err, format.MIMEType()) } policy := rec.Header().Get("Content-Security-Policy") if policy != middleware.ContentSecurityPolicyValue { t.Errorf("GET %s: Content-Security-Policy = %q, want %q", target, policy, middleware.ContentSecurityPolicyValue) } } }