feat: add support for S3 storage backend (#1080)

Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
Elias Schneider
2025-11-10 10:02:25 +01:00
committed by GitHub
parent d5e0cfd4a6
commit bfd71d090c
28 changed files with 1084 additions and 616 deletions

View File

@@ -2,66 +2,92 @@ package service
import (
"bytes"
"context"
"io"
"io/fs"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/storage"
)
func TestAppImagesService_GetImage(t *testing.T) {
tempDir := t.TempDir()
originalUploadPath := common.EnvConfig.UploadPath
common.EnvConfig.UploadPath = tempDir
t.Cleanup(func() {
common.EnvConfig.UploadPath = originalUploadPath
})
imagesDir := filepath.Join(tempDir, "application-images")
require.NoError(t, os.MkdirAll(imagesDir, 0o755))
filePath := filepath.Join(imagesDir, "background.webp")
require.NoError(t, os.WriteFile(filePath, []byte("data"), fs.FileMode(0o644)))
service := NewAppImagesService(map[string]string{"background": "webp"})
path, mimeType, err := service.GetImage("background")
store, err := storage.NewFilesystemStorage(t.TempDir())
require.NoError(t, err)
require.Equal(t, filePath, path)
require.NoError(t, store.Save(context.Background(), path.Join("application-images", "background.webp"), bytes.NewReader([]byte("data"))))
service := NewAppImagesService(map[string]string{"background": "webp"}, store)
reader, size, mimeType, err := service.GetImage(context.Background(), "background")
require.NoError(t, err)
defer reader.Close()
payload, err := io.ReadAll(reader)
require.NoError(t, err)
require.Equal(t, []byte("data"), payload)
require.Equal(t, int64(len(payload)), size)
require.Equal(t, "image/webp", mimeType)
}
func TestAppImagesService_UpdateImage(t *testing.T) {
tempDir := t.TempDir()
originalUploadPath := common.EnvConfig.UploadPath
common.EnvConfig.UploadPath = tempDir
t.Cleanup(func() {
common.EnvConfig.UploadPath = originalUploadPath
})
store, err := storage.NewFilesystemStorage(t.TempDir())
require.NoError(t, err)
imagesDir := filepath.Join(tempDir, "application-images")
require.NoError(t, os.MkdirAll(imagesDir, 0o755))
require.NoError(t, store.Save(context.Background(), path.Join("application-images", "logoLight.svg"), bytes.NewReader([]byte("old"))))
oldPath := filepath.Join(imagesDir, "logoLight.svg")
require.NoError(t, os.WriteFile(oldPath, []byte("old"), fs.FileMode(0o644)))
service := NewAppImagesService(map[string]string{"logoLight": "svg"})
service := NewAppImagesService(map[string]string{"logoLight": "svg"}, store)
fileHeader := newFileHeader(t, "logoLight.png", []byte("new"))
require.NoError(t, service.UpdateImage(fileHeader, "logoLight"))
require.NoError(t, service.UpdateImage(context.Background(), fileHeader, "logoLight"))
_, err := os.Stat(filepath.Join(imagesDir, "logoLight.png"))
reader, _, err := store.Open(context.Background(), path.Join("application-images", "logoLight.png"))
require.NoError(t, err)
_ = reader.Close()
_, _, err = store.Open(context.Background(), path.Join("application-images", "logoLight.svg"))
require.ErrorIs(t, err, fs.ErrNotExist)
}
func TestAppImagesService_ErrorsAndFlags(t *testing.T) {
store, err := storage.NewFilesystemStorage(t.TempDir())
require.NoError(t, err)
_, err = os.Stat(oldPath)
require.ErrorIs(t, err, os.ErrNotExist)
service := NewAppImagesService(map[string]string{}, store)
t.Run("get missing image returns not found", func(t *testing.T) {
_, _, _, err := service.GetImage(context.Background(), "missing")
require.Error(t, err)
var imageErr *common.ImageNotFoundError
assert.ErrorAs(t, err, &imageErr)
})
t.Run("reject unsupported file types", func(t *testing.T) {
err := service.UpdateImage(context.Background(), newFileHeader(t, "logo.txt", []byte("nope")), "logo")
require.Error(t, err)
var fileTypeErr *common.FileTypeNotSupportedError
assert.ErrorAs(t, err, &fileTypeErr)
})
t.Run("delete and extension tracking", func(t *testing.T) {
require.NoError(t, store.Save(context.Background(), path.Join("application-images", "default-profile-picture.png"), bytes.NewReader([]byte("img"))))
service.extensions["default-profile-picture"] = "png"
require.NoError(t, service.DeleteImage(context.Background(), "default-profile-picture"))
assert.False(t, service.IsDefaultProfilePictureSet())
err := service.DeleteImage(context.Background(), "default-profile-picture")
require.Error(t, err)
var imageErr *common.ImageNotFoundError
assert.ErrorAs(t, err, &imageErr)
})
}
func newFileHeader(t *testing.T, filename string, content []byte) *multipart.FileHeader {