mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
167 lines
4.4 KiB
Go
167 lines
4.4 KiB
Go
package imghttp_test
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/core/artwork"
|
|
"github.com/navidrome/navidrome/server/imghttp"
|
|
)
|
|
|
|
const testHash = "0123456789abcdef"
|
|
|
|
var lastMod = time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
|
|
func found() *artwork.Image {
|
|
return &artwork.Image{
|
|
ReadCloser: io.NopCloser(strings.NewReader("IMG")),
|
|
Hash: testHash,
|
|
LastUpdated: lastMod,
|
|
}
|
|
}
|
|
|
|
func placeholder() *artwork.Image {
|
|
return &artwork.Image{ReadCloser: io.NopCloser(strings.NewReader("PH")), Placeholder: true}
|
|
}
|
|
|
|
func TestWriteImageHeaders(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
img *artwork.Image
|
|
requestedHash string
|
|
ifNoneMatch string
|
|
want304 bool
|
|
wantCache string
|
|
wantETag string
|
|
wantLastMod bool
|
|
}{
|
|
{
|
|
name: "placeholder is never cached and carries no validators",
|
|
img: placeholder(),
|
|
wantCache: "no-store",
|
|
},
|
|
{
|
|
name: "found with matching requested hash is immutable",
|
|
img: found(),
|
|
requestedHash: testHash,
|
|
wantCache: "public, max-age=31536000, immutable",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "found with bare id revalidates via no-cache",
|
|
img: found(),
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "found with mismatched requested hash revalidates",
|
|
img: found(),
|
|
requestedHash: "ffffffffffffffff",
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "If-None-Match matching the hash yields 304",
|
|
img: found(),
|
|
requestedHash: testHash,
|
|
ifNoneMatch: `"` + testHash + `"`,
|
|
want304: true,
|
|
wantCache: "public, max-age=31536000, immutable",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "weak If-None-Match matches (weak comparison)",
|
|
img: found(),
|
|
ifNoneMatch: `W/"` + testHash + `"`,
|
|
want304: true,
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "If-None-Match with multiple values matches one",
|
|
img: found(),
|
|
ifNoneMatch: `"deadbeefdeadbeef", W/"` + testHash + `", "cafecafecafecafe"`,
|
|
want304: true,
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "If-None-Match star matches any current representation",
|
|
img: found(),
|
|
ifNoneMatch: "*",
|
|
want304: true,
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "non-matching If-None-Match serves body",
|
|
img: found(),
|
|
ifNoneMatch: `"deadbeefdeadbeef"`,
|
|
want304: false,
|
|
wantCache: "public, no-cache",
|
|
wantETag: `"` + testHash + `"`,
|
|
wantLastMod: true,
|
|
},
|
|
{
|
|
name: "placeholder ignores If-None-Match and never 304s",
|
|
img: placeholder(),
|
|
ifNoneMatch: "*",
|
|
want304: false,
|
|
wantCache: "no-store",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "/img", nil)
|
|
if tc.ifNoneMatch != "" {
|
|
r.Header.Set("If-None-Match", tc.ifNoneMatch)
|
|
}
|
|
|
|
got := imghttp.WriteImageHeaders(w, r, tc.img, tc.requestedHash)
|
|
if got != tc.want304 {
|
|
t.Fatalf("wrote304 = %v, want %v", got, tc.want304)
|
|
}
|
|
|
|
h := w.Header()
|
|
if got := h.Get("Cache-Control"); got != tc.wantCache {
|
|
t.Errorf("Cache-Control = %q, want %q", got, tc.wantCache)
|
|
}
|
|
if got := h.Get("ETag"); got != tc.wantETag {
|
|
t.Errorf("ETag = %q, want %q", got, tc.wantETag)
|
|
}
|
|
if tc.img.Placeholder {
|
|
if h.Get("ETag") != "" {
|
|
t.Errorf("placeholder must not set ETag, got %q", h.Get("ETag"))
|
|
}
|
|
if h.Get("Last-Modified") != "" {
|
|
t.Errorf("placeholder must not set Last-Modified, got %q", h.Get("Last-Modified"))
|
|
}
|
|
}
|
|
if hasLM := h.Get("Last-Modified") != ""; hasLM != tc.wantLastMod {
|
|
t.Errorf("has Last-Modified = %v, want %v", hasLM, tc.wantLastMod)
|
|
}
|
|
if tc.want304 {
|
|
if w.Code != http.StatusNotModified {
|
|
t.Errorf("status = %d, want 304", w.Code)
|
|
}
|
|
if w.Body.Len() != 0 {
|
|
t.Errorf("304 must have empty body, got %q", w.Body.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|