feat: image variants engine
This commit is contained in:
1 parent
f0845de643
commit
f1d56ef50e
80 files changed
+7312
-84
No files matched your search
@@ -0,0 +1,204 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/handlers/staticimages"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
domain "RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/config"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
variantOwnerId uint32 = 1
|
||||
variantAdminId uint32 = 2
|
||||
variantPlainUserId uint32 = 3
|
||||
variantUnverifiedId uint32 = 4
|
||||
variantStaleId uint32 = 5
|
||||
variantDefaultCap int64 = 10485760
|
||||
variantDefaultPixels int64 = 50000000
|
||||
variantDefaultRenders = 4
|
||||
variantSourceName = "a1b2.png"
|
||||
)
|
||||
|
||||
type variantEnv struct {
|
||||
t *testing.T
|
||||
users *mockUserRepo
|
||||
images *mockImageRepo
|
||||
variants *mockVariantRepo
|
||||
files *mockVariantFileRepo
|
||||
storage *mockVariantStorage
|
||||
renderer *countingRenderer
|
||||
audit *activityHarness
|
||||
token *jwtauth.JWTAuth
|
||||
now time.Time
|
||||
admin http.Handler
|
||||
public http.Handler
|
||||
}
|
||||
|
||||
func newVariantEnv(t *testing.T) *variantEnv {
|
||||
t.Helper()
|
||||
return newVariantEnvWith(t, newCountingRenderer(), variantDefaultCap)
|
||||
}
|
||||
|
||||
func newVariantEnvWith(t *testing.T, renderer *countingRenderer, maxSourceBytes int64) *variantEnv {
|
||||
t.Helper()
|
||||
return newVariantEnvLimits(t, renderer, useCases.ServeVariantLimits{MaxSourceBytes: maxSourceBytes, MaxSourcePixels: variantDefaultPixels, MaxConcurrentRenders: variantDefaultRenders})
|
||||
}
|
||||
|
||||
func newVariantEnvLimits(t *testing.T, renderer *countingRenderer, limits useCases.ServeVariantLimits) *variantEnv {
|
||||
t.Helper()
|
||||
validator.InitValidator()
|
||||
env := &variantEnv{
|
||||
t: t,
|
||||
users: newMockUserRepo(),
|
||||
images: newMockImageRepo(),
|
||||
variants: newMockVariantRepo(),
|
||||
files: newMockVariantFileRepo(),
|
||||
storage: newMockVariantStorage(),
|
||||
renderer: renderer,
|
||||
audit: newActivityHarness(t),
|
||||
token: jwtauth.New("HS256", []byte("test-secret"), nil),
|
||||
now: time.Date(2026, time.October, 8, 9, 0, 0, 0, time.UTC),
|
||||
}
|
||||
env.users.users[variantOwnerId] = user.User{ID: variantOwnerId, Username: "marta", DisplayName: "Marta Alves", IsVerified: true, Role: user.RoleOwner}
|
||||
env.users.users[variantAdminId] = user.User{ID: variantAdminId, Username: "dev", IsVerified: true, Role: user.RoleAdmin}
|
||||
env.users.users[variantPlainUserId] = user.User{ID: variantPlainUserId, Username: "pat", IsVerified: true}
|
||||
env.users.users[variantUnverifiedId] = user.User{ID: variantUnverifiedId, Username: "quinn", IsVerified: false, Role: user.RoleAdmin}
|
||||
env.users.users[variantStaleId] = user.User{ID: variantStaleId, Username: "stale", IsVerified: true, Role: user.RoleAdmin, TokenVersion: 1}
|
||||
|
||||
container := env.container(limits)
|
||||
admin := chi.NewRouter()
|
||||
admin.Mount("/v1", api.InitBackendRoutes(container, config.Settings{}))
|
||||
env.admin = admin
|
||||
|
||||
public := chi.NewRouter()
|
||||
public.Use(chimiddleware.GetHead)
|
||||
public.Mount("/static/uploadedImages", staticimages.NewHandler(container.OpenMediaUseCase, container.ServeVariantUseCase))
|
||||
env.public = public
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *variantEnv) container(limits useCases.ServeVariantLimits) *api.UseCases {
|
||||
clock := func() time.Time { return e.now }
|
||||
recorder := e.audit.recorder
|
||||
return &api.UseCases{
|
||||
Token: e.token,
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(e.users),
|
||||
OpenMediaUseCase: useCases.NewOpenMediaUseCase(e.storage),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(e.images, e.storage, e.files, recorder),
|
||||
MergeMediaUseCase: useCases.NewMergeMediaUseCase(e.images, newMockArticleRepo(), e.storage, e.files, recorder),
|
||||
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),
|
||||
SetVariantEnabledUseCase: useCases.NewSetVariantEnabledUseCase(e.variants, e.files, clock, recorder),
|
||||
DeleteVariantUseCase: useCases.NewDeleteVariantUseCase(e.variants, e.files, e.storage, recorder),
|
||||
PurgeVariantUseCase: useCases.NewPurgeVariantUseCase(e.variants, e.files, e.storage, recorder),
|
||||
ServeVariantUseCase: useCases.NewServeVariantUseCase(e.variants, e.files, e.images, e.storage, e.renderer, limits),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *variantEnv) cookie(userId uint32, tokenVersion uint32) *http.Cookie {
|
||||
e.t.Helper()
|
||||
_, signed, err := e.token.Encode(map[string]any{"user_id": userId, "token_version": tokenVersion})
|
||||
if err != nil {
|
||||
e.t.Fatalf("failed to encode jwt: %v", err)
|
||||
}
|
||||
return &http.Cookie{Name: "jwt", Value: signed}
|
||||
}
|
||||
|
||||
func (e *variantEnv) send(method string, path string, body string, userId uint32) *httptest.ResponseRecorder {
|
||||
e.t.Helper()
|
||||
request := httptest.NewRequest(method, path, strings.NewReader(body))
|
||||
if body != "" {
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if userId != 0 {
|
||||
request.AddCookie(e.cookie(userId, 0))
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
e.admin.ServeHTTP(recorder, request)
|
||||
return recorder
|
||||
}
|
||||
|
||||
func (e *variantEnv) sendAsAdmin(method string, path string, body string) *httptest.ResponseRecorder {
|
||||
e.t.Helper()
|
||||
return e.send(method, path, body, variantAdminId)
|
||||
}
|
||||
|
||||
func (e *variantEnv) get(path string, header http.Header) *httptest.ResponseRecorder {
|
||||
e.t.Helper()
|
||||
request := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
for name, values := range header {
|
||||
request.Header[name] = values
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
e.public.ServeHTTP(recorder, request)
|
||||
return recorder
|
||||
}
|
||||
|
||||
func (e *variantEnv) seedSource(storedName string, content []byte) {
|
||||
e.storage.seed(storedName, content)
|
||||
if known, _ := e.images.ExistsByStoredName(storedName); !known {
|
||||
e.seedImage(e.images.nextID+1, storedName)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *variantEnv) seedImage(id uint32, storedName string) domain.Image {
|
||||
image := domain.Image{ID: id, Path: domain.PublicPath(storedName), Filename: storedName, ContentType: "image/png", SHA256: "same"}
|
||||
e.images.images[id] = image
|
||||
if id > e.images.nextID {
|
||||
e.images.nextID = id
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
func (e *variantEnv) seedThumb() domain.Variant {
|
||||
return seedVariant(e.variants, "thumb", 400, 300, domain.FitCover, domain.FormatJPEG, 80)
|
||||
}
|
||||
|
||||
func (e *variantEnv) seedPhoto() {
|
||||
e.seedSource(variantSourceName, encodePNG(e.t, variantSolidImage(1000, 800, variantRed)))
|
||||
}
|
||||
|
||||
func (e *variantEnv) events() int {
|
||||
e.t.Helper()
|
||||
return len(e.audit.all(e.t))
|
||||
}
|
||||
|
||||
func (e *variantEnv) lastEvent() (string, map[string]any) {
|
||||
e.t.Helper()
|
||||
events := e.audit.all(e.t)
|
||||
if len(events) == 0 {
|
||||
e.t.Fatal("no event recorded")
|
||||
}
|
||||
encoded, _ := json.Marshal(events[0])
|
||||
var decoded map[string]any
|
||||
_ = json.Unmarshal(encoded, &decoded)
|
||||
return events[0].Action, decoded
|
||||
}
|
||||
|
||||
func decodeBody(t *testing.T, recorder *httptest.ResponseRecorder) map[string]any {
|
||||
t.Helper()
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &decoded); err != nil {
|
||||
t.Fatalf("body is not a json object: %q", recorder.Body.String())
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
func bearerKeyHeader() http.Header {
|
||||
return http.Header{"Authorization": []string{"Bearer " + apikey.RawKeyPrefix + "validkey"}}
|
||||
}
|
||||
Reference in new issue
Block a user