[🤖] Add Unit-Tests
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m34s

This commit is contained in:
2026-04-27 10:46:08 +02:00
parent dad0e3240d
commit 02d6894ec6
116 changed files with 18795 additions and 1 deletions
+302
View File
@@ -0,0 +1,302 @@
package imageext
import (
"bytes"
"image"
"image/color"
"image/jpeg"
"image/png"
"strings"
"testing"
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
)
// makeRGBA creates a solid-color RGBA image of the given size.
func makeRGBA(w, h int, c color.Color) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.Set(x, y, c)
}
}
return img
}
// makeGradient creates an RGBA image with a gradient pattern.
// Useful for codecs that may behave oddly with uniform colors.
func makeGradient(w, h int) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.Set(x, y, color.RGBA{
R: uint8((x * 255) / max1(w-1)),
G: uint8((y * 255) / max1(h-1)),
B: uint8(((x + y) * 255) / max1(w+h-2)),
A: 255,
})
}
}
return img
}
func max1(v int) int {
if v <= 0 {
return 1
}
return v
}
func TestCropImage_HalfRegion(t *testing.T) {
src := makeGradient(100, 80)
out, err := CropImage(src, 0.0, 0.0, 0.5, 0.5)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 50)
tst.AssertEqual(t, out.Bounds().Dy(), 40)
}
func TestCropImage_Offset(t *testing.T) {
src := makeGradient(200, 100)
out, err := CropImage(src, 0.25, 0.5, 0.5, 0.5)
tst.AssertNoErr(t, err)
// SubImage preserves coordinates of the parent image.
tst.AssertEqual(t, out.Bounds().Min.X, 50)
tst.AssertEqual(t, out.Bounds().Min.Y, 50)
tst.AssertEqual(t, out.Bounds().Dx(), 100)
tst.AssertEqual(t, out.Bounds().Dy(), 50)
}
func TestCropImage_Full(t *testing.T) {
src := makeGradient(40, 40)
out, err := CropImage(src, 0.0, 0.0, 1.0, 1.0)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 40)
tst.AssertEqual(t, out.Bounds().Dy(), 40)
}
func TestEncodeImage_AllCompressions(t *testing.T) {
src := makeGradient(20, 16)
cases := []struct {
comp ImageCompresson
mime string
signature []byte
}{
{CompressionPNGNone, "image/png", []byte{0x89, 'P', 'N', 'G'}},
{CompressionPNGSpeed, "image/png", []byte{0x89, 'P', 'N', 'G'}},
{CompressionPNGBest, "image/png", []byte{0x89, 'P', 'N', 'G'}},
{CompressionJPEG100, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG90, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG80, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG70, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG60, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG50, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG25, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG10, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
{CompressionJPEG1, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}},
}
for _, c := range cases {
t.Run(string(c.comp), func(t *testing.T) {
buf, mime, err := EncodeImage(src, c.comp)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, mime, c.mime)
tst.AssertTrue(t, buf.Len() > 0)
data := buf.Bytes()
tst.AssertTrue(t, len(data) >= len(c.signature))
tst.AssertTrue(t, bytes.Equal(data[:len(c.signature)], c.signature))
// Round-trip decode to confirm the bytes form a valid image.
var dec image.Image
var derr error
if c.mime == "image/png" {
dec, derr = png.Decode(bytes.NewReader(data))
} else {
dec, derr = jpeg.Decode(bytes.NewReader(data))
}
tst.AssertNoErr(t, derr)
tst.AssertEqual(t, dec.Bounds().Dx(), 20)
tst.AssertEqual(t, dec.Bounds().Dy(), 16)
})
}
}
func TestEncodeImage_UnknownCompression(t *testing.T) {
src := makeRGBA(4, 4, color.White)
buf, mime, err := EncodeImage(src, ImageCompresson("UNKNOWN"))
tst.AssertTrue(t, err != nil)
tst.AssertEqual(t, mime, "")
tst.AssertEqual(t, buf.Len(), 0)
}
func TestObjectFitImage_Cover_SmallerThanBB(t *testing.T) {
// Image (100x100) is smaller than the BB (200x100), so the output is
// scaled down to the smaller-axis factor of 0.5: 100x50.
src := makeGradient(100, 100)
out, rect, err := ObjectFitImage(src, 200, 100, ImageFitCover, color.Transparent)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 100)
tst.AssertEqual(t, out.Bounds().Dy(), 50)
tst.AssertDeepEqual(t, rect, PercentageRectangle{0, 0, 1, 1})
}
func TestObjectFitImage_Cover_LargerThanBB(t *testing.T) {
// Image (400x200) is larger than the BB (200x100); fac is capped at 1, so
// the output is exactly the BB size.
src := makeGradient(400, 200)
out, _, err := ObjectFitImage(src, 200, 100, ImageFitCover, color.Transparent)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 200)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
}
func TestObjectFitImage_ContainCenter(t *testing.T) {
// Image 100x100 in a BB of 200x100 -> output 200x100, drawn rect 100x100 centered.
src := makeGradient(100, 100)
out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainCenter, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 200)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
// (200-100)/2 = 50 -> X=50/200 = 0.25, W=100/200=0.5, Y=0, H=1
tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0.25, Y: 0, W: 0.5, H: 1})
}
func TestObjectFitImage_ContainTopLeft(t *testing.T) {
src := makeGradient(100, 100)
out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainTopLeft, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 200)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0, W: 0.5, H: 1})
}
func TestObjectFitImage_ContainTopRight(t *testing.T) {
src := makeGradient(100, 100)
out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainTopRight, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 200)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0.5, Y: 0, W: 0.5, H: 1})
}
func TestObjectFitImage_ContainBottomLeft(t *testing.T) {
// Image 200x100 in a BB of 100x100 (image is bigger so facOut is capped at 1)
src := makeGradient(200, 100)
out, rect, err := ObjectFitImage(src, 100, 100, ImageFitContainBottomLeft, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 100)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
// dw=100, dh=50 -> bottom-left rect: (0, 50, 100, 100) -> Y=0.5, H=0.5
tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0.5, W: 1, H: 0.5})
}
func TestObjectFitImage_ContainBottomRight(t *testing.T) {
src := makeGradient(200, 100)
out, rect, err := ObjectFitImage(src, 100, 100, ImageFitContainBottomRight, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 100)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0.5, W: 1, H: 0.5})
}
func TestObjectFitImage_Stretch(t *testing.T) {
// Image 100x100 in BB 200x100 -> uses max(facW=0.5, facH=1.0) capped at 1, so result is 200x100.
src := makeGradient(100, 100)
out, rect, err := ObjectFitImage(src, 200, 100, ImageFitStretch, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 200)
tst.AssertEqual(t, out.Bounds().Dy(), 100)
tst.AssertDeepEqual(t, rect, PercentageRectangle{0, 0, 1, 1})
}
func TestObjectFitImage_Stretch_SmallImage(t *testing.T) {
// Image 50x25 in BB 200x100 -> max(0.25, 0.25) = 0.25, output 50x25.
src := makeGradient(50, 25)
out, _, err := ObjectFitImage(src, 200, 100, ImageFitStretch, color.Black)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 50)
tst.AssertEqual(t, out.Bounds().Dy(), 25)
}
func TestObjectFitImage_UnknownFit(t *testing.T) {
src := makeGradient(20, 20)
out, _, err := ObjectFitImage(src, 100, 100, ImageFit("BOGUS"), color.Black)
tst.AssertTrue(t, err != nil)
if out != nil {
t.Errorf("expected nil image on error, got %v", out)
}
}
func TestVerifyAndDecodeImage_PNG(t *testing.T) {
src := makeGradient(12, 8)
buf := bytes.Buffer{}
tst.AssertNoErr(t, png.Encode(&buf, src))
out, err := VerifyAndDecodeImage(&buf, "image/png")
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 12)
tst.AssertEqual(t, out.Bounds().Dy(), 8)
}
func TestVerifyAndDecodeImage_JPEG(t *testing.T) {
src := makeGradient(16, 16)
buf := bytes.Buffer{}
tst.AssertNoErr(t, jpeg.Encode(&buf, src, &jpeg.Options{Quality: 90}))
out, err := VerifyAndDecodeImage(&buf, "image/jpeg")
tst.AssertNoErr(t, err)
tst.AssertEqual(t, out.Bounds().Dx(), 16)
tst.AssertEqual(t, out.Bounds().Dy(), 16)
}
func TestVerifyAndDecodeImage_UnknownMime(t *testing.T) {
out, err := VerifyAndDecodeImage(strings.NewReader("whatever"), "image/gif")
tst.AssertTrue(t, err != nil)
if out != nil {
t.Errorf("expected nil image on error, got %v", out)
}
}
func TestVerifyAndDecodeImage_BadPNG(t *testing.T) {
out, err := VerifyAndDecodeImage(strings.NewReader("not a png"), "image/png")
tst.AssertTrue(t, err != nil)
if out != nil {
t.Errorf("expected nil image on error, got %v", out)
}
}
func TestVerifyAndDecodeImage_BadJPEG(t *testing.T) {
out, err := VerifyAndDecodeImage(strings.NewReader("not a jpeg"), "image/jpeg")
tst.AssertTrue(t, err != nil)
if out != nil {
t.Errorf("expected nil image on error, got %v", out)
}
}