mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Comments only; no executable code changed. Verified by comparing the Go
token stream of every touched file before and after: identical.
Removes 375 of the 1104 comment lines this branch added, targeting content
that belongs in a commit message or PR body rather than in the code:
rejected alternatives ("DeleteIfUnchanged, not Delete", "Waking all beats
routing by kind"), refactor history ("as the legacy reader did"), issue
references (#5798, #5597, #5376), benchmark numbers (~400ms, ~16k allocs),
and four persistence doc comments that duplicated the interface godoc in
model/artwork.go verbatim.
Comments predating this branch are left untouched.
The ASCII fixture trees in the e2e suites are deliberately kept above the
line budget: they diagram the fixture layout with its expected outcomes,
and every pre-existing block in those files carries one.
118 lines
5.3 KiB
Go
118 lines
5.3 KiB
Go
package imghttp_test
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/core/artwork"
|
|
"github.com/navidrome/navidrome/server/imghttp"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
const testHash = "0123456789abcdef"
|
|
const testRepTag = testHash + ".300.false.q75"
|
|
|
|
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}
|
|
}
|
|
|
|
// A re-encoded response carries a representation ETag distinct from the pixel hash, so the
|
|
// validator versions with the encode settings.
|
|
func resized() *artwork.Image {
|
|
return &artwork.Image{
|
|
ReadCloser: io.NopCloser(strings.NewReader("IMG")),
|
|
Hash: testHash,
|
|
ETag: testRepTag,
|
|
LastUpdated: lastMod,
|
|
}
|
|
}
|
|
|
|
func unvalidated() *artwork.Image {
|
|
return &artwork.Image{ReadCloser: io.NopCloser(strings.NewReader("IMG")), LastUpdated: lastMod}
|
|
}
|
|
|
|
var _ = Describe("WriteImageHeaders", func() {
|
|
type testCase struct {
|
|
img *artwork.Image
|
|
requestedHash string
|
|
ifNoneMatch string
|
|
want304 bool
|
|
wantCache string
|
|
wantETag string
|
|
wantLastMod bool
|
|
}
|
|
|
|
DescribeTable("applies the artwork caching contract",
|
|
func(c testCase) {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "/img", nil)
|
|
if c.ifNoneMatch != "" {
|
|
r.Header.Set("If-None-Match", c.ifNoneMatch)
|
|
}
|
|
|
|
Expect(imghttp.WriteImageHeaders(w, r, c.img, c.requestedHash)).To(Equal(c.want304))
|
|
|
|
h := w.Header()
|
|
Expect(h.Get("Cache-Control")).To(Equal(c.wantCache))
|
|
Expect(h.Get("ETag")).To(Equal(c.wantETag))
|
|
if c.img.Placeholder {
|
|
Expect(h.Get("ETag")).To(BeEmpty(), "placeholder must not set an ETag")
|
|
Expect(h.Get("Last-Modified")).To(BeEmpty(), "placeholder must not set Last-Modified")
|
|
}
|
|
Expect(h.Get("Last-Modified") != "").To(Equal(c.wantLastMod))
|
|
if c.want304 {
|
|
Expect(w.Code).To(Equal(http.StatusNotModified))
|
|
Expect(w.Body.Len()).To(BeZero(), "a 304 must have an empty body")
|
|
}
|
|
},
|
|
Entry("placeholder is never cached and carries no validators",
|
|
testCase{img: placeholder(), wantCache: "no-store"}),
|
|
Entry("found with matching requested hash is immutable",
|
|
testCase{img: found(), requestedHash: testHash, wantCache: "public, max-age=31536000, immutable", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("found with bare id revalidates via no-cache",
|
|
testCase{img: found(), wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("found with mismatched requested hash revalidates",
|
|
testCase{img: found(), requestedHash: "ffffffffffffffff", wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("resized keeps pixel-hash immutable but serves the representation ETag",
|
|
testCase{img: resized(), requestedHash: testHash, wantCache: "public, max-age=31536000, immutable", wantETag: `"` + testRepTag + `"`, wantLastMod: true}),
|
|
Entry("resized 304s on the representation ETag, not the pixel hash",
|
|
testCase{img: resized(), ifNoneMatch: `"` + testRepTag + `"`, want304: true, wantCache: "public, no-cache", wantETag: `"` + testRepTag + `"`, wantLastMod: true}),
|
|
Entry("resized does not 304 on a stale pixel-hash validator (config changed)",
|
|
testCase{img: resized(), ifNoneMatch: `"` + testHash + `"`, want304: false, wantCache: "public, no-cache", wantETag: `"` + testRepTag + `"`, wantLastMod: true}),
|
|
Entry("If-None-Match matching the hash yields 304",
|
|
testCase{img: found(), requestedHash: testHash, ifNoneMatch: `"` + testHash + `"`, want304: true, wantCache: "public, max-age=31536000, immutable", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("weak If-None-Match matches (weak comparison)",
|
|
testCase{img: found(), ifNoneMatch: `W/"` + testHash + `"`, want304: true, wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("If-None-Match with multiple values matches one",
|
|
testCase{img: found(), ifNoneMatch: `"deadbeefdeadbeef", W/"` + testHash + `", "cafecafecafecafe"`, want304: true, wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("If-None-Match star matches any current representation",
|
|
testCase{img: found(), ifNoneMatch: "*", want304: true, wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("non-matching If-None-Match serves body",
|
|
testCase{img: found(), ifNoneMatch: `"deadbeefdeadbeef"`, want304: false, wantCache: "public, no-cache", wantETag: `"` + testHash + `"`, wantLastMod: true}),
|
|
Entry("placeholder ignores If-None-Match and never 304s",
|
|
testCase{img: placeholder(), ifNoneMatch: "*", want304: false, wantCache: "no-store"}),
|
|
|
|
// With no validator, an emitted ETag would be the same empty tag on every such response,
|
|
// and matching it would 304 changed bytes.
|
|
Entry("omits the ETag entirely when there is no validator",
|
|
testCase{img: unvalidated(), wantCache: "public, no-cache", wantLastMod: true}),
|
|
Entry("never 304s an empty validator echoed back by the client",
|
|
testCase{img: unvalidated(), ifNoneMatch: `""`, want304: false,
|
|
wantCache: "public, no-cache", wantLastMod: true}),
|
|
)
|
|
})
|