feat(API): media field type
This commit is contained in:
1 parent
ad5faf3d19
commit
a2d596f4ca
63 files changed
+2567
-259
No files matched your search
@@ -383,7 +383,7 @@ func TestActivityLogProducersScheduledPurge(t *testing.T) {
|
||||
images := &producerBlockingImageRepo{mockImageRepo: newMockImageRepo(), entered: make(chan struct{}, 2), release: make(chan struct{})}
|
||||
articles := newMockArticleRepo()
|
||||
storage := newMockImageStorage()
|
||||
merge := useCases.NewMergeMediaUseCase(images, articles, storage, newMockVariantFileRepo(), h.recorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(images, articles, storage, newMockVariantFileRepo(), h.recorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
run := useCases.NewRunPurgeUseCase(images, articles, merge, newMockPurgeSettingsRepo(), func() time.Time { return now }, log.New(io.Discard, "", 0), h.recorder, plainSites(), plainAuthors())
|
||||
|
||||
finished := make(chan struct{})
|
||||
|
||||
@@ -100,7 +100,7 @@ func newAPIKeyEnvRecording(t *testing.T, recorder audit.Recorder) *apiKeyEnv {
|
||||
mail := newMockMailRepo()
|
||||
env.mail = mail
|
||||
renderer := &fakeBodyRenderer{}
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(env.images, env.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, plainSites(), plainAuthors())
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(env.images, env.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
purgeSettings := newMockPurgeSettingsRepo()
|
||||
restartJobs := &fakeJobStore{}
|
||||
env.container = &api.UseCases{
|
||||
@@ -127,13 +127,13 @@ func newAPIKeyEnvRecording(t *testing.T, recorder audit.Recorder) *apiKeyEnv {
|
||||
AcceptInviteUseCase: useCases.NewAcceptInviteUseCase(env.users, env.invites, hasher, recorder),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(env.images, newMockImageStorage(), recorder),
|
||||
GenerateVariantsOnUploadUseCase: newInertGenerateOnUploadUseCase(),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(env.images, env.articles, env.users, newMockVariantFileRepo(), plainSites(), plainAuthors()),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(env.images, env.articles, env.users, newMockVariantFileRepo(), plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(env.images, recorder),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(env.images, newMockImageStorage(), newMockVariantFileRepo(), recorder, plainSites(), plainAuthors(), env.articles),
|
||||
MergeMediaUseCase: mergeMedia,
|
||||
RunPurgeUseCase: useCases.NewRunPurgeUseCase(env.images, env.articles, mergeMedia, purgeSettings, clock, log.New(io.Discard, "", 0), recorder, plainSites(), plainAuthors()),
|
||||
RequestRestartUseCase: useCases.NewRequestRestartUseCase(env.updates, restartJobs, &fakeHelperDetector{installed: true}, &fakeResultStore{}, &fakeRestartRequestStore{}, apiKeyRunningVersion, clock, recorder),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(env.images, env.articles, plainSites(), plainAuthors()),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(env.images, env.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
GetSettingsUseCase: useCases.NewGetSettingsUseCase(apiKeyRunningVersion),
|
||||
GetActivityRetentionUseCase: useCases.NewGetActivityRetentionUseCase(newActivitySettingsFake(), newSizeEvents(t), time.Now),
|
||||
GetUpdateStatusUseCase: newUpdateStatusUseCase(env.updates, &fakeJobStore{}, &fakeHelperDetector{}, apiKeyRunningVersion),
|
||||
|
||||
@@ -355,14 +355,14 @@ func newAuthorEnv(t *testing.T) *authorEnv {
|
||||
sites := plainSites()
|
||||
recorder := p.audit.recorder
|
||||
renderer := &fakeBodyRenderer{}
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors)
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c := p.container
|
||||
c.CreateArticleUseCase = useCases.NewCreateArticleUseCase(p.articles, renderer, recorder, sites, authors)
|
||||
c.UpdateArticleUseCase = useCases.NewUpdateArticleUseCase(p.articles, renderer, recorder, sites, authors, p.images)
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, authors)
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.DeleteMediaUseCase = useCases.NewDeleteMediaUseCase(p.images, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors, p.articles)
|
||||
c.MergeMediaUseCase = mergeMedia
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, authors)
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.RunPurgeUseCase = useCases.NewRunPurgeUseCase(p.images, p.articles, mergeMedia, newMockPurgeSettingsRepo(), func() time.Time { return p.now }, log.New(io.Discard, "", 0), recorder, sites, authors)
|
||||
c.ListActivityUseCase = useCases.NewListActivityUseCase(p.audit.store, p.users, p.articles, p.images, p.keys, p.audit.clock, sites, authors)
|
||||
c.ListAuthorsUseCase = useCases.NewListAuthorsUseCase(authors, p.users)
|
||||
|
||||
@@ -424,7 +424,7 @@ func TestBuiltinTypeHTTP(t *testing.T) {
|
||||
ListBuiltinTypesUseCase: useCases.NewListBuiltinTypesUseCase(),
|
||||
GetBuiltinTypeUseCase: useCases.NewGetBuiltinTypeUseCase(),
|
||||
CreateContentTypeUseCase: useCases.NewCreateContentTypeUseCase(panickingContentTypeRepo{}, now),
|
||||
CreateEntryUseCase: useCases.NewCreateEntryUseCase(panickingContentTypeRepo{}, panickingEntryRepo{}, now),
|
||||
CreateEntryUseCase: useCases.NewCreateEntryUseCase(panickingContentTypeRepo{}, panickingEntryRepo{}, newMockImageRepo(), now),
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(container, config.Settings{}))
|
||||
|
||||
@@ -213,6 +213,8 @@ func (m *mockEntryRepo) FindByFieldValue(contentTypeID uint32, fieldKey string,
|
||||
type contentTypeEnv struct {
|
||||
types *mockContentTypeRepo
|
||||
entries *mockEntryRepo
|
||||
images *mockImageRepo
|
||||
container *api.UseCases
|
||||
users *mockUserRepo
|
||||
keys *mockAPIKeyRepo
|
||||
generator *hashing.SHA256KeyGenerator
|
||||
@@ -228,6 +230,7 @@ func newContentTypeEnv(t *testing.T) *contentTypeEnv {
|
||||
env := &contentTypeEnv{
|
||||
types: newMockContentTypeRepo(),
|
||||
entries: entries,
|
||||
images: newMockImageRepo(),
|
||||
users: newMockUserRepo(),
|
||||
keys: newMockAPIKeyRepo(),
|
||||
generator: hashing.NewSHA256KeyGenerator(),
|
||||
@@ -253,18 +256,23 @@ func newContentTypeEnv(t *testing.T) *contentTypeEnv {
|
||||
ListBuiltinTypesUseCase: useCases.NewListBuiltinTypesUseCase(),
|
||||
GetBuiltinTypeUseCase: useCases.NewGetBuiltinTypeUseCase(),
|
||||
LookupUserNamesUseCase: useCases.NewLookupUserNamesUseCase(env.users),
|
||||
CreateEntryUseCase: useCases.NewCreateEntryUseCase(env.types, env.entries, now),
|
||||
GetEntryUseCase: useCases.NewGetEntryUseCase(env.types, env.entries),
|
||||
ListEntriesUseCase: useCases.NewListEntriesUseCase(env.types, env.entries),
|
||||
UpdateEntryUseCase: useCases.NewUpdateEntryUseCase(env.types, env.entries, now),
|
||||
CreateEntryUseCase: useCases.NewCreateEntryUseCase(env.types, env.entries, env.images, now),
|
||||
GetEntryUseCase: useCases.NewGetEntryUseCase(env.types, env.entries, env.images),
|
||||
ListEntriesUseCase: useCases.NewListEntriesUseCase(env.types, env.entries, env.images),
|
||||
UpdateEntryUseCase: useCases.NewUpdateEntryUseCase(env.types, env.entries, env.images, now),
|
||||
DeleteEntryUseCase: useCases.NewDeleteEntryUseCase(env.types, env.entries),
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(container, config.Settings{}))
|
||||
env.router = router
|
||||
env.container = container
|
||||
env.mountRouter()
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *contentTypeEnv) mountRouter() {
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(e.container, config.Settings{}))
|
||||
e.router = router
|
||||
}
|
||||
|
||||
func (e *contentTypeEnv) request(t *testing.T, method string, path string, body string, mutate func(*http.Request)) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(method, path, strings.NewReader(body))
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
var (
|
||||
contentTypeKeys = []string{"created_at", "fields", "icon", "id", "key", "name", "updated_at"}
|
||||
fieldKeys = []string{"default", "key", "label", "max", "min", "options", "required", "type", "unique"}
|
||||
entryKeys = []string{"content_type", "created_at", "created_by", "data", "id", "updated_at", "updated_by"}
|
||||
entryKeys = []string{"content_type", "created_at", "created_by", "data", "id", "media", "updated_at", "updated_by"}
|
||||
)
|
||||
|
||||
func TestCreatingAContentType(t *testing.T) {
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestCreateEntryUseCase(t *testing.T) {
|
||||
}
|
||||
for _, data := range breaking {
|
||||
fixture, _ := newContentTypeFixture(t)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.now)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, newMockImageRepo(), fixture.now)
|
||||
|
||||
_, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "recipe", Data: data, UserID: 2})
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestCreateEntryUseCase(t *testing.T) {
|
||||
|
||||
t.Run("Use case: a duplicate unique value returns ErrUniqueViolation", func(t *testing.T) {
|
||||
fixture, _ := newContentTypeFixture(t)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.now)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, newMockImageRepo(), fixture.now)
|
||||
data := func() map[string]any { return map[string]any{"title": "Pad thai", "difficulty": "easy"} }
|
||||
if _, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "recipe", Data: data(), UserID: 2}); err != nil {
|
||||
t.Fatalf("first create: %v", err)
|
||||
@@ -91,7 +91,7 @@ func TestCreateEntryUseCase(t *testing.T) {
|
||||
|
||||
t.Run("Use case: the injected clock stamps the entry", func(t *testing.T) {
|
||||
fixture, _ := newContentTypeFixture(t)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.now)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, newMockImageRepo(), fixture.now)
|
||||
|
||||
created, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "recipe", Data: map[string]any{"title": "Pad thai", "difficulty": "easy"}, UserID: 2})
|
||||
|
||||
@@ -551,7 +551,7 @@ func TestEntrySortRules(t *testing.T) {
|
||||
func TestEntryPagingAndEquality(t *testing.T) {
|
||||
t.Run("Pages beyond the maximum are refused by the use case", func(t *testing.T) {
|
||||
fixture, _ := newContentTypeFixture(t)
|
||||
list := useCases.NewListEntriesUseCase(fixture.types, fixture.entries)
|
||||
list := useCases.NewListEntriesUseCase(fixture.types, fixture.entries, newMockImageRepo())
|
||||
cases := []struct {
|
||||
page, size int
|
||||
sort entry.Sort
|
||||
|
||||
@@ -115,7 +115,7 @@ func (e *variantEnv) container(limits useCases.ServeVariantLimits) *api.UseCases
|
||||
GetMyAuthorUseCase: useCases.NewGetMyAuthorUseCase(plainAuthors()),
|
||||
OpenMediaUseCase: useCases.NewOpenMediaUseCase(e.storage),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(e.images, e.storage, e.files, recorder, plainSites(), plainAuthors(), e.articles),
|
||||
MergeMediaUseCase: useCases.NewMergeMediaUseCase(e.images, e.articles, e.storage, e.files, recorder, plainSites(), plainAuthors()),
|
||||
MergeMediaUseCase: useCases.NewMergeMediaUseCase(e.images, e.articles, e.storage, e.files, recorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
ListVariantsUseCase: useCases.NewListVariantsUseCase(e.variants, e.files),
|
||||
CreateVariantUseCase: useCases.NewCreateVariantUseCase(e.variants, e.renderer, clock, recorder),
|
||||
UpdateVariantUseCase: useCases.NewUpdateVariantUseCase(e.variants, e.files, e.storage, e.renderer, clock, recorder),
|
||||
@@ -129,9 +129,9 @@ func (e *variantEnv) container(limits useCases.ServeVariantLimits) *api.UseCases
|
||||
VariantUsageTracker: e.tracker,
|
||||
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(e.images, e.storage, recorder),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(e.images, e.articles, e.users, e.files, plainSites(), plainAuthors()),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(e.images, e.articles, e.users, e.files, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(e.images, recorder),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(e.images, e.articles, plainSites(), plainAuthors()),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(e.images, e.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
|
||||
ListImageVariantsUseCase: useCases.NewListImageVariantsUseCase(e.images, e.variants, e.files, e.renderer),
|
||||
GenerateImageVariantUseCase: useCases.NewGenerateImageVariantUseCase(e.images, e.variants, e.files, e.renderer, serve),
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestMediaMergeRewritesArticlesInIdOrder(t *testing.T) {
|
||||
env.seedArticle(id, "Article", true, env.urlOf(3))
|
||||
}
|
||||
recorder := &mediaOrderRecordingArticleRepo{mockArticleRepo: env.articles}
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, recorder, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, recorder, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
if _, err := uc.Merge(testCtx, 2, []uint32{3}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
|
||||
@@ -62,7 +62,7 @@ func newMediaEnv(t *testing.T) *mediaEnv {
|
||||
env.users.users[mediaUnverifiedUserId] = user.User{ID: 3, Username: "pending", IsVerified: false}
|
||||
env.users.users[mediaStaleUserId] = user.User{ID: 4, Username: "stale", IsVerified: true, TokenVersion: 1}
|
||||
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
now := func() time.Time { return env.clock }
|
||||
container := &api.UseCases{
|
||||
Token: env.token,
|
||||
@@ -75,14 +75,14 @@ func newMediaEnv(t *testing.T) *mediaEnv {
|
||||
GetMyAuthorUseCase: useCases.NewGetMyAuthorUseCase(plainAuthors()),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(env.images, env.storage, discardRecorder),
|
||||
GenerateVariantsOnUploadUseCase: newInertGenerateOnUploadUseCase(),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(env.images, env.articles, env.users, env.variants, plainSites(), plainAuthors()),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(env.images, env.articles, env.users, env.variants, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(env.images, discardRecorder),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(env.images, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), env.articles),
|
||||
MergeMediaUseCase: merge,
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(env.images, env.articles, plainSites(), plainAuthors()),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(env.images, env.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
GetPurgeSettingsUseCase: useCases.NewGetPurgeSettingsUseCase(env.purgeSettings),
|
||||
UpdatePurgeSettingsUseCase: useCases.NewUpdatePurgeSettingsUseCase(env.purgeSettings, now),
|
||||
PreviewPurgeUseCase: useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()),
|
||||
PreviewPurgeUseCase: useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
RunPurgeUseCase: useCases.NewRunPurgeUseCase(env.images, env.articles, merge, env.purgeSettings, now, log.New(env.logs, "", 0), discardRecorder, plainSites(), plainAuthors()),
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
|
||||
@@ -0,0 +1,632 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
"RenewCMS/internal/domain/entry"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func limeMedia() map[string]any {
|
||||
return map[string]any{"id": float64(mfLimeID), "filename": "lime-plaster.jpg", "width": float64(1600), "height": float64(900), "size": float64(240000), "thumbnail_url": mfImagePath}
|
||||
}
|
||||
|
||||
func missingMedia(id int) map[string]any {
|
||||
return map[string]any{"id": float64(id), "missing": true}
|
||||
}
|
||||
|
||||
func entryPath(id uint32) string {
|
||||
return "/v1/content-type/event/entry/" + strconv.Itoa(int(id))
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) storedFieldsJSON(t *testing.T, key string) string {
|
||||
t.Helper()
|
||||
stored, err := e.types.GetByKey(key)
|
||||
if err != nil {
|
||||
t.Fatalf("stored type %s: %v", key, err)
|
||||
}
|
||||
return jsonOf(t, stored.Fields)
|
||||
}
|
||||
|
||||
func TestMediaFieldTypeContentTypes(t *testing.T) {
|
||||
t.Run("Creating a content type with a media field", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
|
||||
rec := env.admin(t, http.MethodPost, "/v1/content-type", typeBody("event", "Events", eventFields(false)))
|
||||
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("status %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
fields := decodeContentObject(t, rec)["fields"].([]any)
|
||||
thumbnail := fields[1].(map[string]any)
|
||||
if thumbnail["type"] != "media" || thumbnail["unique"] != false || thumbnail["default"] != nil || thumbnail["min"] != nil || thumbnail["max"] != nil || !reflect.DeepEqual(thumbnail["options"], []any{}) {
|
||||
t.Errorf("unexpected field %v", thumbnail)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A media field refuses options it can't have", func(t *testing.T) {
|
||||
options := map[string]map[string]any{"unique true": map[string]any{"unique": true}, "default 7": map[string]any{"default": 7}, "default lime": map[string]any{"default": "lime"}, "min 1": map[string]any{"min": 1}, "max 5": map[string]any{"max": 5}, "options a": map[string]any{"options": []string{"a"}}}
|
||||
for name, override := range options {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
field := mediaField("thumbnail", false)
|
||||
for key, value := range override {
|
||||
field[key] = value
|
||||
}
|
||||
|
||||
rec := env.admin(t, http.MethodPost, "/v1/content-type", typeBody("event", "Events", []map[string]any{{"key": "title", "label": "Title", "type": "text", "required": true}, field}))
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusUnprocessableEntity, "invalid_field")
|
||||
if env.types.createCalls != 0 {
|
||||
t.Errorf("a content type was stored")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The type of a saved media field never changes", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A"})
|
||||
before := env.storedFieldsJSON(t, "event")
|
||||
|
||||
toText := []map[string]any{{"key": "title", "label": "Title", "type": "text", "required": true}, {"key": "thumbnail", "label": "Thumbnail", "type": "text"}}
|
||||
toMedia := []map[string]any{{"key": "title", "label": "Title", "type": "media"}, mediaField("thumbnail", false)}
|
||||
for _, fields := range [][]map[string]any{toText, toMedia} {
|
||||
rec := env.admin(t, http.MethodPut, "/v1/content-type/event", typeBody("event", "Events", fields))
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusUnprocessableEntity, "invalid_field")
|
||||
if env.storedFieldsJSON(t, "event") != before {
|
||||
t.Errorf("schema changed")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Adding a media field to a type that has entries", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
name string
|
||||
required bool
|
||||
status int
|
||||
message string
|
||||
updated bool
|
||||
}{{"required", true, http.StatusConflict, "schema_incompatible", false}, {"optional", false, http.StatusOK, "", true}}
|
||||
for _, example := range examples {
|
||||
t.Run(example.name, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "event", []map[string]any{{"key": "title", "label": "Title", "type": "text", "required": true}})
|
||||
for range 12 {
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A"})
|
||||
}
|
||||
before := env.storedFieldsJSON(t, "event")
|
||||
|
||||
rec := env.admin(t, http.MethodPut, "/v1/content-type/event", typeBody("event", "Events", eventFields(example.required)))
|
||||
|
||||
if rec.Code != example.status || (example.message != "" && strings.TrimSpace(rec.Body.String()) != example.message) {
|
||||
t.Errorf("status %d body %q", rec.Code, rec.Body.String())
|
||||
}
|
||||
if (env.storedFieldsJSON(t, "event") != before) != example.updated {
|
||||
t.Errorf("stored schema updated mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Making a media field required", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
nullEntry := env.seedEntry(t, "event", map[string]any{"title": "B", "thumbnail": nil})
|
||||
|
||||
rec := env.admin(t, http.MethodPut, "/v1/content-type/event", typeBody("event", "Events", eventFields(true)))
|
||||
expectStatusAndBody(t, rec, http.StatusConflict, "schema_incompatible")
|
||||
env.admin(t, http.MethodDelete, entryPath(nullEntry.ID), "")
|
||||
rec = env.admin(t, http.MethodPut, "/v1/content-type/event", typeBody("event", "Events", eventFields(true)))
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusOK, "")
|
||||
})
|
||||
|
||||
t.Run("Making a media field required while a stored image is gone", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(999)})
|
||||
|
||||
rec := env.admin(t, http.MethodPut, "/v1/content-type/event", typeBody("event", "Events", eventFields(true)))
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusOK, "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFieldTypeEntries(t *testing.T) {
|
||||
t.Run("Creating an entry with an image", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
|
||||
rec := env.createEntry(t, "event", ctAdminId, `{"title":"Lime plaster workshop","thumbnail":7}`)
|
||||
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("status %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := decodeContentObject(t, rec)
|
||||
if body["data"].(map[string]any)["thumbnail"] != float64(mfLimeID) || !reflect.DeepEqual(body["media"], map[string]any{"thumbnail": limeMedia()}) {
|
||||
t.Errorf("unexpected %v", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An empty optional media field", func(t *testing.T) {
|
||||
examples := map[string]string{`null`: `{"title":"A","thumbnail":null}`, `absent`: `{"title":"A"}`}
|
||||
for name, data := range examples {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
|
||||
rec := env.createEntry(t, "event", ctAdminId, data)
|
||||
|
||||
body := decodeContentObject(t, rec)
|
||||
stored, present := body["data"].(map[string]any)["thumbnail"]
|
||||
if rec.Code != http.StatusCreated || !reflect.DeepEqual(body["media"], map[string]any{}) || (name == "null" && (!present || stored != nil)) || (name == "absent" && present) {
|
||||
t.Errorf("status %d body %v", rec.Code, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Entry values that break a media field are refused and store nothing", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
required bool
|
||||
data string
|
||||
message string
|
||||
}{
|
||||
{true, `{"title":"A"}`, "invalid_entry: thumbnail: required"},
|
||||
{true, `{"title":"A","thumbnail":null}`, "invalid_entry: thumbnail: required"},
|
||||
{false, `{"title":"A","thumbnail":999}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":"7"}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":0}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":-1}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":7.5}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":4294967296}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":true}`, "invalid_entry: thumbnail: type"},
|
||||
{false, `{"title":"A","thumbnail":[7]}`, "invalid_entry: thumbnail: type"},
|
||||
}
|
||||
for _, example := range examples {
|
||||
t.Run(example.data, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, example.required)
|
||||
|
||||
rec := env.createEntry(t, "event", ctAdminId, example.data)
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusUnprocessableEntity, example.message)
|
||||
if len(env.entries.entries) != 0 {
|
||||
t.Errorf("an entry was stored")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Replacing the image of an entry", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "Lime plaster workshop", "thumbnail": float64(mfLimeID)})
|
||||
|
||||
rec := env.member(t, http.MethodPut, entryPath(stored.ID), `{"data":{"title":"Lime plaster workshop","thumbnail":8}}`)
|
||||
|
||||
body := decodeContentObject(t, rec)
|
||||
thumbnail, _ := body["media"].(map[string]any)["thumbnail"].(map[string]any)
|
||||
if rec.Code != http.StatusOK || body["data"].(map[string]any)["thumbnail"] != float64(mfBathroomID) || thumbnail["filename"] != "bathroom-tiles.png" {
|
||||
t.Errorf("status %d body %v", rec.Code, body)
|
||||
}
|
||||
rec = env.member(t, http.MethodPut, entryPath(stored.ID), `{"data":{"title":"Lime plaster workshop","thumbnail":null}}`)
|
||||
body = decodeContentObject(t, rec)
|
||||
if rec.Code != http.StatusOK || body["data"].(map[string]any)["thumbnail"] != nil || !reflect.DeepEqual(body["media"], map[string]any{}) {
|
||||
t.Errorf("status %d body %v", rec.Code, body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An image deleted after the entry was saved", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
|
||||
deleted := env.member(t, http.MethodDelete, "/v1/media/7", "")
|
||||
rec := env.member(t, http.MethodGet, entryPath(stored.ID), "")
|
||||
|
||||
body := decodeContentObject(t, rec)
|
||||
if deleted.Code != http.StatusNoContent || body["data"].(map[string]any)["thumbnail"] != float64(mfLimeID) || !reflect.DeepEqual(body["media"], map[string]any{"thumbnail": missingMedia(mfLimeID)}) {
|
||||
t.Errorf("delete %d body %v", deleted.Code, body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An unchanged id whose image is gone can still be saved when the field is optional", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
delete(env.images.images, mfLimeID)
|
||||
|
||||
rec := env.member(t, http.MethodPut, entryPath(stored.ID), `{"data":{"title":"New title","thumbnail":7}}`)
|
||||
|
||||
body := decodeContentObject(t, rec)
|
||||
if rec.Code != http.StatusOK || body["data"].(map[string]any)["thumbnail"] != float64(mfLimeID) || !reflect.DeepEqual(body["media"], map[string]any{"thumbnail": missingMedia(mfLimeID)}) {
|
||||
t.Errorf("status %d body %v", rec.Code, body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A changed value must exist even when the old one is gone", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
value string
|
||||
status int
|
||||
message string
|
||||
}{{"999", http.StatusUnprocessableEntity, "invalid_entry: thumbnail: type"}, {"8", http.StatusOK, ""}, {"null", http.StatusOK, ""}}
|
||||
for _, example := range examples {
|
||||
t.Run(example.value, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
delete(env.images.images, mfLimeID)
|
||||
|
||||
rec := env.member(t, http.MethodPut, entryPath(stored.ID), `{"data":{"title":"A","thumbnail":`+example.value+`}}`)
|
||||
|
||||
expectStatusAndBody(t, rec, example.status, example.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A required media field with a missing image can't be saved as is", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
value string
|
||||
status int
|
||||
message string
|
||||
updated bool
|
||||
}{{"7", http.StatusUnprocessableEntity, "invalid_entry: thumbnail: required", false}, {"null", http.StatusUnprocessableEntity, "invalid_entry: thumbnail: required", false}, {"8", http.StatusOK, "", true}}
|
||||
for _, example := range examples {
|
||||
t.Run(example.value, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, true)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
delete(env.images.images, mfLimeID)
|
||||
|
||||
rec := env.member(t, http.MethodPut, entryPath(stored.ID), `{"data":{"title":"A","thumbnail":`+example.value+`}}`)
|
||||
|
||||
expectStatusAndBody(t, rec, example.status, example.message)
|
||||
if (env.entries.updateCalls == 1) != example.updated {
|
||||
t.Errorf("updates %d", env.entries.updateCalls)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A create always treats the value as changed", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
delete(env.images.images, mfLimeID)
|
||||
|
||||
rec := env.createEntry(t, "event", ctAdminId, `{"title":"A","thumbnail":7}`)
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusUnprocessableEntity, "invalid_entry: thumbnail: type")
|
||||
})
|
||||
|
||||
t.Run("Two broken fields name only one", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "event", []map[string]any{{"key": "title", "label": "Title", "type": "text", "required": true}, mediaField("thumbnail", false)})
|
||||
|
||||
rec := env.createEntry(t, "event", ctAdminId, `{"thumbnail":999}`)
|
||||
|
||||
expectStatusAndBody(t, rec, http.StatusUnprocessableEntity, "invalid_entry: title: required")
|
||||
})
|
||||
|
||||
t.Run("Entry list responses carry the media object per item", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
env.seedEntry(t, "event", map[string]any{"title": "B", "thumbnail": float64(999)})
|
||||
env.seedEntry(t, "event", map[string]any{"title": "C", "thumbnail": nil})
|
||||
|
||||
rec := env.member(t, http.MethodGet, "/v1/content-type/event/entry?sort=id:asc", "")
|
||||
|
||||
page := decodeContentObject(t, rec)
|
||||
items := page["items"].([]any)
|
||||
want := []any{map[string]any{"thumbnail": limeMedia()}, map[string]any{"thumbnail": missingMedia(999)}, map[string]any{}}
|
||||
for position, item := range items {
|
||||
if !reflect.DeepEqual(objectKeys(item.(map[string]any)), entryKeys) || !reflect.DeepEqual(item.(map[string]any)["media"], want[position]) {
|
||||
t.Errorf("item %d: %v", position, item)
|
||||
}
|
||||
}
|
||||
if len(items) != 3 || page["total"] != float64(3) || page["page"] != float64(1) || page["size"] != float64(25) {
|
||||
t.Errorf("unexpected page %v", page)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Types without a media field are not enriched", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "recipe", recipeFields())
|
||||
created := env.createEntry(t, "recipe", ctAdminId, `{"title":"Pad thai","difficulty":"easy"}`)
|
||||
|
||||
rec := env.member(t, http.MethodGet, "/v1/content-type/recipe/entry/"+strconv.Itoa(int(decodeContentObject(t, created)["id"].(float64))), "")
|
||||
|
||||
body := decodeContentObject(t, rec)
|
||||
if rec.Code != http.StatusOK || !reflect.DeepEqual(objectKeys(body), entryKeys) || !reflect.DeepEqual(body["media"], map[string]any{}) || env.lookups.gets != 0 {
|
||||
t.Errorf("status %d body %v lookups %d", rec.Code, body, env.lookups.gets)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Each distinct image is looked up once per request", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "gallery", []map[string]any{mediaField("cover", false), mediaField("banner", false)})
|
||||
env.seedEntry(t, "gallery", map[string]any{"cover": float64(mfLimeID), "banner": float64(mfLimeID)})
|
||||
env.seedEntry(t, "gallery", map[string]any{"cover": float64(mfLimeID), "banner": float64(mfBathroomID)})
|
||||
|
||||
rec := env.member(t, http.MethodGet, "/v1/content-type/gallery/entry", "")
|
||||
|
||||
if rec.Code != http.StatusOK || env.lookups.gets != 2 {
|
||||
t.Errorf("status %d lookups %d", rec.Code, env.lookups.gets)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A repository failure while resolving media is a 500", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
stored := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)})
|
||||
env.images.fail.failNext("Get")
|
||||
|
||||
rec := env.member(t, http.MethodGet, entryPath(stored.ID), "")
|
||||
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Errorf("status %d", rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFieldTypeLibrary(t *testing.T) {
|
||||
t.Run("Media list items count entries as uses", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "event", []map[string]any{{"key": "title", "label": "Title", "type": "text"}, mediaField("thumbnail", false), mediaField("banner", false)})
|
||||
env.seedArticleUsing(12, "Heat pumps", mfLimeID)
|
||||
env.seedArticleUsing(13, "Insulation", mfLimeID)
|
||||
for range 3 {
|
||||
env.seedEntry(t, "event", map[string]any{"thumbnail": float64(mfBathroomID)})
|
||||
}
|
||||
env.entries.entries[1] = withData(env.entries.entries[1], map[string]any{"thumbnail": float64(mfBathroomID), "banner": float64(mfBathroomID)})
|
||||
for range 5 {
|
||||
env.seedEntry(t, "event", map[string]any{"thumbnail": float64(mfLimeID)})
|
||||
}
|
||||
|
||||
list := decodeContentObject(t, env.member(t, http.MethodGet, "/v1/media/", ""))
|
||||
one := decodeContentObject(t, env.member(t, http.MethodGet, "/v1/media/8", ""))
|
||||
unused := decodeContentObject(t, env.member(t, http.MethodGet, "/v1/media/?filter=unused", ""))
|
||||
|
||||
usedIn := map[float64]float64{}
|
||||
for _, item := range list["items"].([]any) {
|
||||
usedIn[item.(map[string]any)["id"].(float64)] = item.(map[string]any)["used_in"].(float64)
|
||||
}
|
||||
if usedIn[mfBathroomID] != 3 || usedIn[mfLimeID] != 7 || one["used_in"] != float64(3) {
|
||||
t.Errorf("used_in %v, one %v", usedIn, one["used_in"])
|
||||
}
|
||||
if len(unused["items"].([]any)) != 0 || unused["counts"].(map[string]any)["unused"] != float64(0) {
|
||||
t.Errorf("unused %v", unused)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Usage of an image lists articles and entries", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "workshop", []map[string]any{{"key": "seats", "label": "Seats", "type": "number"}, mediaField("thumbnail", false)})
|
||||
env.createEventType(t, false)
|
||||
env.seedArticleUsing(12, "Heat pumps", mfLimeID)
|
||||
workshop := env.seedEntry(t, "workshop", map[string]any{"seats": float64(18), "thumbnail": float64(mfLimeID)})
|
||||
first := env.seedEntry(t, "event", map[string]any{"title": "Lime plaster workshop", "thumbnail": float64(mfLimeID)})
|
||||
env.seedEntry(t, "event", map[string]any{"title": "Other", "thumbnail": float64(mfBathroomID)})
|
||||
second := env.seedEntry(t, "event", map[string]any{"title": "Second", "thumbnail": float64(mfLimeID)})
|
||||
third := env.seedEntry(t, "event", map[string]any{"title": "Third", "thumbnail": float64(mfLimeID)})
|
||||
|
||||
rec := env.member(t, http.MethodGet, "/v1/media/7/usage", "")
|
||||
|
||||
items := decodeContentArray(t, rec)
|
||||
if rec.Code != http.StatusOK || len(items) != 5 {
|
||||
t.Fatalf("status %d items %v", rec.Code, items)
|
||||
}
|
||||
wantArticle := map[string]any{"type": "article", "id": float64(12), "title": "Heat pumps", "published": true, "fields": []any{"cover"}}
|
||||
if !reflect.DeepEqual(items[0], wantArticle) {
|
||||
t.Errorf("article item %v", items[0])
|
||||
}
|
||||
wantWorkshop := map[string]any{"type": "entry", "id": float64(workshop.ID), "fields": []any{"thumbnail"}, "content_type_key": "workshop", "content_type_name": "Workshop", "first_field": map[string]any{"key": "seats", "type": "number", "value": float64(18)}}
|
||||
if !reflect.DeepEqual(items[4], wantWorkshop) {
|
||||
t.Errorf("workshop item %v", items[4])
|
||||
}
|
||||
for _, item := range items[1:4] {
|
||||
if item["content_type_key"] != "event" || item["content_type_name"] != "Event" || item["title"] != nil || item["published"] != nil || item["type"] != "entry" {
|
||||
t.Errorf("event item %v", item)
|
||||
}
|
||||
if len(item) != 6 {
|
||||
t.Errorf("event item keys %v", item)
|
||||
}
|
||||
}
|
||||
gotOrder := []float64{items[1]["id"].(float64), items[2]["id"].(float64), items[3]["id"].(float64), items[4]["id"].(float64)}
|
||||
wantOrder := []float64{float64(first.ID), float64(second.ID), float64(third.ID), float64(workshop.ID)}
|
||||
if !reflect.DeepEqual(gotOrder, wantOrder) {
|
||||
t.Errorf("order after the article should be event %v then workshop: got %v", wantOrder[:3], gotOrder)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The raw first field in usage", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
name string
|
||||
fieldType string
|
||||
stored map[string]any
|
||||
value any
|
||||
}{
|
||||
{"title", "text", map[string]any{"title": "Lime plaster workshop"}, "Lime plaster workshop"},
|
||||
{"body", "richtext", map[string]any{"body": "\n\nFirst line\n\nSecond"}, "\n\nFirst line\n\nSecond"},
|
||||
{"seats", "number", map[string]any{"seats": float64(18)}, float64(18)},
|
||||
{"online", "boolean", map[string]any{"online": false}, false},
|
||||
{"starts_on", "date", map[string]any{"starts_on": "2026-11-21"}, "2026-11-21"},
|
||||
{"title", "text", map[string]any{"title": nil}, nil},
|
||||
{"title", "text", map[string]any{}, nil},
|
||||
}
|
||||
for _, example := range examples {
|
||||
t.Run(example.name+" "+example.fieldType, func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "event", []map[string]any{{"key": example.name, "label": "First", "type": example.fieldType}, mediaField("thumbnail", false)})
|
||||
data := map[string]any{"thumbnail": float64(mfLimeID)}
|
||||
for key, value := range example.stored {
|
||||
data[key] = value
|
||||
}
|
||||
env.seedEntry(t, "event", data)
|
||||
|
||||
items := decodeContentArray(t, env.member(t, http.MethodGet, "/v1/media/7/usage", ""))
|
||||
|
||||
want := map[string]any{"key": example.name, "type": example.fieldType, "value": example.value}
|
||||
if len(items) != 1 || !reflect.DeepEqual(items[0]["first_field"], want) || items[0]["title"] != nil {
|
||||
t.Errorf("unexpected %v", items)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The raw first field in usage for a media field", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createType(t, "event", []map[string]any{mediaField("cover", false)})
|
||||
env.seedEntry(t, "event", map[string]any{"cover": float64(mfLimeID)})
|
||||
|
||||
items := decodeContentArray(t, env.member(t, http.MethodGet, "/v1/media/7/usage", ""))
|
||||
|
||||
want := map[string]any{"key": "cover", "type": "media", "value": float64(mfLimeID)}
|
||||
if len(items) != 1 || !reflect.DeepEqual(items[0]["first_field"], want) || !reflect.DeepEqual(items[0]["fields"], []any{"cover"}) {
|
||||
t.Errorf("unexpected %v", items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Usage of an unused or unknown image", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
|
||||
empty := env.member(t, http.MethodGet, "/v1/media/8/usage", "")
|
||||
unknown := env.member(t, http.MethodGet, "/v1/media/999/usage", "")
|
||||
malformed := env.member(t, http.MethodGet, "/v1/media/abc/usage", "")
|
||||
|
||||
expectStatusAndBody(t, empty, http.StatusOK, "[]")
|
||||
expectStatusAndBody(t, unknown, http.StatusNotFound, apperr.ImageNotFoundErrorMessage)
|
||||
expectStatusAndBody(t, malformed, http.StatusBadRequest, apperr.IdUint32ErrorMessage)
|
||||
})
|
||||
|
||||
t.Run("Deleting an image used by entries is allowed", func(t *testing.T) {
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedArticleUsing(12, "Heat pumps", mfLimeID)
|
||||
var before []entry.Entry
|
||||
for range 5 {
|
||||
before = append(before, env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfLimeID)}))
|
||||
}
|
||||
|
||||
rec := env.member(t, http.MethodDelete, "/v1/media/7", "")
|
||||
|
||||
if rec.Code != http.StatusNoContent || env.entries.updateCalls != 0 {
|
||||
t.Fatalf("status %d updates %d", rec.Code, env.entries.updateCalls)
|
||||
}
|
||||
for _, snapshot := range before {
|
||||
if !reflect.DeepEqual(env.entries.entries[snapshot.ID], snapshot) {
|
||||
t.Errorf("entry changed %v", env.entries.entries[snapshot.ID])
|
||||
}
|
||||
}
|
||||
if _, gone := env.images.images[mfLimeID]; gone {
|
||||
t.Errorf("image should be deleted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Merging duplicates repoints entry media", func(t *testing.T) {
|
||||
env := newMergeEnv(t)
|
||||
first := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfCopyID)})
|
||||
second := env.seedEntry(t, "event", map[string]any{"title": "B", "thumbnail": float64(mfCopyID)})
|
||||
|
||||
rec := env.member(t, http.MethodPost, "/v1/media/merge", `{"keep_id":7,"merge_ids":[9]}`)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
for _, original := range []entry.Entry{first, second} {
|
||||
stored := env.entries.entries[original.ID]
|
||||
if stored.Data["thumbnail"] != float64(mfLimeID) || stored.CreatedBy != original.CreatedBy || stored.UpdatedBy != original.UpdatedBy || !stored.UpdatedAt.Equal(original.UpdatedAt) {
|
||||
t.Errorf("entry %d: %v", original.ID, stored)
|
||||
}
|
||||
}
|
||||
if _, exists := env.images.images[mfCopyID]; exists {
|
||||
t.Errorf("image 9 should be gone")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A failure while repointing entries stops the merge", func(t *testing.T) {
|
||||
env := newMergeEnv(t)
|
||||
first := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfCopyID)})
|
||||
second := env.seedEntry(t, "event", map[string]any{"title": "B", "thumbnail": float64(mfCopyID)})
|
||||
env.spy.failOnUpdate = 2
|
||||
|
||||
rec := env.member(t, http.MethodPost, "/v1/media/merge", `{"keep_id":7,"merge_ids":[9]}`)
|
||||
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("status %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
for _, original := range []entry.Entry{first, second} {
|
||||
if !reflect.DeepEqual(env.entries.entries[original.ID], original) {
|
||||
t.Errorf("entry %d not restored: %v", original.ID, env.entries.entries[original.ID])
|
||||
}
|
||||
}
|
||||
if _, exists := env.images.images[mfCopyID]; !exists || len(env.images.deleteCalls) != 0 {
|
||||
t.Errorf("no image should be removed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Entry uses decide which duplicate is kept", func(t *testing.T) {
|
||||
env := newMergeEnv(t)
|
||||
env.seedArticleUsing(12, "Heat pumps", mfLimeID)
|
||||
baseline := decodeContentObject(t, env.admin(t, http.MethodGet, "/v1/media/purge-preview", ""))
|
||||
baseList := baseline["list"].([]any)
|
||||
if len(baseList) != 1 || baseList[0].(map[string]any)["keep_id"] != float64(mfLimeID) {
|
||||
t.Fatalf("baseline %v", baseline)
|
||||
}
|
||||
|
||||
env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfCopyID)})
|
||||
tie := decodeContentObject(t, env.admin(t, http.MethodGet, "/v1/media/purge-preview", ""))
|
||||
tieList := tie["list"].([]any)
|
||||
if tieList[0].(map[string]any)["keep_id"] != float64(mfLimeID) || tieList[0].(map[string]any)["used_in"] != float64(1) || tie["fingerprint"] != baseline["fingerprint"] {
|
||||
t.Errorf("one article against one entry is a tie, the older copy stays: %v", tie)
|
||||
}
|
||||
|
||||
env.seedEntry(t, "event", map[string]any{"title": "B", "thumbnail": float64(mfCopyID)})
|
||||
decided := decodeContentObject(t, env.admin(t, http.MethodGet, "/v1/media/purge-preview", ""))
|
||||
decidedList := decided["list"].([]any)
|
||||
if decidedList[0].(map[string]any)["keep_id"] != float64(mfCopyID) || decidedList[0].(map[string]any)["used_in"] != float64(2) || decided["fingerprint"] == baseline["fingerprint"] {
|
||||
t.Errorf("two entries outweigh one article: %v", decided)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The purge plan counts entries as uses", func(t *testing.T) {
|
||||
env := newMergeEnv(t)
|
||||
used := env.seedEntry(t, "event", map[string]any{"title": "A", "thumbnail": float64(mfCopyID)})
|
||||
|
||||
preview := decodeContentObject(t, env.admin(t, http.MethodGet, "/v1/media/purge-preview", ""))
|
||||
run := env.admin(t, http.MethodPost, "/v1/media/purge", "")
|
||||
|
||||
list := preview["list"].([]any)
|
||||
if len(list) != 1 || list[0].(map[string]any)["keep_id"] != float64(mfCopyID) || list[0].(map[string]any)["used_in"] != float64(1) {
|
||||
t.Errorf("preview %v", preview)
|
||||
}
|
||||
if run.Code != http.StatusOK || env.entries.entries[used.ID].Data["thumbnail"] != float64(mfCopyID) {
|
||||
t.Errorf("run %d %s, entry %v", run.Code, run.Body.String(), env.entries.entries[used.ID])
|
||||
}
|
||||
if _, exists := env.images.images[mfLimeID]; exists {
|
||||
t.Errorf("image 7 should be merged away")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newMergeEnv(t *testing.T) *mediaFieldEnv {
|
||||
t.Helper()
|
||||
env := newMediaFieldEnv(t)
|
||||
env.createEventType(t, false)
|
||||
env.seedImage(mfCopyID, "lime-plaster-copy.jpg", 1600, 900, 240000, "hash-lime")
|
||||
return env
|
||||
}
|
||||
|
||||
func withData(record entry.Entry, data map[string]any) entry.Entry {
|
||||
record.Data = data
|
||||
return record
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/entry"
|
||||
imageDomain "RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
mfLimeID = 7
|
||||
mfBathroomID = 8
|
||||
mfCopyID = 9
|
||||
mfImagePath = "/v1/static/uploadedImages/lime-plaster.jpg"
|
||||
)
|
||||
|
||||
var errMediaFieldEntryUpdate = errors.New("entry update failure")
|
||||
|
||||
type mfEntrySpyRepo struct {
|
||||
*mockEntryRepo
|
||||
failOnUpdate int
|
||||
updates int
|
||||
}
|
||||
|
||||
var _ entry.Repository = &mfEntrySpyRepo{}
|
||||
|
||||
func (s *mfEntrySpyRepo) Update(record entry.Entry) (entry.Entry, error) {
|
||||
s.updates++
|
||||
if s.failOnUpdate > 0 && s.updates == s.failOnUpdate {
|
||||
return entry.Entry{}, errMediaFieldEntryUpdate
|
||||
}
|
||||
return s.mockEntryRepo.Update(record)
|
||||
}
|
||||
|
||||
type mfImageSpyRepo struct {
|
||||
*mockImageRepo
|
||||
gets int
|
||||
}
|
||||
|
||||
var _ imageDomain.Repository = &mfImageSpyRepo{}
|
||||
|
||||
func (s *mfImageSpyRepo) Get(id uint32) (imageDomain.Image, error) {
|
||||
s.gets++
|
||||
return s.mockImageRepo.Get(id)
|
||||
}
|
||||
|
||||
type mediaFieldEnv struct {
|
||||
*contentTypeEnv
|
||||
articles *mockArticleRepo
|
||||
spy *mfEntrySpyRepo
|
||||
lookups *mfImageSpyRepo
|
||||
logs *bytes.Buffer
|
||||
}
|
||||
|
||||
func newMediaFieldEnv(t *testing.T) *mediaFieldEnv {
|
||||
t.Helper()
|
||||
base := newContentTypeEnv(t)
|
||||
env := &mediaFieldEnv{contentTypeEnv: base, articles: newMockArticleRepo(), spy: &mfEntrySpyRepo{mockEntryRepo: base.entries}, lookups: &mfImageSpyRepo{mockImageRepo: base.images}, logs: &bytes.Buffer{}}
|
||||
settings := newMockPurgeSettingsRepo()
|
||||
now := func() time.Time { return base.clock }
|
||||
merge := useCases.NewMergeMediaUseCase(base.images, env.articles, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), base.types, env.spy)
|
||||
container := base.container
|
||||
container.CreateEntryUseCase = useCases.NewCreateEntryUseCase(base.types, base.entries, env.lookups, now)
|
||||
container.GetEntryUseCase = useCases.NewGetEntryUseCase(base.types, base.entries, env.lookups)
|
||||
container.ListEntriesUseCase = useCases.NewListEntriesUseCase(base.types, base.entries, env.lookups)
|
||||
container.UpdateEntryUseCase = useCases.NewUpdateEntryUseCase(base.types, base.entries, env.lookups, now)
|
||||
container.ListMediaUseCase = useCases.NewListMediaUseCase(base.images, env.articles, base.users, newMockVariantFileRepo(), plainSites(), plainAuthors(), base.types, base.entries)
|
||||
container.DeleteMediaUseCase = useCases.NewDeleteMediaUseCase(base.images, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), env.articles)
|
||||
container.MergeMediaUseCase = merge
|
||||
container.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(base.images, env.articles, plainSites(), plainAuthors(), base.types, base.entries)
|
||||
container.PreviewPurgeUseCase = useCases.NewPreviewPurgeUseCase(base.images, env.articles, plainSites(), plainAuthors(), base.types, base.entries)
|
||||
container.RunPurgeUseCase = useCases.NewRunPurgeUseCase(base.images, env.articles, merge, settings, now, log.New(env.logs, "", 0), discardRecorder, plainSites(), plainAuthors())
|
||||
base.mountRouter()
|
||||
env.seedImage(mfLimeID, "lime-plaster.jpg", 1600, 900, 240000, "hash-lime")
|
||||
env.seedImage(mfBathroomID, "bathroom-tiles.png", 800, 600, 51200, "hash-bathroom")
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) seedImage(id uint32, filename string, width int, height int, size int64, sha string) {
|
||||
e.images.images[id] = imageDomain.Image{ID: id, Path: "/v1/static/uploadedImages/" + filename, Filename: filename, Size: size, ContentType: "image/jpeg", Width: width, Height: height, SHA256: sha, UploadedByID: ctAdminId, CreatedAt: e.clock.Add(time.Duration(id) * time.Minute)}
|
||||
}
|
||||
|
||||
func mediaField(key string, required bool) map[string]any {
|
||||
return map[string]any{"key": key, "label": "Thumbnail", "type": "media", "required": required, "unique": false, "default": nil, "min": nil, "max": nil, "options": []string{}}
|
||||
}
|
||||
|
||||
func eventFields(required bool) []map[string]any {
|
||||
return []map[string]any{{"key": "title", "label": "Title", "type": "text", "required": true}, mediaField("thumbnail", required)}
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) createEventType(t *testing.T, required bool) {
|
||||
t.Helper()
|
||||
e.createType(t, "event", eventFields(required))
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) seedEntry(t *testing.T, typeKey string, data map[string]any) entry.Entry {
|
||||
t.Helper()
|
||||
owner, err := e.types.GetByKey(typeKey)
|
||||
if err != nil {
|
||||
t.Fatalf("seed type %s: %v", typeKey, err)
|
||||
}
|
||||
stored, err := e.entries.Create(entry.Entry{ContentTypeID: owner.ID, Data: data, CreatedBy: ctMemberId, UpdatedBy: ctMemberId, CreatedAt: e.clock, UpdatedAt: e.clock})
|
||||
if err != nil {
|
||||
t.Fatalf("seed entry: %v", err)
|
||||
}
|
||||
return stored
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) member(t *testing.T, method string, path string, body string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
return e.as(t, ctMemberId, method, path, body)
|
||||
}
|
||||
|
||||
func (e *mediaFieldEnv) seedArticleUsing(id uint32, title string, imageID uint32) {
|
||||
cover := imageID
|
||||
e.articles.articles[id] = article.Article{ID: id, Title: title, IsOnline: true, CoverImageID: &cover}
|
||||
}
|
||||
|
||||
func jsonOf(t *testing.T, value any) string {
|
||||
t.Helper()
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
return string(encoded)
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/contenttype"
|
||||
"RenewCMS/internal/domain/entry"
|
||||
imageDomain "RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func mediaThumbnailSchema(t *testing.T, required bool) []contenttype.Field {
|
||||
t.Helper()
|
||||
fields, err := contenttype.ValidateFields([]contenttype.Field{
|
||||
{Key: "title", Label: "Title", Type: contenttype.TypeText, Required: true},
|
||||
{Key: "thumbnail", Label: "Thumbnail", Type: contenttype.TypeMedia, Required: required},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("schema: %v", err)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func expectFieldError(t *testing.T, err error, key string, rule string) {
|
||||
t.Helper()
|
||||
var fieldError entry.FieldError
|
||||
if !errors.Is(err, entry.ErrInvalidEntry) || !errors.As(err, &fieldError) || fieldError.Key != key || fieldError.Rule != rule {
|
||||
t.Errorf("want %s %s, got %v", key, rule, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaFieldDomain(t *testing.T) {
|
||||
t.Run("contenttype.ValidateFields refuses what a media field can't have", func(t *testing.T) {
|
||||
refused := map[string]contenttype.Field{
|
||||
"unique": {Unique: true},
|
||||
"default int": {Default: float64(7)},
|
||||
"default str": {Default: "lime"},
|
||||
"min": {Min: float64(1)},
|
||||
"max": {Max: float64(5)},
|
||||
"options": {Options: []string{"a"}},
|
||||
}
|
||||
for name, base := range refused {
|
||||
base.Key, base.Label, base.Type = "thumbnail", "Thumbnail", contenttype.TypeMedia
|
||||
if _, err := contenttype.ValidateFields([]contenttype.Field{base}); !errors.Is(err, contenttype.ErrInvalidField) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
plain := contenttype.Field{Key: "thumbnail", Label: "Thumbnail", Type: contenttype.TypeMedia, Required: true}
|
||||
if _, err := contenttype.ValidateFields([]contenttype.Field{plain}); err != nil {
|
||||
t.Errorf("a plain media field should pass: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("contenttype.MediaID accepts integers from 1 to the uint32 maximum only", func(t *testing.T) {
|
||||
accepted := map[any]uint32{float64(1): 1, float64(7): 7, float64(math.MaxUint32): math.MaxUint32, 9: 9, int64(8): 8, uint32(3): 3}
|
||||
for value, want := range accepted {
|
||||
if got, ok := contenttype.MediaID(value); !ok || got != want {
|
||||
t.Errorf("%v: %d %v", value, got, ok)
|
||||
}
|
||||
}
|
||||
for _, value := range []any{float64(0), float64(-1), 7.5, float64(math.MaxUint32) + 1, "7", true, []any{7.0}, nil, 0, -3, uint32(0), math.NaN()} {
|
||||
if _, ok := contenttype.MediaID(value); ok {
|
||||
t.Errorf("%v should be refused", value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("entry.Validate applies required and type to a media field", func(t *testing.T) {
|
||||
schema := mediaThumbnailSchema(t, true)
|
||||
|
||||
expectFieldError(t, entry.Validate(schema, map[string]any{"title": "A"}), "thumbnail", entry.RuleRequired)
|
||||
expectFieldError(t, entry.Validate(schema, map[string]any{"title": "A", "thumbnail": nil}), "thumbnail", entry.RuleRequired)
|
||||
expectFieldError(t, entry.Validate(schema, map[string]any{"title": "A", "thumbnail": "7"}), "thumbnail", contenttype.RuleType)
|
||||
expectFieldError(t, entry.Validate(schema, map[string]any{"title": "A", "thumbnail": 7.5}), "thumbnail", contenttype.RuleType)
|
||||
if err := entry.Validate(schema, map[string]any{"title": "A", "thumbnail": float64(7)}); err != nil {
|
||||
t.Errorf("valid media: %v", err)
|
||||
}
|
||||
if err := entry.Validate(mediaThumbnailSchema(t, false), map[string]any{"title": "A"}); err != nil {
|
||||
t.Errorf("optional media: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mediaFieldFixture struct {
|
||||
types *mockContentTypeRepo
|
||||
entries *mockEntryRepo
|
||||
images *mockImageRepo
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func newMediaFieldFixture(t *testing.T, required bool) *mediaFieldFixture {
|
||||
t.Helper()
|
||||
fixture := &mediaFieldFixture{types: newMockContentTypeRepo(), entries: newMockEntryRepo(), images: newMockImageRepo(), now: func() time.Time { return time.Date(2026, time.October, 5, 9, 0, 0, 0, time.UTC) }}
|
||||
if _, err := fixture.types.Create(contenttype.ContentType{Key: "event", Name: "Events", Fields: mediaThumbnailSchema(t, required)}); err != nil {
|
||||
t.Fatalf("type: %v", err)
|
||||
}
|
||||
fixture.images.images[7] = imageDomain.Image{ID: 7, Filename: "lime-plaster.jpg", Path: mfImagePath, Width: 1600, Height: 900, Size: 240000}
|
||||
fixture.images.images[8] = imageDomain.Image{ID: 8, Filename: "bathroom-tiles.png", Path: "/v1/static/uploadedImages/bathroom-tiles.png"}
|
||||
fixture.types.createCalls = 0
|
||||
return fixture
|
||||
}
|
||||
|
||||
func TestMediaFieldUseCases(t *testing.T) {
|
||||
t.Run("CreateEntry returns the same errors for a missing library image and creates nothing", func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.images, fixture.now)
|
||||
|
||||
_, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "event", Data: map[string]any{"title": "A", "thumbnail": float64(999)}, UserID: 2})
|
||||
|
||||
expectFieldError(t, err, "thumbnail", contenttype.RuleType)
|
||||
if fixture.entries.createCalls != 0 {
|
||||
t.Errorf("creates %d", fixture.entries.createCalls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateEntry checks Validate before library existence", func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.images, fixture.now)
|
||||
|
||||
_, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "event", Data: map[string]any{"thumbnail": float64(999)}, UserID: 2})
|
||||
|
||||
expectFieldError(t, err, "title", entry.RuleRequired)
|
||||
})
|
||||
|
||||
t.Run("CreateEntry surfaces a library failure", func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
fixture.images.fail.failNext("Get")
|
||||
create := useCases.NewCreateEntryUseCase(fixture.types, fixture.entries, fixture.images, fixture.now)
|
||||
|
||||
_, err := create.Create(useCases.CreateEntryCommand{ContentTypeKey: "event", Data: map[string]any{"title": "A", "thumbnail": float64(7)}, UserID: 2})
|
||||
|
||||
if !errors.Is(err, errRepositoryFailure) || fixture.entries.createCalls != 0 {
|
||||
t.Errorf("err %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateEntry treats only the stored id as unchanged", func(t *testing.T) {
|
||||
examples := []struct {
|
||||
name string
|
||||
required bool
|
||||
value any
|
||||
rule string
|
||||
}{
|
||||
{"unchanged and gone, optional", false, float64(7), ""},
|
||||
{"unchanged and gone, required", true, float64(7), entry.RuleRequired},
|
||||
{"changed to a missing image", false, float64(999), contenttype.RuleType},
|
||||
{"changed to an existing image", true, float64(8), ""},
|
||||
{"cleared, optional", false, nil, ""},
|
||||
}
|
||||
for _, example := range examples {
|
||||
t.Run(example.name, func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, example.required)
|
||||
delete(fixture.images.images, 7)
|
||||
stored, _ := fixture.entries.Create(entry.Entry{ContentTypeID: 1, Data: map[string]any{"title": "A", "thumbnail": float64(7)}})
|
||||
fixture.entries.createCalls = 0
|
||||
update := useCases.NewUpdateEntryUseCase(fixture.types, fixture.entries, fixture.images, fixture.now)
|
||||
|
||||
_, err := update.Update(useCases.UpdateEntryCommand{ContentTypeKey: "event", ID: stored.ID, Data: map[string]any{"title": "A", "thumbnail": example.value}, UserID: 3})
|
||||
|
||||
if example.rule == "" {
|
||||
if err != nil || fixture.entries.updateCalls != 1 {
|
||||
t.Errorf("err %v updates %d", err, fixture.entries.updateCalls)
|
||||
}
|
||||
return
|
||||
}
|
||||
expectFieldError(t, err, "thumbnail", example.rule)
|
||||
if fixture.entries.updateCalls != 0 {
|
||||
t.Errorf("updates %d", fixture.entries.updateCalls)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateContentType returns ErrSchemaIncompatible for a new required media field and does not update", func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
fixture.types.types[1] = contenttype.ContentType{ID: 1, Key: "event", Name: "Events", Fields: mediaThumbnailSchema(t, false)[:1]}
|
||||
fixture.entries.Create(entry.Entry{ContentTypeID: 1, Data: map[string]any{"title": "A"}})
|
||||
update := useCases.NewUpdateContentTypeUseCase(fixture.types, fixture.entries, fixture.now)
|
||||
|
||||
_, err := update.Update(useCases.UpdateContentTypeCommand{Key: "event", Name: "Events", Fields: mediaThumbnailSchema(t, true)})
|
||||
|
||||
if !errors.Is(err, contenttype.ErrSchemaIncompatible) || fixture.types.updateCalls != 0 {
|
||||
t.Errorf("err %v updates %d", err, fixture.types.updateCalls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetEntry and ListEntries resolve media once per distinct image and mark missing ones", func(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
for _, id := range []float64{7, 7, 999} {
|
||||
fixture.entries.Create(entry.Entry{ContentTypeID: 1, Data: map[string]any{"title": "A", "thumbnail": id}})
|
||||
}
|
||||
spy := &mfImageSpyRepo{mockImageRepo: fixture.images}
|
||||
list := useCases.NewListEntriesUseCase(fixture.types, fixture.entries, spy)
|
||||
|
||||
page, err := list.ListPage(useCases.ListEntriesCommand{ContentTypeKey: "event", Page: 1, Size: 25, Sort: entry.Sort{Field: entry.SortByID, Direction: entry.SortAsc}})
|
||||
|
||||
if err != nil || spy.gets != 2 || len(page.Items) != 3 {
|
||||
t.Fatalf("err %v lookups %d", err, spy.gets)
|
||||
}
|
||||
if page.Items[0].Media["thumbnail"].Filename != "lime-plaster.jpg" || !page.Items[2].Media["thumbnail"].Missing || page.Items[2].Media["thumbnail"].ID != 999 {
|
||||
t.Errorf("media %+v %+v", page.Items[0].Media, page.Items[2].Media)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFieldUnknownDimensions(t *testing.T) {
|
||||
fixture := newMediaFieldFixture(t, false)
|
||||
owner, err := fixture.types.GetByKey("event")
|
||||
if err != nil {
|
||||
t.Fatalf("type: %v", err)
|
||||
}
|
||||
created, err := fixture.entries.Create(entry.Entry{ContentTypeID: owner.ID, Data: map[string]any{"title": "A", "thumbnail": float64(8)}})
|
||||
if err != nil {
|
||||
t.Fatalf("entry: %v", err)
|
||||
}
|
||||
get := useCases.NewGetEntryUseCase(fixture.types, fixture.entries, fixture.images)
|
||||
|
||||
found, err := get.Get("event", created.ID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
media := found.Media["thumbnail"]
|
||||
if media.Width != nil || media.Height != nil {
|
||||
t.Errorf("dimensions %v %v, want nil", media.Width, media.Height)
|
||||
}
|
||||
}
|
||||
@@ -943,7 +943,7 @@ func TestMediaMerging(t *testing.T) {
|
||||
|
||||
t.Run("A duplicated id reaching the use case is refused before anything is read", func(t *testing.T) {
|
||||
env := newEnv(t)
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
_, err := uc.Merge(testCtx, 2, []uint32{3, 3})
|
||||
|
||||
@@ -1038,7 +1038,7 @@ func TestMediaSentinels(t *testing.T) {
|
||||
env.seedImage(2, "b.png", 1, "image/png", 1, 1, hashB, 1)
|
||||
env.seedImage(3, "c.png", 1, "image/png", 1, 1, "", 1)
|
||||
env.seedImage(4, "d.png", 1, "image/png", 1, 1, "", 1)
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
uc := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
if _, err := uc.Merge(testCtx, 1, []uint32{9999}); !errors.Is(err, domainImage.ErrMergeCopyMissing) {
|
||||
t.Errorf("expected ErrMergeCopyMissing, got %v", err)
|
||||
|
||||
@@ -270,7 +270,7 @@ func TestListMediaUseCasePaging(t *testing.T) {
|
||||
build := func() (*mockImageRepo, *mockVariantFileRepo, *useCases.ListMediaUseCase) {
|
||||
images := newMockImageRepo()
|
||||
variants := newMockVariantFileRepo()
|
||||
return images, variants, useCases.NewListMediaUseCase(images, newMockArticleRepo(), newMockUserRepo(), variants, plainSites(), plainAuthors())
|
||||
return images, variants, useCases.NewListMediaUseCase(images, newMockArticleRepo(), newMockUserRepo(), variants, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
}
|
||||
seed := func(images *mockImageRepo, count int) {
|
||||
for id := uint32(1); id <= uint32(count); id++ {
|
||||
|
||||
@@ -31,7 +31,7 @@ type schedulerEnv struct {
|
||||
func newSchedulerEnv(t *testing.T) *schedulerEnv {
|
||||
t.Helper()
|
||||
env := newPurgeEnv(t)
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
now := func() time.Time { return env.clock }
|
||||
runPurge := useCases.NewRunPurgeUseCase(env.images, env.articles, merge, env.purgeSettings, now, log.New(env.logs, "", 0), discardRecorder, plainSites(), plainAuthors())
|
||||
return &schedulerEnv{
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestUpdatePurgeSettingsRejectsOutOfRangeIntervals(t *testing.T) {
|
||||
|
||||
func TestRunPurgeDistinguishesBusyFromConflict(t *testing.T) {
|
||||
newRun := func(env *mediaEnv) *useCases.RunPurgeUseCase {
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
return useCases.NewRunPurgeUseCase(env.images, env.articles, merge, env.purgeSettings, func() time.Time { return env.clock }, log.New(&bytes.Buffer{}, "", 0), discardRecorder, plainSites(), plainAuthors())
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestPurgePlanOrderingAndFingerprint(t *testing.T) {
|
||||
env.useImages(11, 5)
|
||||
env.useImages(12, 3)
|
||||
|
||||
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()).Preview()
|
||||
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()).Preview()
|
||||
|
||||
var kept []uint32
|
||||
for _, group := range preview.List {
|
||||
@@ -132,7 +132,7 @@ func TestPurgePlanOrderingAndFingerprint(t *testing.T) {
|
||||
env.useImages(10, 1, 2, 3)
|
||||
env.useImages(11, 5)
|
||||
|
||||
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()).Preview()
|
||||
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()).Preview()
|
||||
|
||||
sum := sha256.Sum256([]byte("1:2,3;5:4;"))
|
||||
if err != nil || preview.Fingerprint != hex.EncodeToString(sum[:]) {
|
||||
@@ -148,7 +148,7 @@ func TestMergeMediaExposesTheRewrittenArticleIds(t *testing.T) {
|
||||
env.useImages(1, 2)
|
||||
env.useImages(2, 1, 2)
|
||||
env.useImages(3, 1)
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
result, err := merge.Merge(testCtx, 1, []uint32{2})
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestMergeMediaExposesTheRewrittenArticleIds(t *testing.T) {
|
||||
env.useImages(1, 2)
|
||||
env.useImages(2, 2)
|
||||
env.articles.failUpdateBodyFor = map[uint32]error{2: errRepositoryFailure}
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
|
||||
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
result, err := merge.Merge(testCtx, 1, []uint32{2})
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestMergeMediaRemovesTheMergedBlobs(t *testing.T) {
|
||||
create := useCases.NewCreateImageUseCase(repo, store, discardRecorder)
|
||||
kept, copyOne, copyTwo := uploadPng(t, create), uploadPng(t, create), uploadPng(t, create)
|
||||
|
||||
result, err := useCases.NewMergeMediaUseCase(repo, articles, store, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors()).Merge(testCtx, kept.ID, []uint32{copyOne.ID, copyTwo.ID})
|
||||
result, err := useCases.NewMergeMediaUseCase(repo, articles, store, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()).Merge(testCtx, kept.ID, []uint32{copyOne.ID, copyTwo.ID})
|
||||
|
||||
if err != nil || result.Merged != 2 {
|
||||
t.Fatalf("merge returned %+v, %v", result, err)
|
||||
|
||||
@@ -143,11 +143,11 @@ func newRuntimeSettingsContainer(t *testing.T) *api.UseCases {
|
||||
SendMailUseCase: useCases.NewSendMailUseCase(mailRepo),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(imageRepo, newMockImageStorage(), discardRecorder),
|
||||
GenerateVariantsOnUploadUseCase: newInertGenerateOnUploadUseCase(),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(imageRepo, articleRepo, userRepo, newMockVariantFileRepo(), plainSites(), plainAuthors()),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(imageRepo, articleRepo, userRepo, newMockVariantFileRepo(), plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(imageRepo, discardRecorder),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(imageRepo, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockArticleRepo()),
|
||||
MergeMediaUseCase: useCases.NewMergeMediaUseCase(imageRepo, articleRepo, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors()),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(imageRepo, articleRepo, plainSites(), plainAuthors()),
|
||||
MergeMediaUseCase: useCases.NewMergeMediaUseCase(imageRepo, articleRepo, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(imageRepo, articleRepo, plainSites(), plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ func TestSeoDataCleanerCompensationFailuresAreLogged(t *testing.T) {
|
||||
e.updateArticle(9, func(a *article.Article) { a.Body = e.bodyUsing(6) })
|
||||
buffer := captureLog(t)
|
||||
flaky := &seoFlakyBodyArticles{mockArticleRepo: e.articles}
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, flaky, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors)
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, flaky, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
_, err := merge.Merge(t.Context(), 5, []uint32{6})
|
||||
|
||||
@@ -330,7 +330,7 @@ func TestSeoDataCleanerCaseInsensitiveBodyMatches(t *testing.T) {
|
||||
e := newSeoEnv(t)
|
||||
seed(e)
|
||||
before := e.stored(9)
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, seoCaseInsensitiveBodyArticles{e.articles}, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors)
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, seoCaseInsensitiveBodyArticles{e.articles}, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
result, err := merge.Merge(t.Context(), 5, []uint32{6})
|
||||
|
||||
@@ -342,7 +342,7 @@ func TestSeoDataCleanerCaseInsensitiveBodyMatches(t *testing.T) {
|
||||
t.Run("Usage only lists real body references", func(t *testing.T) {
|
||||
e := newSeoEnv(t)
|
||||
seed(e)
|
||||
usage := useCases.NewGetMediaUsageUseCase(e.images, seoCaseInsensitiveBodyArticles{e.articles}, e.sites, e.authors)
|
||||
usage := useCases.NewGetMediaUsageUseCase(e.images, seoCaseInsensitiveBodyArticles{e.articles}, e.sites, e.authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
items, err := usage.Usage(6)
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ func TestSeoDataMediaMerge(t *testing.T) {
|
||||
seedDuplicates(e)
|
||||
e.updateArticle(8, func(a *article.Article) { a.CoverImageID = idPointer(6) })
|
||||
e.updateArticle(9, func(a *article.Article) { a.SocialImageID = idPointer(6) })
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, e.articles, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors)
|
||||
merge := useCases.NewMergeMediaUseCase(e.images, e.articles, newMockImageStorage(), newMockVariantFileRepo(), discardRecorder, e.sites, e.authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
|
||||
result, err := merge.Merge(testCtx, 5, []uint32{6})
|
||||
|
||||
|
||||
@@ -53,17 +53,17 @@ func newSeoEnv(t *testing.T) *seoEnv {
|
||||
}
|
||||
recorder := p.audit.recorder
|
||||
renderer := &fakeBodyRenderer{}
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors)
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c := p.container
|
||||
c.CreateArticleUseCase = useCases.NewCreateArticleUseCase(p.articles, renderer, recorder, sites, authors)
|
||||
c.UpdateArticleUseCase = useCases.NewUpdateArticleUseCase(p.articles, renderer, recorder, sites, authors, p.images)
|
||||
c.ListArticlesUseCase = useCases.NewListArticlesUseCase(p.articles, renderer, sites)
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, authors)
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.DeleteMediaUseCase = useCases.NewDeleteMediaUseCase(p.images, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors, p.articles)
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, authors)
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.MergeMediaUseCase = mergeMedia
|
||||
c.RunPurgeUseCase = useCases.NewRunPurgeUseCase(p.images, p.articles, mergeMedia, newMockPurgeSettingsRepo(), func() time.Time { return p.now }, log.New(io.Discard, "", 0), recorder, sites, authors)
|
||||
c.PreviewPurgeUseCase = useCases.NewPreviewPurgeUseCase(p.images, p.articles, sites, authors)
|
||||
c.PreviewPurgeUseCase = useCases.NewPreviewPurgeUseCase(p.images, p.articles, sites, authors, newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.ListSitesUseCase = useCases.NewListSitesUseCase(sites)
|
||||
c.CreateSiteUseCase = useCases.NewCreateSiteUseCase(sites, p.images, recorder)
|
||||
c.UpdateSiteUseCase = useCases.NewUpdateSiteUseCase(sites, p.images, recorder)
|
||||
|
||||
@@ -298,15 +298,15 @@ func newSiteEnv(t *testing.T) *siteEnv {
|
||||
sites := newMockSiteRepo(p.articles)
|
||||
recorder := p.audit.recorder
|
||||
renderer := &fakeBodyRenderer{}
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, plainAuthors())
|
||||
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c := p.container
|
||||
c.CreateArticleUseCase = useCases.NewCreateArticleUseCase(p.articles, renderer, recorder, sites, plainAuthors())
|
||||
c.UpdateArticleUseCase = useCases.NewUpdateArticleUseCase(p.articles, renderer, recorder, sites, plainAuthors(), p.images)
|
||||
c.ListArticlesUseCase = useCases.NewListArticlesUseCase(p.articles, renderer, sites)
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, plainAuthors())
|
||||
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.DeleteMediaUseCase = useCases.NewDeleteMediaUseCase(p.images, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, plainAuthors(), p.articles)
|
||||
c.MergeMediaUseCase = mergeMedia
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, plainAuthors())
|
||||
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, plainAuthors(), newMockContentTypeRepo(), newMockEntryRepo())
|
||||
c.RunPurgeUseCase = useCases.NewRunPurgeUseCase(p.images, p.articles, mergeMedia, newMockPurgeSettingsRepo(), func() time.Time { return p.now }, log.New(io.Discard, "", 0), recorder, sites, plainAuthors())
|
||||
c.ListSitesUseCase = useCases.NewListSitesUseCase(sites)
|
||||
c.CreateSiteUseCase = useCases.NewCreateSiteUseCase(sites, p.images, recorder)
|
||||
|
||||
Reference in new issue
Block a user