324 lines
8.0 KiB
Go
324 lines
8.0 KiB
Go
package test
|
|
|
|
import (
|
|
imageDomain "RenewCMS/internal/domain/image"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"errors"
|
|
"io"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var mockImageEpoch = time.Date(2026, time.June, 1, 12, 0, 0, 0, time.UTC)
|
|
|
|
var mockImageExtensions = map[string]string{
|
|
"image/png": ".png",
|
|
"image/jpeg": ".jpeg",
|
|
"image/webp": ".webp",
|
|
"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
|
|
fail failSwitch
|
|
listCalls int
|
|
failDeleteFor map[uint32]error
|
|
deleteCalls []uint32
|
|
createCalls int
|
|
onDelete func(id uint32)
|
|
|
|
storedNameLookups int
|
|
}
|
|
|
|
func newMockImageRepo() *mockImageRepo {
|
|
return &mockImageRepo{images: make(map[uint32]imageDomain.Image)}
|
|
}
|
|
|
|
func (m *mockImageRepo) sortedImages() []imageDomain.Image {
|
|
result := make([]imageDomain.Image, 0, len(m.images))
|
|
for _, img := range m.images {
|
|
result = append(result, img)
|
|
}
|
|
sort.Slice(result, func(i, j int) bool { return result[i].ID < result[j].ID })
|
|
return result
|
|
}
|
|
|
|
func (m *mockImageRepo) Create(input imageDomain.Image) (imageDomain.Image, error) {
|
|
m.createCalls++
|
|
if err := m.fail.trigger("Create"); err != nil {
|
|
return imageDomain.Image{}, err
|
|
}
|
|
for m.images[m.nextID+1].ID != 0 {
|
|
m.nextID++
|
|
}
|
|
m.nextID++
|
|
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
|
|
}
|
|
img, ok := m.images[id]
|
|
if !ok {
|
|
return imageDomain.Image{}, imageDomain.ErrImageNotFound
|
|
}
|
|
return img, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) Delete(id uint32) error {
|
|
m.deleteCalls = append(m.deleteCalls, id)
|
|
if m.onDelete != nil {
|
|
m.onDelete(id)
|
|
}
|
|
if err := m.fail.trigger("Delete"); err != nil {
|
|
return err
|
|
}
|
|
if err, ok := m.failDeleteFor[id]; ok {
|
|
return err
|
|
}
|
|
if _, ok := m.images[id]; !ok {
|
|
return imageDomain.ErrImageNotFound
|
|
}
|
|
delete(m.images, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockImageRepo) List(query imageDomain.SearchQuery) ([]imageDomain.Image, error) {
|
|
m.listCalls++
|
|
if err := m.fail.trigger("List"); err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]imageDomain.Image, 0)
|
|
for _, img := range m.sortedImages() {
|
|
if query.Search != "" && !strings.Contains(strings.ToLower(img.Filename), strings.ToLower(query.Search)) {
|
|
continue
|
|
}
|
|
if query.Hash != "" && img.SHA256 != query.Hash {
|
|
continue
|
|
}
|
|
result = append(result, img)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) FindByHash(hash string) ([]imageDomain.Image, error) {
|
|
if err := m.fail.trigger("FindByHash"); err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]imageDomain.Image, 0)
|
|
for _, img := range m.sortedImages() {
|
|
if hash != "" && img.SHA256 == hash {
|
|
result = append(result, img)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) Rename(id uint32, filename string) (imageDomain.Image, error) {
|
|
if err := m.fail.trigger("Rename"); err != nil {
|
|
return imageDomain.Image{}, err
|
|
}
|
|
img, ok := m.images[id]
|
|
if !ok {
|
|
return imageDomain.Image{}, imageDomain.ErrImageNotFound
|
|
}
|
|
img.Filename = filename
|
|
m.images[id] = img
|
|
return img, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) hashCounts() map[string]int {
|
|
counts := map[string]int{}
|
|
for _, img := range m.images {
|
|
if img.SHA256 != "" {
|
|
counts[img.SHA256]++
|
|
}
|
|
}
|
|
return counts
|
|
}
|
|
|
|
func (m *mockImageRepo) ListDuplicates() ([]imageDomain.Image, error) {
|
|
if err := m.fail.trigger("ListDuplicates"); err != nil {
|
|
return nil, err
|
|
}
|
|
counts := m.hashCounts()
|
|
result := make([]imageDomain.Image, 0)
|
|
for _, img := range m.sortedImages() {
|
|
if img.SHA256 != "" && counts[img.SHA256] > 1 {
|
|
result = append(result, img)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) Counts() (imageDomain.Counts, error) {
|
|
if err := m.fail.trigger("Counts"); err != nil {
|
|
return imageDomain.Counts{}, err
|
|
}
|
|
groups := 0
|
|
for _, count := range m.hashCounts() {
|
|
if count > 1 {
|
|
groups++
|
|
}
|
|
}
|
|
return imageDomain.Counts{All: len(m.images), DuplicateGroups: groups}, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) ExistsByStoredName(storedName string) (bool, error) {
|
|
if err := m.fail.trigger("ExistsByStoredName"); err != nil {
|
|
return false, err
|
|
}
|
|
for _, img := range m.images {
|
|
if imageDomain.StoredName(img.Path) == storedName {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (m *mockImageRepo) ListByStoredNames(storedNames []string) ([]imageDomain.Image, error) {
|
|
if err := m.fail.trigger("ListByStoredNames"); err != nil {
|
|
return nil, err
|
|
}
|
|
m.storedNameLookups++
|
|
result := make([]imageDomain.Image, 0)
|
|
for _, img := range m.sortedImages() {
|
|
if slices.Contains(storedNames, imageDomain.StoredName(img.Path)) {
|
|
result = append(result, img)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
var _ imageDomain.Repository = &mockImageRepo{}
|
|
|
|
func TestImageUseCases(t *testing.T) {
|
|
repo := newMockImageRepo()
|
|
createUc := useCases.NewCreateImageUseCase(repo, newMockImageStorage(), discardRecorder)
|
|
deleteUc := useCases.NewDeleteMediaUseCase(repo, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
|
|
|
t.Run("Create Image", func(t *testing.T) {
|
|
input := imageDomain.ImageInput{
|
|
Content: strings.NewReader(string(pngBytes(t, 3, 2))),
|
|
Filename: "test.png",
|
|
ContentType: "image/png",
|
|
}
|
|
|
|
res, err := createUc.CreateImage(testCtx, input)
|
|
if err != nil {
|
|
t.Fatalf("CreateImage failed: %v", err)
|
|
}
|
|
if res.ID == 0 {
|
|
t.Errorf("Expected valid ID, got 0")
|
|
}
|
|
if res.Filename != "test.png" {
|
|
t.Errorf("Filename mismatch: %s", res.Filename)
|
|
}
|
|
})
|
|
|
|
t.Run("Delete Image", func(t *testing.T) {
|
|
repo.images[99] = imageDomain.Image{ID: 99, Path: "/static/delete-me.png"}
|
|
|
|
err := deleteUc.Delete(testCtx, 99)
|
|
if err != nil {
|
|
t.Fatalf("Delete failed: %v", err)
|
|
}
|
|
|
|
if _, exists := repo.images[99]; exists {
|
|
t.Errorf("Image 99 should have been deleted")
|
|
}
|
|
})
|
|
|
|
t.Run("Delete unknown image", func(t *testing.T) {
|
|
if err := deleteUc.Delete(testCtx, 12345); !errors.Is(err, imageDomain.ErrImageNotFound) {
|
|
t.Errorf("expected ErrImageNotFound, got %v", err)
|
|
}
|
|
})
|
|
}
|