package test import ( "RenewCMS/api" validator "RenewCMS/api/pkg/validator" domainArticle "RenewCMS/internal/domain/article" domainImage "RenewCMS/internal/domain/image" "RenewCMS/internal/domain/user" "RenewCMS/internal/infrastructure/config" "RenewCMS/internal/infrastructure/useCases" "bytes" "encoding/json" "log" "net/http" "net/http/httptest" "strconv" "strings" "testing" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" ) const ( mediaVerifiedUserId uint32 = 1 mediaSecondUserId uint32 = 2 mediaUnverifiedUserId uint32 = 3 mediaStaleUserId uint32 = 4 ) type mediaEnv struct { articles *mockArticleRepo images *mockImageRepo variants *mockVariantFileRepo storage *mockImageStorage users *mockUserRepo purgeSettings *mockPurgeSettingsRepo clock time.Time logs *bytes.Buffer token *jwtauth.JWTAuth router http.Handler } func newMediaEnv(t *testing.T) *mediaEnv { t.Helper() validator.InitValidator() env := &mediaEnv{ articles: newMockArticleRepo(), images: newMockImageRepo(), variants: newMockVariantFileRepo(), storage: newMockImageStorage(), users: newMockUserRepo(), token: jwtauth.New("HS256", []byte("test-secret"), nil), purgeSettings: newMockPurgeSettingsRepo(), clock: time.Date(2026, time.October, 5, 9, 0, 0, 0, time.UTC), logs: &bytes.Buffer{}, } env.users.users[mediaVerifiedUserId] = user.User{ID: 1, Username: "ines", DisplayName: "Inès Duarte", IsVerified: true} env.users.users[mediaSecondUserId] = user.User{ID: 2, Username: "marta", IsVerified: true} 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()) now := func() time.Time { return env.clock } container := &api.UseCases{ Token: env.token, CreateArticleUseCase: useCases.NewCreateArticleUseCase(env.articles, &fakeBodyRenderer{}, discardRecorder, plainSites(), plainAuthors()), GetArticleUseCase: useCases.NewGetArticleUseCase(env.articles, &fakeBodyRenderer{}), ListArticlesUseCase: useCases.NewListArticlesUseCase(env.articles, &fakeBodyRenderer{}, plainSites()), UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(env.articles, &fakeBodyRenderer{}, discardRecorder, plainSites(), plainAuthors()), DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(env.articles, discardRecorder), GetUserUseCase: useCases.NewGetUserUseCase(env.users), 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()), RenameMediaUseCase: useCases.NewRenameMediaUseCase(env.images, discardRecorder), DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(env.images, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors()), MergeMediaUseCase: merge, GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(env.images, env.articles), GetPurgeSettingsUseCase: useCases.NewGetPurgeSettingsUseCase(env.purgeSettings), UpdatePurgeSettingsUseCase: useCases.NewUpdatePurgeSettingsUseCase(env.purgeSettings, now), PreviewPurgeUseCase: useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()), RunPurgeUseCase: useCases.NewRunPurgeUseCase(env.images, env.articles, merge, env.purgeSettings, now, log.New(env.logs, "", 0), discardRecorder, plainSites(), plainAuthors()), } router := chi.NewRouter() router.Mount("/v1", api.InitBackendRoutes(container, config.Settings{})) env.router = router return env } func (e *mediaEnv) cookie(t *testing.T, userId uint32, tokenVersion uint32) *http.Cookie { t.Helper() _, tokenString, err := e.token.Encode(map[string]any{"user_id": userId, "token_version": tokenVersion}) if err != nil { t.Fatalf("failed to encode jwt: %v", err) } return &http.Cookie{Name: "jwt", Value: tokenString} } func (e *mediaEnv) serve(t *testing.T, req *http.Request, userId uint32) *httptest.ResponseRecorder { t.Helper() if userId != 0 { req.AddCookie(e.cookie(t, userId, 0)) } rec := httptest.NewRecorder() e.router.ServeHTTP(rec, req) return rec } func (e *mediaEnv) send(t *testing.T, method string, path string, body string, userId uint32) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(method, path, strings.NewReader(body)) if body != "" { req.Header.Set("Content-Type", "application/json") } return e.serve(t, req, userId) } func (e *mediaEnv) seedImage(id uint32, filename string, size int64, contentType string, width int, height int, hash string, uploadedBy uint32) { extension := map[string]string{"image/jpeg": ".jpeg", "image/svg+xml": ".svg", "image/png": ".png"}[contentType] e.images.images[id] = domainImage.Image{ ID: id, Path: "/static/uploadedImages/img-" + strconv.Itoa(int(id)) + "-" + extension, Filename: filename, Size: size, ContentType: contentType, Width: width, Height: height, SHA256: hash, UploadedByID: uploadedBy, CreatedAt: mockImageEpoch.Add(time.Duration(id) * time.Minute), } if id > e.images.nextID { e.images.nextID = id } } func (e *mediaEnv) urlOf(id uint32) string { return e.images.images[id].Path } func (e *mediaEnv) seedArticle(id uint32, title string, online bool, body string) { e.articles.articles[id] = domainArticle.Article{ID: id, Title: title, IsOnline: online, Body: body} if id > e.articles.nextID { e.articles.nextID = id } } func (e *mediaEnv) bodies() map[uint32]string { result := map[uint32]string{} for id, a := range e.articles.articles { result[id] = a.Body } return result } func (e *mediaEnv) imageIds() []uint32 { ids := make([]uint32, 0) for _, img := range e.images.sortedImages() { ids = append(ids, img.ID) } return ids } func decodeObject(t *testing.T, body string) map[string]any { t.Helper() var decoded map[string]any if err := json.Unmarshal([]byte(body), &decoded); err != nil { t.Fatalf("failed to decode %q: %v", body, err) } return decoded } func numberOf(value any) uint32 { number, _ := value.(float64) return uint32(number) }