check / check (push) Waiting to run
The new package internal/providers builds the cache, the fetcher, the processor, the image service, the session manager and the encrypted URL generator from the config. pixad's fx graph provides them with providers.Constructors(), and handlers.New receives them built. The server tests build the same graph, with only the fetcher's dial function replaced. database.New opens the database, so these parts can read it while fx builds them; the cache's stop hook still runs before the database's. Each setting's default is declared in internal/config, and internal/providers works out the default cache_max_bytes. The cache's MaxBytes is the one value for the disk cache limit, and 0 turns the disk cache off. The fetcher and the processor take explicit values. Model: opus-5-5
142 lines
4.4 KiB
Go
142 lines
4.4 KiB
Go
package imageprocessor
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/pixa/internal/imagerequest"
|
|
)
|
|
|
|
// TestImageProcessor_RefusesComputedSideAboveMaximum verifies that a size
|
|
// with one side 0, which Process computes from the input's proportions, is
|
|
// served when that side comes out at MaxOutputDimension and refused with
|
|
// ErrOutputTooLarge when it would be above. A 4096x1 or 1x4096 input given
|
|
// 2 for its short side has 8192 for its long side.
|
|
func TestImageProcessor_RefusesComputedSideAboveMaximum(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
inputWidth, inputHeight int
|
|
size imagerequest.Size
|
|
wantErr error
|
|
}{
|
|
{"width at the maximum", 4096, 1,
|
|
imagerequest.Size{Height: 2}, nil},
|
|
{"width above the maximum", 4096, 1,
|
|
imagerequest.Size{Height: 3}, ErrOutputTooLarge},
|
|
{"height at the maximum", 1, 4096,
|
|
imagerequest.Size{Width: 2}, nil},
|
|
{"height above the maximum", 1, 4096,
|
|
imagerequest.Size{Width: 3}, ErrOutputTooLarge},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
input := createTestJPEG(t, tt.inputWidth, tt.inputHeight)
|
|
|
|
result, err := New(Params{
|
|
MaxInputBytes: 50 << 20,
|
|
MaxConcurrentProcessing: 1,
|
|
}).Process(t.Context(),
|
|
bytes.NewReader(input),
|
|
&imagerequest.Transform{
|
|
Size: tt.size,
|
|
Format: imagerequest.FormatJPEG,
|
|
FitMode: imagerequest.FitCover,
|
|
})
|
|
if result != nil {
|
|
t.Logf("Process() made %dx%d", result.Width, result.Height)
|
|
|
|
if max(result.Width, result.Height) != MaxOutputDimension {
|
|
t.Errorf("Process() made %dx%d, want a side of %d",
|
|
result.Width, result.Height, MaxOutputDimension)
|
|
}
|
|
}
|
|
|
|
if !errors.Is(err, tt.wantErr) {
|
|
t.Errorf("Process() error = %v, want %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestImageProcessor_ComputedSideIsAtLeastOne verifies that, for a size with
|
|
// one side 0, the side computed from the input's proportions is 1 where it
|
|
// would round down to 0, and that Process makes that size in every fit mode
|
|
// but FitOutside, which scales the input to cover the size and so can make
|
|
// it longer. Each case checks the size targetDimensions gives before it
|
|
// calls Process: with that side at 0, an 8192x1 input at 1300x0 makes
|
|
// libvips ask for about 39 GB with FitCover.
|
|
func TestImageProcessor_ComputedSideIsAtLeastOne(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
inputWidth, inputHeight int
|
|
size imagerequest.Size
|
|
wantWidth, wantHeight int
|
|
}{
|
|
{"8192x1 at 100x0", 8192, 1, imagerequest.Size{Width: 100}, 100, 1},
|
|
{"8192x1 at 1300x0", 8192, 1, imagerequest.Size{Width: 1300}, 1300, 1},
|
|
{"8192x2 at 100x0", 8192, 2, imagerequest.Size{Width: 100}, 100, 1},
|
|
{"1x8192 at 0x100", 1, 8192, imagerequest.Size{Height: 100}, 1, 100},
|
|
{"1x8192 at 0x1300", 1, 8192, imagerequest.Size{Height: 1300}, 1, 1300},
|
|
{"2x8192 at 0x100", 2, 8192, imagerequest.Size{Height: 100}, 1, 100},
|
|
}
|
|
|
|
fits := []imagerequest.Fit{
|
|
imagerequest.FitCover, imagerequest.FitContain, imagerequest.FitFill,
|
|
imagerequest.FitInside, imagerequest.FitOutside,
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
for _, fit := range fits {
|
|
t.Run(tt.name+" "+string(fit), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
width, height, err := targetDimensions(tt.size,
|
|
tt.inputWidth, tt.inputHeight)
|
|
if err != nil || width != tt.wantWidth || height != tt.wantHeight {
|
|
t.Fatalf("targetDimensions() = %dx%d, %v, want %dx%d",
|
|
width, height, err, tt.wantWidth, tt.wantHeight)
|
|
}
|
|
|
|
input := createTestJPEG(t, tt.inputWidth, tt.inputHeight)
|
|
|
|
result, err := New(Params{
|
|
MaxInputBytes: 50 << 20,
|
|
MaxConcurrentProcessing: 1,
|
|
}).Process(t.Context(),
|
|
bytes.NewReader(input),
|
|
&imagerequest.Transform{
|
|
Size: tt.size,
|
|
Format: imagerequest.FormatJPEG,
|
|
FitMode: fit,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Process() error = %v", err)
|
|
}
|
|
|
|
defer func() { _ = result.Content.Close() }()
|
|
|
|
t.Logf("Process() made %dx%d", result.Width, result.Height)
|
|
|
|
ok := result.Width == tt.wantWidth && result.Height == tt.wantHeight
|
|
if fit == imagerequest.FitOutside {
|
|
ok = result.Width >= tt.wantWidth && result.Height >= tt.wantHeight
|
|
}
|
|
|
|
if !ok {
|
|
t.Errorf("Process() made %dx%d, want %dx%d (at least, for %s)",
|
|
result.Width, result.Height, tt.wantWidth, tt.wantHeight,
|
|
imagerequest.FitOutside)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|