feat: media duplicate purge, part 1 of 3: persisted purge settings and the purge API
This commit is contained in:
1 parent
161ddc0f7d
commit
e82d0ce254
25 files changed
+2459
-41
No files matched your search
@@ -0,0 +1,129 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
domainPurge "RenewCMS/internal/domain/purge"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
persistence "RenewCMS/internal/infrastructure/persistence/repositories"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPurgeRoleGuardIsAnAllowlist(t *testing.T) {
|
||||
for _, role := range []string{"OWNER", "Admin", "superuser"} {
|
||||
for _, route := range []struct{ method, path string }{
|
||||
{http.MethodGet, "/v1/media/purge-settings"},
|
||||
{http.MethodPatch, "/v1/media/purge-settings"},
|
||||
{http.MethodGet, "/v1/media/purge-preview"},
|
||||
{http.MethodPost, "/v1/media/purge"},
|
||||
} {
|
||||
t.Run(role+" "+route.method+" "+route.path, func(t *testing.T) {
|
||||
env := newPurgeEnv(t)
|
||||
env.users.users[20] = user.User{ID: 20, Username: "other", IsVerified: true, Role: user.Role(role)}
|
||||
|
||||
rec := env.send(t, route.method, route.path, "", 20)
|
||||
|
||||
expectRaw(t, rec, 403, apperr.OwnerOrAdminRequiredErrorMessage+"\n")
|
||||
if env.purgeSettings.getCalls != 0 || env.purgeSettings.saveCalls != 0 {
|
||||
t.Errorf("expected no purge setting access")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPurgeLayering(t *testing.T) {
|
||||
list := func(t *testing.T, format string, pkg string) []string {
|
||||
t.Helper()
|
||||
output, err := exec.Command("go", "list", "-tags", "nofrontend", "-f", format, pkg).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("go list failed: %v: %s", err, output)
|
||||
}
|
||||
return strings.Fields(string(output))
|
||||
}
|
||||
|
||||
t.Run("internal/domain/purge imports only the stdlib and other domain packages", func(t *testing.T) {
|
||||
for _, imported := range list(t, `{{join .Imports "\n"}}`, "RenewCMS/internal/domain/purge") {
|
||||
if strings.Contains(imported, ".") && !strings.HasPrefix(imported, "RenewCMS/internal/domain/") {
|
||||
t.Errorf("unexpected import %s", imported)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("the use cases import no http, chi, gorm, persistence, mailer, models or api package", func(t *testing.T) {
|
||||
for _, imported := range list(t, `{{join .Imports "\n"}}`, "RenewCMS/internal/infrastructure/useCases") {
|
||||
for _, forbidden := range []string{"net/http", "go-chi", "gorm", "persistence", "mailer", "RenewCMS/api"} {
|
||||
if strings.Contains(imported, forbidden) {
|
||||
t.Errorf("useCases must not import %s", imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPurgeSettingsRepositoryRoundTrip(t *testing.T) {
|
||||
newRepo := func(t *testing.T) *persistence.PurgeSettingsRepository {
|
||||
t.Helper()
|
||||
db := openInMemoryDb(t)
|
||||
if err := db.AutoMigrate(&models.PurgeSettings{}); err != nil {
|
||||
t.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
return persistence.NewPurgeSettingsRepository(db)
|
||||
}
|
||||
|
||||
t.Run("Nothing saved yields the defaults", func(t *testing.T) {
|
||||
got, err := newRepo(t).Get()
|
||||
|
||||
if err != nil || !reflect.DeepEqual(got, domainPurge.DefaultSettings()) {
|
||||
t.Errorf("expected defaults, got %+v, %v", got, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Null and set timestamps survive a save", func(t *testing.T) {
|
||||
repo := newRepo(t)
|
||||
withNulls := domainPurge.Settings{AutoPurgeEnabled: true, AutoPurgeIntervalDays: 7}
|
||||
if err := repo.Save(withNulls); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := repo.Get()
|
||||
if err != nil || !reflect.DeepEqual(got, withNulls) {
|
||||
t.Errorf("expected %+v, got %+v, %v", withNulls, got, err)
|
||||
}
|
||||
|
||||
last := time.Date(2026, 9, 1, 9, 0, 0, 0, time.UTC)
|
||||
enabled := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
|
||||
full := domainPurge.Settings{AutoPurgeEnabled: true, AutoPurgeIntervalDays: 365, LastPurgeAt: &last, LastPurgeMerged: 3, LastPurgeArticlesUpdated: 1, EnabledAt: &enabled}
|
||||
if err := repo.Save(full); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = repo.Get()
|
||||
if err != nil || got.LastPurgeAt == nil || !got.LastPurgeAt.Equal(last) || got.EnabledAt == nil || !got.EnabledAt.Equal(enabled) || got.LastPurgeMerged != 3 || got.LastPurgeArticlesUpdated != 1 || got.AutoPurgeIntervalDays != 365 {
|
||||
t.Errorf("unexpected round trip %+v, %v", got, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Repeated saves keep a single row", func(t *testing.T) {
|
||||
db := openInMemoryDb(t)
|
||||
if err := db.AutoMigrate(&models.PurgeSettings{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := persistence.NewPurgeSettingsRepository(db)
|
||||
for days := 1; days <= 3; days++ {
|
||||
if err := repo.Save(domainPurge.Settings{AutoPurgeIntervalDays: days}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
db.Model(&models.PurgeSettings{}).Count(&count)
|
||||
|
||||
if count != 1 {
|
||||
t.Errorf("expected a single row, got %d", count)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user