Files
RenewCMS/test/media_purge_test.go
T

186 lines
6.0 KiB
Go

package test
import (
"RenewCMS/internal/domain/purge"
"RenewCMS/internal/infrastructure/useCases"
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"log"
"reflect"
"sync"
"testing"
"time"
)
func TestUpdatePurgeSettingsRejectsOutOfRangeIntervals(t *testing.T) {
for _, days := range []int{0, 366} {
repo := newMockPurgeSettingsRepo()
update := useCases.NewUpdatePurgeSettingsUseCase(repo, time.Now)
_, err := update.Update(useCases.UpdatePurgeSettingsCommand{AutoPurgeIntervalDays: &days})
if !errors.Is(err, purge.ErrInvalidPurgeInterval) {
t.Errorf("interval %d: expected ErrInvalidPurgeInterval, got %v", days, err)
}
if repo.saveCalls != 0 {
t.Errorf("interval %d: expected Save not to be called", days)
}
}
}
func TestRunPurgeDistinguishesBusyFromConflict(t *testing.T) {
newRun := func(env *mediaEnv) *useCases.RunPurgeUseCase {
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
return useCases.NewRunPurgeUseCase(env.images, env.articles, merge, env.purgeSettings, func() time.Time { return env.clock }, log.New(&bytes.Buffer{}, "", 0), discardRecorder, plainSites(), plainAuthors())
}
t.Run("a non-matching fingerprint is a conflict and writes nothing", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2)
env.useImages(10, 1)
wrong := "wrong"
_, err := newRun(env).Run(testCtx, &wrong)
if !errors.Is(err, purge.ErrPurgeConflict) || errors.Is(err, purge.ErrPurgeBusy) {
t.Errorf("expected only ErrPurgeConflict, got %v", err)
}
if env.purgeSettings.saveCalls != 0 || len(env.images.deleteCalls) != 0 {
t.Errorf("expected no write")
}
})
t.Run("a run while another holds the guard is busy and writes nothing", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2)
env.useImages(10, 1)
entered := make(chan struct{})
release := make(chan struct{})
var once sync.Once
env.images.onDelete = func(uint32) {
once.Do(func() {
close(entered)
<-release
})
}
run := newRun(env)
done := make(chan error, 1)
go func() {
_, err := run.Run(testCtx, nil)
done <- err
}()
<-entered
savesBefore := env.purgeSettings.saveCalls
_, err := run.Run(testCtx, nil)
if !errors.Is(err, purge.ErrPurgeBusy) || errors.Is(err, purge.ErrPurgeConflict) {
t.Errorf("expected only ErrPurgeBusy, got %v", err)
}
if env.purgeSettings.saveCalls != savesBefore {
t.Errorf("expected no write from the busy call")
}
close(release)
if err := <-done; err != nil {
t.Errorf("expected the first run to finish, got %v", err)
}
})
t.Run("a scheduler run without a fingerprint gets the result and the persistence", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2)
env.useImages(10, 1)
result, err := newRun(env).Run(testCtx, nil)
if err != nil || result != (purge.RunResult{GroupsTotal: 1, GroupsPurged: 1, Merged: 1}) {
t.Errorf("unexpected result %+v, %v", result, err)
}
if env.purgeSettings.settings.LastPurgeAt == nil || env.purgeSettings.settings.LastPurgeMerged != 1 {
t.Errorf("expected last_purge_* persisted")
}
})
}
func TestPurgePlanOrderingAndFingerprint(t *testing.T) {
t.Run("groups are ordered by the kept image id whatever the order they were found in", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 6)
env.seedGroup(hashB, 2, 5)
env.seedGroup(hashC, 3, 4)
env.useImages(10, 6)
env.useImages(11, 5)
env.useImages(12, 3)
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()).Preview()
var kept []uint32
for _, group := range preview.List {
kept = append(kept, group.KeepID)
}
if err != nil || !reflect.DeepEqual(kept, []uint32{3, 5, 6}) {
t.Errorf("expected kept ids [3 5 6], got %v, %v", kept, err)
}
})
t.Run("the fingerprint hashes the kept id and the comma separated merged ids of each group", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2, 3)
env.seedGroup(hashB, 4, 5)
env.useImages(10, 1, 2, 3)
env.useImages(11, 5)
preview, err := useCases.NewPreviewPurgeUseCase(env.images, env.articles, plainSites(), plainAuthors()).Preview()
sum := sha256.Sum256([]byte("1:2,3;5:4;"))
if err != nil || preview.Fingerprint != hex.EncodeToString(sum[:]) {
t.Errorf("unexpected fingerprint %q, %v", preview.Fingerprint, err)
}
})
}
func TestMergeMediaExposesTheRewrittenArticleIds(t *testing.T) {
t.Run("Existing merges report the same counts and now also the ids", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2)
env.useImages(1, 2)
env.useImages(2, 1, 2)
env.useImages(3, 1)
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
result, err := merge.Merge(testCtx, 1, []uint32{2})
if err != nil || result.Merged != 1 || result.ArticlesUpdated != 2 || !reflect.DeepEqual(result.UpdatedArticleIDs, []uint32{1, 2}) {
t.Errorf("unexpected result %+v, %v", result, err)
}
})
t.Run("A rolled-back merge returns no ids", func(t *testing.T) {
env := newPurgeEnv(t)
env.seedGroup(hashA, 1, 2)
env.useImages(1, 2)
env.useImages(2, 2)
env.articles.failUpdateBodyFor = map[uint32]error{2: errRepositoryFailure}
merge := useCases.NewMergeMediaUseCase(env.images, env.articles, env.storage, newMockVariantFileRepo(), discardRecorder, plainSites(), plainAuthors())
result, err := merge.Merge(testCtx, 1, []uint32{2})
if err == nil || result.Merged != 0 || result.ArticlesUpdated != 0 || len(result.UpdatedArticleIDs) != 0 {
t.Errorf("unexpected result %+v, %v", result, err)
}
})
}
func TestPurgeSettingsNextPurgeAt(t *testing.T) {
t.Run("is null when off or when never enabled", func(t *testing.T) {
if purge.DefaultSettings().NextPurgeAt() != nil {
t.Errorf("expected nil")
}
if (purge.Settings{AutoPurgeEnabled: true, AutoPurgeIntervalDays: 3}).NextPurgeAt() != nil {
t.Errorf("expected nil without enabled_at")
}
})
}