feat: image variants per image
This commit is contained in:
1 parent
f567866161
commit
951fef18ba
92 files changed
+8695
-413
No files matched your search
@@ -0,0 +1,240 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/image"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
tooLargeReason = "The source image is too large to generate this variant."
|
||||
genericReason = "Something went wrong on our side. Please try again."
|
||||
)
|
||||
|
||||
func (e *variantEnv) seedFiveEnabled() {
|
||||
seedVariant(e.variants, "card", 800, 0, domain.FitInside, domain.FormatJPEG, 80)
|
||||
seedVariant(e.variants, "hero", 1200, 630, domain.FitCover, domain.FormatJPEG, 82)
|
||||
seedVariant(e.variants, "og", 1200, 630, domain.FitCover, domain.FormatJPEG, 80)
|
||||
seedVariant(e.variants, "square", 600, 600, domain.FitCover, domain.FormatJPEG, 85)
|
||||
seedVariant(e.variants, "thumb", 400, 300, domain.FitCover, domain.FormatJPEG, 80)
|
||||
e.seedPerImage(1, "north-wall.jpg", "a1b2.png", "image/png", 1000, 800, solidPNG(e.t, 1000, 800))
|
||||
}
|
||||
|
||||
func (e *variantEnv) failHero(cause error) {
|
||||
heroFingerprint := e.variants.stored("hero").Settings().Fingerprint()
|
||||
e.renderer.failFor = func(settings domain.VariantSettings) error {
|
||||
if settings.Fingerprint() == heroFingerprint {
|
||||
return cause
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func assertImageTarget(t *testing.T, event map[string]any, id string, name string) {
|
||||
t.Helper()
|
||||
target := event["target"].(map[string]any)
|
||||
if target["type"] != "image" || target["id"] != id || target["name"] != name || event["category"] != "media" {
|
||||
t.Errorf("target = %v, category = %v", target, event["category"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerImageVariantAudit(t *testing.T) {
|
||||
t.Run("Generate all is recorded with the counts", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
|
||||
env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
|
||||
action, event := env.lastEvent()
|
||||
params := event["params"].(map[string]any)
|
||||
causes, _ := params["causes"].([]any)
|
||||
if env.events() != 1 || action != "media.variant_generate_all" || event["outcome"] != "success" || params["generated"] != 5.0 || params["failed"] != 0.0 || causes == nil || len(causes) != 0 {
|
||||
t.Errorf("event = %v", event)
|
||||
}
|
||||
assertImageTarget(t, event, "1", "north-wall.jpg")
|
||||
})
|
||||
|
||||
t.Run("A partial failure is a success that names its causes", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.failHero(domain.ErrSourceTooLarge)
|
||||
|
||||
env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
|
||||
_, event := env.lastEvent()
|
||||
params := event["params"].(map[string]any)
|
||||
causes := params["causes"].([]any)
|
||||
if env.events() != 1 || event["outcome"] != "success" || params["generated"] != 4.0 || params["failed"] != 1.0 || len(causes) != 1 || causes[0] != "too_large" {
|
||||
t.Errorf("event = %v", event)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Every variant failing is a failed event with a plain reason", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.renderer.failFor = func(domain.VariantSettings) error { return domain.ErrSourceTooLarge }
|
||||
|
||||
recorder := env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
|
||||
body := decodeBody(t, recorder)
|
||||
_, event := env.lastEvent()
|
||||
if recorder.Code != 200 || body["generated"] != 0.0 || body["failed"] != 5.0 || env.events() != 1 || event["action"] != "media.variant_generate_all" || event["outcome"] != "failed" || event["failure_reason"] != tooLargeReason {
|
||||
t.Errorf("response = %d %q, event = %v", recorder.Code, recorder.Body.String(), event)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The failure reasons of the other causes are plain sentences", func(t *testing.T) {
|
||||
causes := map[string]error{
|
||||
"Variants aren't generated for this format.": domain.ErrVariantSourceUnsupported,
|
||||
"The source image can't be read, so this variant can't be generated.": domain.ErrVariantSourceUnreadable,
|
||||
genericReason: errors.New("boom"),
|
||||
}
|
||||
for reason, cause := range causes {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.renderer.failFor = func(domain.VariantSettings) error { return cause }
|
||||
|
||||
env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
|
||||
if _, event := env.lastEvent(); event["failure_reason"] != reason {
|
||||
t.Errorf("reason for %v = %v, want %q", cause, event["failure_reason"], reason)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
noEvent := []struct {
|
||||
name string
|
||||
prepare func(env *variantEnv)
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{"all five are already generated", func(env *variantEnv) {
|
||||
env.seedFiveEnabled()
|
||||
env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
}, http.MethodPost, "/v1/media/1/variants"},
|
||||
{"no variant is enabled", func(env *variantEnv) { env.seedPerImage(1, "north-wall.jpg", "a1b2.png", "image/png", 10, 10, nil) }, http.MethodPost, "/v1/media/1/variants"},
|
||||
{"an SVG", func(env *variantEnv) {
|
||||
env.seedFiveEnabled()
|
||||
env.seedPerImage(1, "logo.svg", "e5f6.svg", "image/svg+xml", 0, 0, nil)
|
||||
}, http.MethodPost, "/v1/media/1/variants"},
|
||||
{"a single generation", func(env *variantEnv) { env.seedFiveEnabled() }, http.MethodPost, "/v1/media/1/variants/thumb"},
|
||||
{"an unknown image", func(env *variantEnv) { env.seedFiveEnabled() }, http.MethodPost, "/v1/media/9999/variants"},
|
||||
{"a listing", func(env *variantEnv) { env.seedFiveEnabled() }, http.MethodGet, "/v1/media/1/variants"},
|
||||
}
|
||||
for _, row := range noEvent {
|
||||
t.Run("Nothing was done, nothing is recorded: "+row.name, func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
row.prepare(env)
|
||||
before := env.events()
|
||||
|
||||
env.send(row.method, row.path, "", variantOwnerId)
|
||||
|
||||
if env.events() != before {
|
||||
t.Errorf("events = %d, want %d", env.events(), before)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Nothing was done, nothing is recorded: a public generation", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
|
||||
recorder := env.get("/static/uploadedImages/a1b2.png?v=thumb", nil)
|
||||
|
||||
if recorder.Code != 200 || env.events() != 0 {
|
||||
t.Errorf("status = %d, events = %d", recorder.Code, env.events())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Purging the files of one variant of an image is recorded", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.seedCurrentFile("thumb", "a1b2.png", 18432)
|
||||
|
||||
env.send(http.MethodDelete, "/v1/media/1/variants/thumb", "", variantOwnerId)
|
||||
|
||||
action, event := env.lastEvent()
|
||||
params := event["params"].(map[string]any)
|
||||
if env.events() != 1 || action != "media.variant_purge_image" || event["outcome"] != "success" || params["purged_files"] != 1.0 || params["purged_bytes"] != 18432.0 {
|
||||
t.Errorf("event = %v", event)
|
||||
}
|
||||
assertImageTarget(t, event, "1", "north-wall.jpg")
|
||||
})
|
||||
|
||||
purgeNoEvent := []struct{ name, path string }{
|
||||
{"nothing is generated", "/v1/media/1/variants/thumb"},
|
||||
{"the variant is unknown", "/v1/media/1/variants/missing"},
|
||||
{"the image is unknown", "/v1/media/9999/variants/thumb"},
|
||||
}
|
||||
for _, row := range purgeNoEvent {
|
||||
t.Run("A purge that removes nothing, or finds nothing, is not recorded: "+row.name, func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
|
||||
env.send(http.MethodDelete, row.path, "", variantOwnerId)
|
||||
|
||||
if env.events() != 0 {
|
||||
t.Errorf("events = %d", env.events())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("A purge that fails on storage is recorded as failed", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.seedCurrentFile("thumb", "a1b2.png", 18432)
|
||||
env.storage.failDelete["cached-thumb-a1b2.png"] = errVariantStorageFailure
|
||||
|
||||
recorder := env.send(http.MethodDelete, "/v1/media/1/variants/thumb", "", variantOwnerId)
|
||||
|
||||
action, event := env.lastEvent()
|
||||
if recorder.Code != 500 || env.events() != 1 || action != "media.variant_purge_image" || event["outcome"] != "failed" || event["failure_reason"] != genericReason {
|
||||
t.Errorf("status = %d, event = %v", recorder.Code, event)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A failing audit write never changes the response", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.audit.probe.appendErr = errDiskFull
|
||||
|
||||
recorder := env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
|
||||
if recorder.Code != 200 || decodeBody(t, recorder)["generated"] != 5.0 || env.files.rowCount() != 5 {
|
||||
t.Errorf("response = %d %q", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The new events read back through the existing activity API", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedFiveEnabled()
|
||||
env.send(http.MethodPost, "/v1/media/1/variants", "", variantOwnerId)
|
||||
env.send(http.MethodDelete, "/v1/media/1/variants/thumb", "", variantOwnerId)
|
||||
|
||||
recorder := env.send(http.MethodGet, "/v1/activity?category=media", "", variantOwnerId)
|
||||
|
||||
items := itemsOf(t, recorder)
|
||||
actions := []any{}
|
||||
for _, item := range items {
|
||||
actions = append(actions, item["action"])
|
||||
if item["target"].(map[string]any)["type"] != "image" || item["params"] == nil {
|
||||
t.Errorf("item = %v", item)
|
||||
}
|
||||
}
|
||||
if recorder.Code != 200 || len(items) != 2 || actions[0] != "media.variant_purge_image" || actions[1] != "media.variant_generate_all" {
|
||||
t.Errorf("response = %d %q", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An upload that generates files records only the upload", func(t *testing.T) {
|
||||
env := newVariantEnv(t)
|
||||
env.seedUploadVariants()
|
||||
|
||||
recorder := env.upload(uploadedFilename, "image/png", solidPNG(t, 1000, 800))
|
||||
|
||||
if action, _ := env.lastEvent(); recorder.Code != 201 || env.events() != 1 || action != "media.upload" {
|
||||
t.Errorf("status = %d, events = %d", recorder.Code, env.events())
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user