279 lines
10 KiB
Go
279 lines
10 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/article"
|
|
"RenewCMS/internal/domain/audit"
|
|
domain "RenewCMS/internal/domain/image"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func eventsOf(env *variantEnv, action string) []audit.Event {
|
|
matching := []audit.Event{}
|
|
for _, event := range env.audit.all(env.t) {
|
|
if event.Action == action {
|
|
matching = append(matching, event)
|
|
}
|
|
}
|
|
return matching
|
|
}
|
|
|
|
func onlyEventOf(t *testing.T, env *variantEnv, action string) audit.Event {
|
|
t.Helper()
|
|
matching := eventsOf(env, action)
|
|
if len(matching) != 1 || env.events() != 1 {
|
|
t.Fatalf("%d %s events among %d", len(matching), action, env.events())
|
|
}
|
|
return matching[0]
|
|
}
|
|
|
|
func marshalled(t *testing.T, value any) string {
|
|
t.Helper()
|
|
encoded, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(encoded)
|
|
}
|
|
|
|
func assertVariantTarget(t *testing.T, event audit.Event, name string) {
|
|
t.Helper()
|
|
if got := marshalled(t, event.Target); got != `{"type":"image_variant","id":"`+name+`","name":"`+name+`"}` {
|
|
t.Errorf("target = %s", got)
|
|
}
|
|
}
|
|
|
|
func ownerHeroEnv(t *testing.T) *variantEnv {
|
|
t.Helper()
|
|
env := newVariantEnv(t)
|
|
seedVariant(env.variants, "hero", 1200, 630, domain.FitCover, domain.FormatWebP, 82)
|
|
seedVariantFiles(env.files, env.storage, "hero", "src", 244, 344000)
|
|
return env
|
|
}
|
|
|
|
func TestImageVariantsActivityLog(t *testing.T) {
|
|
t.Run("Creating a variant is recorded", func(t *testing.T) {
|
|
env := newVariantEnv(t)
|
|
|
|
env.send(http.MethodPost, "/v1/image-variants", `{"name": "hero", "width": 1200, "height": 630, "fit": "cover", "format": "webp", "quality": 82}`, variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_create")
|
|
assertVariantTarget(t, event, "hero")
|
|
if event.Outcome != audit.OutcomeSuccess || event.Category != audit.CategoryMedia || event.Source != audit.SourceWeb || event.Actor.Name != "Marta Alves" || *event.Actor.ID != variantOwnerId {
|
|
t.Errorf("event = %+v", event)
|
|
}
|
|
if got := marshalled(t, event.Params); got != `{"fit":"cover","format":"webp","height":630,"quality":82,"width":1200}` {
|
|
t.Errorf("params = %s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("Creating a width-only png variant records no quality and no height", func(t *testing.T) {
|
|
env := newVariantEnv(t)
|
|
|
|
env.send(http.MethodPost, "/v1/image-variants", `{"name": "logo", "width": 100, "format": "png"}`, variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_create")
|
|
if got := marshalled(t, event.Params); got != `{"fit":"cover","format":"png","width":100}` {
|
|
t.Errorf("params = %s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("Updating a variant is recorded with the diff and the purge count", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodPut, "/v1/image-variants/hero", `{"width": 1200, "height": 630, "fit": "cover", "format": "webp", "quality": 70}`, variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_update")
|
|
assertVariantTarget(t, event, "hero")
|
|
if got := marshalled(t, event.Changes); got != `[{"field":"quality","kind":"text","before":"82","after":"70"}]` {
|
|
t.Errorf("changes = %s", got)
|
|
}
|
|
if got := marshalled(t, event.Params); got != `{"purged_files":244}` {
|
|
t.Errorf("params = %s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("A removed dimension is recorded as auto", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodPut, "/v1/image-variants/hero", `{"width": 1200, "fit": "cover", "format": "webp", "quality": 82}`, variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_update")
|
|
if got := marshalled(t, event.Changes); got != `[{"field":"height","kind":"text","before":"630","after":"auto"}]` {
|
|
t.Errorf("changes = %s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("A switch-only update does not claim a purge", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodPut, "/v1/image-variants/hero", `{"width": 1200, "height": 630, "fit": "cover", "format": "webp", "quality": 82, "generate_on_upload": true}`, variantOwnerId)
|
|
|
|
if env.events() != 0 {
|
|
t.Errorf("events = %d", env.events())
|
|
}
|
|
})
|
|
|
|
t.Run("Disabling through a PUT is recorded as a disable and not as an update", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodPut, "/v1/image-variants/hero", `{"width": 1200, "height": 630, "fit": "cover", "format": "webp", "quality": 82, "enabled": false}`, variantOwnerId)
|
|
|
|
assertVariantTarget(t, onlyEventOf(t, env, "media.variant_disable"), "hero")
|
|
})
|
|
|
|
t.Run("Deleting a variant is recorded with the removed files", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodDelete, "/v1/image-variants/hero", "", variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_delete")
|
|
assertVariantTarget(t, event, "hero")
|
|
if got := marshalled(t, event.Params); got != `{"purged_bytes":83936000,"purged_files":244}` {
|
|
t.Errorf("params = %s", got)
|
|
}
|
|
})
|
|
|
|
for _, row := range []struct {
|
|
before, after bool
|
|
action string
|
|
}{{false, true, "media.variant_enable"}, {true, false, "media.variant_disable"}} {
|
|
t.Run("Enabling and disabling are recorded: "+row.action, func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
hero := env.variants.stored("hero")
|
|
hero.Enabled = row.before
|
|
env.variants.variants["hero"] = hero
|
|
payload := `{"enabled": false}`
|
|
if row.after {
|
|
payload = `{"enabled": true}`
|
|
}
|
|
|
|
env.send(http.MethodPatch, "/v1/image-variants/hero", payload, variantOwnerId)
|
|
|
|
assertVariantTarget(t, onlyEventOf(t, env, row.action), "hero")
|
|
})
|
|
}
|
|
|
|
t.Run("A PATCH to the current value records nothing", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
recorder := env.send(http.MethodPatch, "/v1/image-variants/hero", `{"enabled": true}`, variantOwnerId)
|
|
|
|
if recorder.Code != 200 || env.events() != 0 {
|
|
t.Errorf("status = %d, events = %d", recorder.Code, env.events())
|
|
}
|
|
})
|
|
|
|
t.Run("A refused create is recorded as failed", func(t *testing.T) {
|
|
env := newVariantEnv(t)
|
|
env.seedThumb()
|
|
|
|
env.send(http.MethodPost, "/v1/image-variants", `{"name": "thumb", "width": 10}`, variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_create")
|
|
assertVariantTarget(t, event, "thumb")
|
|
if event.Outcome != audit.OutcomeFailed || event.FailureReason == nil || *event.FailureReason != "A variant with that name already exists." {
|
|
t.Errorf("event = %+v", event)
|
|
}
|
|
})
|
|
|
|
nothing := []struct{ name, method, path, body string }{
|
|
{"validation failure", http.MethodPost, "/v1/image-variants", `{"name": "hero", "width": 9000}`},
|
|
{"unknown PUT", http.MethodPut, "/v1/image-variants/missing", `{"width": 100}`},
|
|
{"unknown DELETE", http.MethodDelete, "/v1/image-variants/missing", ""},
|
|
{"unknown PATCH", http.MethodPatch, "/v1/image-variants/missing", `{"enabled": true}`},
|
|
{"unknown purge", http.MethodPost, "/v1/image-variants/missing/purge", ""},
|
|
{"list", http.MethodGet, "/v1/image-variants", ""},
|
|
}
|
|
for _, row := range nothing {
|
|
t.Run("Requests that fail validation or find nothing record nothing: "+row.name, func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(row.method, row.path, row.body, variantOwnerId)
|
|
|
|
if env.events() != 0 {
|
|
t.Errorf("events = %d", env.events())
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("A public GET that generates a file records nothing", func(t *testing.T) {
|
|
env := servingEnv(t)
|
|
|
|
env.get(thumbURL, nil)
|
|
|
|
if env.events() != 0 {
|
|
t.Errorf("events = %d", env.events())
|
|
}
|
|
})
|
|
|
|
t.Run("Purging a variant's cache is recorded", func(t *testing.T) {
|
|
env := ownerHeroEnv(t)
|
|
|
|
env.send(http.MethodPost, "/v1/image-variants/hero/purge", "", variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_purge")
|
|
assertVariantTarget(t, event, "hero")
|
|
if got := marshalled(t, event.Params); event.Outcome != audit.OutcomeSuccess || got != `{"purged_bytes":83936000,"purged_files":244}` {
|
|
t.Errorf("params = %s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("Purging an empty cache records nothing", func(t *testing.T) {
|
|
env := newVariantEnv(t)
|
|
seedVariant(env.variants, "hero", 100, 0, domain.FitCover, domain.FormatJPEG, 80)
|
|
|
|
recorder := env.send(http.MethodPost, "/v1/image-variants/hero/purge", "", variantOwnerId)
|
|
|
|
if recorder.Code != 200 || env.events() != 0 {
|
|
t.Errorf("status = %d, events = %d", recorder.Code, env.events())
|
|
}
|
|
})
|
|
|
|
t.Run("A purge that fails on storage is recorded as failed", func(t *testing.T) {
|
|
env := newVariantEnv(t)
|
|
seedVariant(env.variants, "hero", 100, 0, domain.FitCover, domain.FormatJPEG, 80)
|
|
rows := seedVariantFiles(env.files, env.storage, "hero", "src", 3, 10)
|
|
env.storage.failDelete[rows[1].CachedName] = errVariantStorageFailure
|
|
|
|
recorder := env.send(http.MethodPost, "/v1/image-variants/hero/purge", "", variantOwnerId)
|
|
|
|
event := onlyEventOf(t, env, "media.variant_purge")
|
|
if recorder.Code != 500 || event.Outcome != audit.OutcomeFailed || event.FailureReason == nil || *event.FailureReason != "Something went wrong on our side. Please try again." {
|
|
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.audit.probe.appendErr = errors.New("audit store down")
|
|
|
|
recorder := env.send(http.MethodPost, "/v1/image-variants", `{"name": "hero", "width": 100}`, variantOwnerId)
|
|
|
|
if recorder.Code != 201 || env.variants.stored("hero").Name != "hero" {
|
|
t.Errorf("status = %d", recorder.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("The existing allowlist scenario is updated", func(t *testing.T) {
|
|
allowed := []string{"title", "online", "body", "filename", "display_name", "width", "height", "fit", "format", "quality"}
|
|
emitted := []audit.Change{}
|
|
emitted = append(emitted, audit.ArticleChanges(article.Article{Title: "a", Body: "x"}, article.Article{Title: "b", Body: "y", IsOnline: true})...)
|
|
before := domain.VariantSettings{Width: intPointer(1), Height: intPointer(2), Fit: domain.FitCover, Format: domain.FormatJPEG, Quality: intPointer(80)}
|
|
after := domain.VariantSettings{Width: intPointer(3), Fit: domain.FitInside, Format: domain.FormatPNG}
|
|
emitted = append(emitted, audit.VariantChanges(before, after)...)
|
|
|
|
for _, change := range emitted {
|
|
if !slices.Contains(allowed, change.Field) || change.Kind != audit.ChangeText && change.Kind != audit.ChangeBody {
|
|
t.Errorf("unexpected change %+v", change)
|
|
}
|
|
}
|
|
if len(emitted) != 8 {
|
|
t.Errorf("emitted %d changes", len(emitted))
|
|
}
|
|
})
|
|
}
|