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) { t.Skip("Skipping ProcessAvatar test - requires real multipart.FileHeader with Open() method") // This test would require creating a real multipart.FileHeader with a working Open() method // which is complex to mock. The functionality is tested indirectly through ValidateImage, // ResizeImage, and EncodeJPEG tests. } func TestImageService_ProcessAvatar_InvalidFormat(t *testing.T) { t.Skip("Skipping ProcessAvatar test - requires custom multipart.FileHeader wrapper") } func TestImageService_ProcessAvatar_TooLarge(t *testing.T) { t.Skip("Skipping ProcessAvatar test - 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) }