fix(artwork): reject a nil reader in the resize cache instead of panicking

resizedItem.Reader closes what open() hands back, so an open() that reports "no image"
as (nil, nil) rather than an error takes the request down with a nil-pointer panic. Every
caller returns an error today, and no test covered it: the resolution e2e harness stubs
the resize reader out entirely, so no e2e path reaches this code at all.

Guard it and cover Reader directly.
This commit is contained in:
Deluan 2026-08-14 19:00:10 -04:00
commit d9dc6fc55f
2 changed files with 45 additions and 0 deletions

View file

@ -55,6 +55,10 @@ func (r *resizedItem) Reader(ctx context.Context) (io.ReadCloser, error) {
if err != nil {
return nil, err
}
// An open() that reports "no image" as a nil reader would otherwise panic on the Close below.
if orig == nil {
return nil, ErrUnavailable
}
defer orig.Close()
data, err := readCapped(orig)
if err != nil {

View file

@ -0,0 +1,41 @@
package artwork
import (
"context"
"errors"
"io"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("resizedItem", func() {
Describe("Reader", func() {
newItem := func(open func() (io.ReadCloser, error)) *resizedItem {
return &resizedItem{hash: "abc123", size: 300, open: open}
}
It("reports a nil reader as unavailable instead of panicking on it", func() {
// Every caller is expected to report "no image" as an error, but a nil reader reaches
// the deferred Close as a nil interface, which takes the whole request down.
_, err := newItem(func() (io.ReadCloser, error) { return nil, nil }).Reader(context.Background())
Expect(err).To(MatchError(ErrUnavailable))
})
It("propagates the open error", func() {
boom := errors.New("boom")
_, err := newItem(func() (io.ReadCloser, error) { return nil, boom }).Reader(context.Background())
Expect(err).To(MatchError(boom))
})
It("serves the original bytes when they cannot be resized", func() {
rc, err := newItem(func() (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("not an image")), nil
}).Reader(context.Background())
Expect(err).ToNot(HaveOccurred())
defer rc.Close()
Expect(io.ReadAll(rc)).To(Equal([]byte("not an image")))
})
})
})