Merge branch 'main' into fix/issue-4

This commit is contained in:
Jeffrey Paul 2026-02-09 01:31:03 +01:00
commit 39354e41c3
4 changed files with 121 additions and 16 deletions

View File

@ -0,0 +1,43 @@
package imgcache
import (
"fmt"
"math"
"testing"
)
func TestSizePercentSafeWithZeroFetchBytes(t *testing.T) {
// Simulate the calculation from processAndStore
fetchBytes := int64(0)
outputSize := int64(100)
var sizePercent float64
if fetchBytes > 0 {
sizePercent = float64(outputSize) / float64(fetchBytes) * 100.0
}
// sizePercent should be 0, not Inf or NaN
if math.IsInf(sizePercent, 0) || math.IsNaN(sizePercent) {
t.Errorf("sizePercent = %f, should not be Inf/NaN", sizePercent)
}
// Should produce valid log output
result := fmt.Sprintf("%.1f%%", sizePercent)
if result != "0.0%" {
t.Errorf("formatted size ratio = %q, want %q", result, "0.0%")
}
}
func TestSizePercentNormalCase(t *testing.T) {
fetchBytes := int64(1000)
outputSize := int64(500)
var sizePercent float64
if fetchBytes > 0 {
sizePercent = float64(outputSize) / float64(fetchBytes) * 100.0
}
if math.Abs(sizePercent-50.0) > 0.001 {
t.Errorf("sizePercent = %f, want 50.0", sizePercent)
}
}

View File

@ -153,8 +153,8 @@ func (s *Service) processFromSourceOrFetch(
}
}
// Fetch from upstream if we don't have source data
if sourceData == nil {
// Fetch from upstream if we don't have source data or it's empty
if len(sourceData) == 0 {
resp, err := s.fetchAndProcess(ctx, req, cacheKey)
if err != nil {
return nil, err
@ -264,7 +264,11 @@ func (s *Service) processAndStore(
// Log conversion details
outputSize := int64(len(processedData))
sizePercent := float64(outputSize) / float64(fetchBytes) * 100.0 //nolint:mnd // percentage calculation
var sizePercent float64
if fetchBytes > 0 {
sizePercent = float64(outputSize) / float64(fetchBytes) * 100.0 //nolint:mnd // percentage calculation
}
s.log.Info("image converted",
"host", req.SourceHost,

View File

@ -6,6 +6,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"strconv"
"time"
)
@ -96,22 +97,24 @@ func (s *Signer) GenerateSignedURL(req *ImageRequest, ttl time.Duration) (path s
sizeStr = fmt.Sprintf("%dx%d", req.Size.Width, req.Size.Height)
}
// Build the path
path = fmt.Sprintf("/v1/image/%s%s/%s.%s",
req.SourceHost,
req.SourcePath,
sizeStr,
req.Format,
)
// Append query if present
// Build the path.
// When a source query is present, it is embedded as a path segment
// (e.g. /host/path?query/size.fmt) so that ParseImagePath can extract
// it from the last-slash split. The "?" inside a path segment is
// percent-encoded by clients but chi delivers it decoded, which is
// exactly what the URL parser expects.
if req.SourceQuery != "" {
// The query goes before the size segment in our URL scheme
// So we need to rebuild the path
path = fmt.Sprintf("/v1/image/%s%s?%s/%s.%s",
path = fmt.Sprintf("/v1/image/%s%s%%3F%s/%s.%s",
req.SourceHost,
req.SourcePath,
url.PathEscape(req.SourceQuery),
sizeStr,
req.Format,
)
} else {
path = fmt.Sprintf("/v1/image/%s%s/%s.%s",
req.SourceHost,
req.SourcePath,
req.SourceQuery,
sizeStr,
req.Format,
)

View File

@ -0,0 +1,55 @@
package imgcache
import (
"strings"
"testing"
"time"
)
func TestGenerateSignedURL_WithQueryString(t *testing.T) {
signer := NewSigner("test-secret-key-for-testing!")
req := &ImageRequest{
SourceHost: "cdn.example.com",
SourcePath: "/photos/cat.jpg",
SourceQuery: "token=abc&v=2",
Size: Size{Width: 800, Height: 600},
Format: FormatWebP,
}
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
// The path must NOT contain a bare "?" that would be interpreted as a query string delimiter.
// The size segment must appear as the last path component.
if strings.Contains(path, "?token=abc") {
t.Errorf("GenerateSignedURL() produced bare query string in path: %q", path)
}
// The size segment must be present in the path
if !strings.Contains(path, "/800x600.webp") {
t.Errorf("GenerateSignedURL() missing size segment in path: %q", path)
}
// Path should end with the size.format, not with query params
if !strings.HasSuffix(path, "/800x600.webp") {
t.Errorf("GenerateSignedURL() path should end with size.format: %q", path)
}
}
func TestGenerateSignedURL_WithoutQueryString(t *testing.T) {
signer := NewSigner("test-secret-key-for-testing!")
req := &ImageRequest{
SourceHost: "cdn.example.com",
SourcePath: "/photos/cat.jpg",
Size: Size{Width: 800, Height: 600},
Format: FormatWebP,
}
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
expected := "/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp"
if path != expected {
t.Errorf("GenerateSignedURL() path = %q, want %q", path, expected)
}
}