refac: decouple persistence and image storage behind driver registries

This commit is contained in:
fsylvain committed 2026-10-06 02:13:35 +02:00
1 parent 421cfafec3
commit 81e299f09a
46 files changed
+1036 -374

No files matched your search

+82 -19
View File
@@ -21,6 +21,55 @@ var mockImageExtensions = map[string]string{
"image/svg+xml": ".svg",
}
type mockImageStorage struct {
blobs map[string][]byte
putCalls int
nextName int
lastContent []byte
deleted []string
failPut error
failDelete error
}
func newMockImageStorage() *mockImageStorage {
return &mockImageStorage{blobs: map[string][]byte{}}
}
func (m *mockImageStorage) Put(contentType string, content io.Reader) (string, error) {
m.putCalls++
if m.failPut != nil {
return "", m.failPut
}
data, err := io.ReadAll(content)
if err != nil {
return "", err
}
m.nextName++
name := "stored-" + strconv.Itoa(m.nextName) + mockImageExtensions[contentType]
m.blobs[name] = data
m.lastContent = data
return name, nil
}
func (m *mockImageStorage) Open(name string) (imageDomain.Blob, error) {
data, ok := m.blobs[name]
if !ok {
return imageDomain.Blob{}, imageDomain.ErrBlobNotFound
}
return imageDomain.Blob{Content: io.NopCloser(strings.NewReader(string(data))), Size: int64(len(data))}, nil
}
func (m *mockImageStorage) Delete(name string) error {
m.deleted = append(m.deleted, name)
if m.failDelete != nil {
return m.failDelete
}
delete(m.blobs, name)
return nil
}
var _ imageDomain.Storage = &mockImageStorage{}
type mockImageRepo struct {
images map[uint32]imageDomain.Image
nextID uint32
@@ -28,7 +77,6 @@ type mockImageRepo struct {
failDeleteFor map[uint32]error
deleteCalls []uint32
createCalls int
lastContent []byte
onDelete func(id uint32)
}
@@ -45,34 +93,49 @@ func (m *mockImageRepo) sortedImages() []imageDomain.Image {
return result
}
func (m *mockImageRepo) Create(input imageDomain.ImageInput) (imageDomain.Image, error) {
func (m *mockImageRepo) Create(input imageDomain.Image) (imageDomain.Image, error) {
m.createCalls++
if err := m.fail.trigger("Create"); err != nil {
return imageDomain.Image{}, err
}
if input.Content != nil {
m.lastContent, _ = io.ReadAll(input.Content)
}
for m.images[m.nextID+1].ID != 0 {
m.nextID++
}
m.nextID++
img := imageDomain.Image{
ID: m.nextID,
Path: "/static/uploadedImages/" + strconv.Itoa(int(m.nextID)) + mockImageExtensions[input.ContentType],
Filename: input.Filename,
Size: input.Size,
ContentType: input.ContentType,
Width: input.Width,
Height: input.Height,
SHA256: input.SHA256,
UploadedByID: input.UploadedByID,
CreatedAt: mockImageEpoch.Add(time.Duration(m.nextID) * time.Minute),
}
img := input
img.ID = m.nextID
img.CreatedAt = mockImageEpoch.Add(time.Duration(m.nextID) * time.Minute)
m.images[img.ID] = img
return img, nil
}
func (m *mockImageRepo) ListMissingMetadata() ([]imageDomain.Image, error) {
if err := m.fail.trigger("ListMissingMetadata"); err != nil {
return nil, err
}
pending := []imageDomain.Image{}
for _, img := range m.sortedImages() {
if img.Filename == "" {
pending = append(pending, img)
}
}
return pending, nil
}
func (m *mockImageRepo) UpdateMetadata(id uint32, metadata imageDomain.Metadata) error {
if err := m.fail.trigger("UpdateMetadata"); err != nil {
return err
}
img, ok := m.images[id]
if !ok {
return imageDomain.ErrImageNotFound
}
img.Filename, img.Size, img.ContentType = metadata.Filename, metadata.Size, metadata.ContentType
img.Width, img.Height, img.SHA256 = metadata.Width, metadata.Height, metadata.SHA256
m.images[id] = img
return nil
}
func (m *mockImageRepo) Get(id uint32) (imageDomain.Image, error) {
if err := m.fail.trigger("Get"); err != nil {
return imageDomain.Image{}, err
@@ -186,8 +249,8 @@ var _ imageDomain.Repository = &mockImageRepo{}
func TestImageUseCases(t *testing.T) {
repo := newMockImageRepo()
createUc := useCases.NewCreateImageUseCase(repo)
deleteUc := useCases.NewDeleteMediaUseCase(repo)
createUc := useCases.NewCreateImageUseCase(repo, newMockImageStorage())
deleteUc := useCases.NewDeleteMediaUseCase(repo, newMockImageStorage())
t.Run("Create Image", func(t *testing.T) {
input := imageDomain.ImageInput{