241 lines
9.1 KiB
Go
241 lines
9.1 KiB
Go
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())
|
|
}
|
|
})
|
|
}
|