mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-14 11:48:09 -05:00
245 lines
8.5 KiB
Go
245 lines
8.5 KiB
Go
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
package card
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/base64"
|
||
|
"fmt"
|
||
|
"image"
|
||
|
"image/color"
|
||
|
"image/png"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"code.gitea.io/gitea/modules/log"
|
||
|
"code.gitea.io/gitea/modules/test"
|
||
|
|
||
|
"github.com/golang/freetype/truetype"
|
||
|
"github.com/stretchr/testify/assert"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"golang.org/x/image/font/gofont/goregular"
|
||
|
)
|
||
|
|
||
|
func TestNewCard(t *testing.T) {
|
||
|
width, height := 100, 50
|
||
|
card, err := NewCard(width, height)
|
||
|
require.NoError(t, err, "No error should occur when creating a new card")
|
||
|
assert.NotNil(t, card, "Card should not be nil")
|
||
|
assert.Equal(t, width, card.Img.Bounds().Dx(), "Width should match the provided width")
|
||
|
assert.Equal(t, height, card.Img.Bounds().Dy(), "Height should match the provided height")
|
||
|
|
||
|
// Checking default margin
|
||
|
assert.Equal(t, 0, card.Margin, "Default margin should be 0")
|
||
|
|
||
|
// Checking font parsing
|
||
|
originalFont, _ := truetype.Parse(goregular.TTF)
|
||
|
assert.Equal(t, originalFont, card.Font, "Fonts should be equivalent")
|
||
|
}
|
||
|
|
||
|
func TestSplit(t *testing.T) {
|
||
|
// Note: you normally wouldn't split the same card twice as draw operations would start to overlap each other; but
|
||
|
// it's fine for this limited scope test
|
||
|
card, _ := NewCard(200, 100)
|
||
|
|
||
|
// Test vertical split
|
||
|
leftCard, rightCard := card.Split(true, 50)
|
||
|
assert.Equal(t, 100, leftCard.Img.Bounds().Dx(), "Left card should have half the width of original")
|
||
|
assert.Equal(t, 100, leftCard.Img.Bounds().Dy(), "Left card height unchanged by split")
|
||
|
assert.Equal(t, 100, rightCard.Img.Bounds().Dx(), "Right card should have half the width of original")
|
||
|
assert.Equal(t, 100, rightCard.Img.Bounds().Dy(), "Right card height unchanged by split")
|
||
|
|
||
|
// Test horizontal split
|
||
|
topCard, bottomCard := card.Split(false, 50)
|
||
|
assert.Equal(t, 200, topCard.Img.Bounds().Dx(), "Top card width unchanged by split")
|
||
|
assert.Equal(t, 50, topCard.Img.Bounds().Dy(), "Top card should have half the height of original")
|
||
|
assert.Equal(t, 200, bottomCard.Img.Bounds().Dx(), "Bottom width unchanged by split")
|
||
|
assert.Equal(t, 50, bottomCard.Img.Bounds().Dy(), "Bottom card should have half the height of original")
|
||
|
}
|
||
|
|
||
|
func TestDrawTextSingleLine(t *testing.T) {
|
||
|
card, _ := NewCard(300, 100)
|
||
|
lines, err := card.DrawText("This is a single line", color.Black, 12, Middle, Center)
|
||
|
require.NoError(t, err, "No error should occur when drawing text")
|
||
|
assert.Len(t, lines, 1, "Should be exactly one line")
|
||
|
assert.Equal(t, "This is a single line", lines[0], "Text should match the input")
|
||
|
}
|
||
|
|
||
|
func TestDrawTextLongLine(t *testing.T) {
|
||
|
card, _ := NewCard(300, 100)
|
||
|
text := "This text is definitely too long to fit in three hundred pixels width without wrapping"
|
||
|
lines, err := card.DrawText(text, color.Black, 12, Middle, Center)
|
||
|
require.NoError(t, err, "No error should occur when drawing text")
|
||
|
assert.Len(t, lines, 2, "Text should wrap into multiple lines")
|
||
|
assert.Equal(t, "This text is definitely too long to fit in three hundred", lines[0], "Text should match the input")
|
||
|
assert.Equal(t, "pixels width without wrapping", lines[1], "Text should match the input")
|
||
|
}
|
||
|
|
||
|
func TestDrawTextWordTooLong(t *testing.T) {
|
||
|
card, _ := NewCard(300, 100)
|
||
|
text := "Line 1 Superduperlongwordthatcannotbewrappedbutshouldenduponitsownsingleline Line 3"
|
||
|
lines, err := card.DrawText(text, color.Black, 12, Middle, Center)
|
||
|
require.NoError(t, err, "No error should occur when drawing text")
|
||
|
assert.Len(t, lines, 3, "Text should create two lines despite long word")
|
||
|
assert.Equal(t, "Line 1", lines[0], "First line should contain text before the long word")
|
||
|
assert.Equal(t, "Superduperlongwordthatcannotbewrappedbutshouldenduponitsownsingleline", lines[1], "Second line couldn't wrap the word so it just overflowed")
|
||
|
assert.Equal(t, "Line 3", lines[2], "Third line continued with wrapping")
|
||
|
}
|
||
|
|
||
|
func TestFetchExternalImageServer(t *testing.T) {
|
||
|
blackPng, err := base64.URLEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4AWNgAAAAAgABc3UBGAAAAABJRU5ErkJggg==")
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var tooWideBuf bytes.Buffer
|
||
|
imgTooWide := image.NewGray(image.Rect(0, 0, 16001, 10))
|
||
|
err = png.Encode(&tooWideBuf, imgTooWide)
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
return
|
||
|
}
|
||
|
imgTooWidePng := tooWideBuf.Bytes()
|
||
|
|
||
|
var tooTallBuf bytes.Buffer
|
||
|
imgTooTall := image.NewGray(image.Rect(0, 0, 10, 16002))
|
||
|
err = png.Encode(&tooTallBuf, imgTooTall)
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
return
|
||
|
}
|
||
|
imgTooTallPng := tooTallBuf.Bytes()
|
||
|
|
||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
switch r.URL.Path {
|
||
|
case "/timeout":
|
||
|
// Simulate a timeout by taking a long time to respond
|
||
|
time.Sleep(8 * time.Second)
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(blackPng)
|
||
|
case "/notfound":
|
||
|
http.NotFound(w, r)
|
||
|
case "/image.png":
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(blackPng)
|
||
|
case "/weird-content":
|
||
|
w.Header().Set("Content-Type", "text/html")
|
||
|
w.Write([]byte("<html></html>"))
|
||
|
case "/giant-response":
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(make([]byte, 10485760))
|
||
|
case "/invalid.png":
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(make([]byte, 100))
|
||
|
case "/mismatched.jpg":
|
||
|
w.Header().Set("Content-Type", "image/jpeg")
|
||
|
w.Write(blackPng) // valid png, but wrong content-type
|
||
|
case "/too-wide.png":
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(imgTooWidePng)
|
||
|
case "/too-tall.png":
|
||
|
w.Header().Set("Content-Type", "image/png")
|
||
|
w.Write(imgTooTallPng)
|
||
|
default:
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
}
|
||
|
}))
|
||
|
defer server.Close()
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
url string
|
||
|
expectedSuccess bool
|
||
|
expectedLog string
|
||
|
}{
|
||
|
{
|
||
|
name: "timeout error",
|
||
|
url: "/timeout",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "error when fetching external image from",
|
||
|
},
|
||
|
{
|
||
|
name: "external fetch success",
|
||
|
url: "/image.png",
|
||
|
expectedSuccess: true,
|
||
|
expectedLog: "",
|
||
|
},
|
||
|
{
|
||
|
name: "404 fallback",
|
||
|
url: "/notfound",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "non-OK error code when fetching external image",
|
||
|
},
|
||
|
{
|
||
|
name: "unsupported content type",
|
||
|
url: "/weird-content",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "fetching external image returned unsupported Content-Type",
|
||
|
},
|
||
|
{
|
||
|
name: "response too large",
|
||
|
url: "/giant-response",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "while fetching external image response size hit MaxFileSize",
|
||
|
},
|
||
|
{
|
||
|
name: "invalid png",
|
||
|
url: "/invalid.png",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "error when decoding external image",
|
||
|
},
|
||
|
{
|
||
|
name: "mismatched content type",
|
||
|
url: "/mismatched.jpg",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "while fetching external image, mismatched image body",
|
||
|
},
|
||
|
{
|
||
|
name: "too wide",
|
||
|
url: "/too-wide.png",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "while fetching external image, width 16001 exceeds Avatar.MaxWidth",
|
||
|
},
|
||
|
{
|
||
|
name: "too tall",
|
||
|
url: "/too-tall.png",
|
||
|
expectedSuccess: false,
|
||
|
expectedLog: "while fetching external image, height 16002 exceeds Avatar.MaxHeight",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, testCase := range tests {
|
||
|
t.Run(testCase.name, func(t *testing.T) {
|
||
|
// stopMark is used as a logging boundary to verify that the expected message (testCase.expectedLog) is
|
||
|
// logged during the `fetchExternalImage` operation. This is verified by a combination of checking that the
|
||
|
// stopMark message was received, and that the filtered log (logFiltered[0]) was received.
|
||
|
stopMark := fmt.Sprintf(">>>>>>>>>>>>>STOP: %s<<<<<<<<<<<<<<<", testCase.name)
|
||
|
|
||
|
logChecker, cleanup := test.NewLogChecker(log.DEFAULT, log.TRACE)
|
||
|
logChecker.Filter(testCase.expectedLog).StopMark(stopMark)
|
||
|
defer cleanup()
|
||
|
|
||
|
card, _ := NewCard(100, 100)
|
||
|
img, ok := card.fetchExternalImage(server.URL + testCase.url)
|
||
|
|
||
|
if testCase.expectedSuccess {
|
||
|
assert.True(t, ok, "expected success from fetchExternalImage")
|
||
|
assert.NotNil(t, img)
|
||
|
} else {
|
||
|
assert.False(t, ok, "expected failure from fetchExternalImage")
|
||
|
assert.Nil(t, img)
|
||
|
}
|
||
|
|
||
|
log.Info(stopMark)
|
||
|
|
||
|
logFiltered, logStopped := logChecker.Check(5 * time.Second)
|
||
|
assert.True(t, logStopped, "failed to find log stop mark")
|
||
|
assert.True(t, logFiltered[0], "failed to find in log: '%s'", testCase.expectedLog)
|
||
|
})
|
||
|
}
|
||
|
}
|