veza/veza-backend-api/internal/services/image_service_test.go
senke a6cf20e614 fix(tests): fix 2 skipped tests, add clear skip reasons to 11 others
INT-04: Fixed nil UserID panic in AuditService (re-enabled 2 tests).
Added INT-04 comments explaining skip reasons for tests requiring
PostgreSQL, real file headers, or external services.
2026-02-22 17:53:00 +01:00

288 lines
7.4 KiB
Go

package services
import (
"bytes"
"image"
"image/color"
"image/jpeg"
"mime/multipart"
"os"
"path/filepath"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupTestImageService(t *testing.T) (*ImageService, string, func()) {
tempDir := t.TempDir()
service := NewImageService(tempDir)
cleanup := func() {
os.RemoveAll(tempDir)
}
return service, tempDir, cleanup
}
func createTestImage(width, height int) image.Image {
img := image.NewRGBA(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
img.Set(x, y, color.RGBA{uint8(x % 256), uint8(y % 256), 128, 255})
}
}
return img
}
func TestImageService_NewImageService(t *testing.T) {
service := NewImageService("/test/dir")
assert.NotNil(t, service)
assert.Equal(t, "/test/dir", service.uploadDir)
}
func TestImageService_NewImageService_DefaultDir(t *testing.T) {
service := NewImageService("")
assert.NotNil(t, service)
assert.Equal(t, "uploads/avatars", service.uploadDir)
}
func TestImageService_ValidateImage_Success(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
header := make(map[string][]string)
header["Content-Type"] = []string{"image/jpeg"}
fileHeader := &multipart.FileHeader{
Filename: "test.jpg",
Size: 1024 * 1024,
Header: header,
}
err := service.ValidateImage(fileHeader)
assert.NoError(t, err)
}
func TestImageService_ValidateImage_PNG(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
header := make(map[string][]string)
header["Content-Type"] = []string{"image/png"}
fileHeader := &multipart.FileHeader{
Filename: "test.png",
Size: 1024 * 1024,
Header: header,
}
err := service.ValidateImage(fileHeader)
assert.NoError(t, err)
}
func TestImageService_ValidateImage_WebP(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
header := make(map[string][]string)
header["Content-Type"] = []string{"image/webp"}
fileHeader := &multipart.FileHeader{
Filename: "test.webp",
Size: 1024 * 1024,
Header: header,
}
err := service.ValidateImage(fileHeader)
assert.NoError(t, err)
}
func TestImageService_ValidateImage_TooLarge(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
header := make(map[string][]string)
header["Content-Type"] = []string{"image/jpeg"}
fileHeader := &multipart.FileHeader{
Filename: "test.jpg",
Size: 6 * 1024 * 1024,
Header: header,
}
err := service.ValidateImage(fileHeader)
assert.Error(t, err)
assert.Contains(t, err.Error(), "5MB limit")
}
func TestImageService_ValidateImage_InvalidFormat(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
header := make(map[string][]string)
header["Content-Type"] = []string{"image/gif"}
fileHeader := &multipart.FileHeader{
Filename: "test.gif",
Size: 1024 * 1024,
Header: header,
}
err := service.ValidateImage(fileHeader)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported image format")
}
func TestImageService_ResizeImage_Square(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
img := createTestImage(400, 400)
resized := service.ResizeImage(img, 200, 200)
bounds := resized.Bounds()
assert.Equal(t, 200, bounds.Dx())
assert.Equal(t, 200, bounds.Dy())
}
func TestImageService_ResizeImage_Wide(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
img := createTestImage(800, 400) // 2:1 ratio
resized := service.ResizeImage(img, 200, 200) // Target 1:1
bounds := resized.Bounds()
assert.Equal(t, 200, bounds.Dx())
assert.Equal(t, 200, bounds.Dy())
}
func TestImageService_ResizeImage_Tall(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
img := createTestImage(400, 800) // 1:2 ratio
resized := service.ResizeImage(img, 200, 200) // Target 1:1
bounds := resized.Bounds()
assert.Equal(t, 200, bounds.Dx())
assert.Equal(t, 200, bounds.Dy())
}
func TestImageService_EncodeJPEG_Success(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
img := createTestImage(200, 200)
data, err := service.EncodeJPEG(img)
_ = service // Use service
assert.NoError(t, err)
assert.NotNil(t, data)
assert.Greater(t, len(data), 0)
// Verify it's valid JPEG
_, err = jpeg.Decode(bytes.NewReader(data))
assert.NoError(t, err)
}
func TestImageService_ProcessAvatar_Success(t *testing.T) {
// INT-04: Skip - requires real multipart.FileHeader with Open() method; complex to mock.
// Functionality tested indirectly via ValidateImage, ResizeImage, EncodeJPEG.
t.Skip("requires real multipart.FileHeader with Open() method")
}
func TestImageService_ProcessAvatar_InvalidFormat(t *testing.T) {
// INT-04: Skip - same as ProcessAvatar_Success; multipart.FileHeader hard to mock.
t.Skip("requires custom multipart.FileHeader wrapper")
}
func TestImageService_ProcessAvatar_TooLarge(t *testing.T) {
// INT-04: Skip - same as ProcessAvatar_Success; multipart.FileHeader hard to mock.
t.Skip("requires custom multipart.FileHeader wrapper")
}
func TestImageService_UploadToS3_Success(t *testing.T) {
service, tempDir, cleanup := setupTestImageService(t)
defer cleanup()
data := []byte("test image data")
key := "test-key.jpg"
url, err := service.UploadToS3(data, key)
assert.NoError(t, err)
assert.Contains(t, url, "test-key.jpg")
// Verify file was created
filePath := filepath.Join(tempDir, "test-key.jpg")
_, err = os.Stat(filePath)
assert.NoError(t, err)
// Verify file content
fileData, err := os.ReadFile(filePath)
assert.NoError(t, err)
assert.Equal(t, data, fileData)
}
func TestImageService_UploadToS3_CreatesDirectory(t *testing.T) {
service, tempDir, cleanup := setupTestImageService(t)
defer cleanup()
// Use a subdirectory that doesn't exist
service.uploadDir = filepath.Join(tempDir, "new", "subdir")
data := []byte("test image data")
key := "test-key.jpg"
_, err := service.UploadToS3(data, key)
assert.NoError(t, err)
// Verify directory was created
_, err = os.Stat(service.uploadDir)
assert.NoError(t, err)
}
func TestImageService_DeleteFromS3_Success(t *testing.T) {
service, tempDir, cleanup := setupTestImageService(t)
defer cleanup()
// Create a file first
key := "test-key.jpg"
data := []byte("test image data")
_, err := service.UploadToS3(data, key)
require.NoError(t, err)
// Delete it
url := "/uploads/avatars/test-key.jpg"
err = service.DeleteFromS3(url)
assert.NoError(t, err)
// Verify file was deleted
filePath := filepath.Join(tempDir, "test-key.jpg")
_, err = os.Stat(filePath)
assert.Error(t, err)
assert.True(t, os.IsNotExist(err))
}
func TestImageService_DeleteFromS3_NotExists(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
url := "/uploads/avatars/non-existent.jpg"
err := service.DeleteFromS3(url)
assert.NoError(t, err) // Should not error if file doesn't exist
}
func TestImageService_GenerateS3Key(t *testing.T) {
service, _, cleanup := setupTestImageService(t)
defer cleanup()
userID := uuid.New()
key := service.GenerateS3Key(userID)
assert.Contains(t, key, "avatars")
assert.Contains(t, key, userID.String())
assert.Contains(t, key, ".jpg")
}
func TestImageService_Constants(t *testing.T) {
assert.Equal(t, 5*1024*1024, MaxAvatarSize)
assert.Equal(t, 200, AvatarWidth)
assert.Equal(t, 200, AvatarHeight)
assert.Equal(t, 90, JPEGQuality)
}