166 lines
6.7 KiB
Go
166 lines
6.7 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// fakeStorageService is a minimal StorageService double recording what
|
|
// ManualPurgeStorage actually asked it to count/purge, so tests can assert on
|
|
// category parsing (including "avatar" never becoming a domain.MediaCategory
|
|
// argument) without a real files.Service/Postgres.
|
|
type fakeStorageService struct {
|
|
countCalls int
|
|
purgeCalls int
|
|
|
|
lastCategories []domain.MediaCategory
|
|
lastIncludeAvatars bool
|
|
lastBefore *time.Time
|
|
|
|
countDocs, countPhotos int
|
|
purgedDocs, purgedPhotos int
|
|
bytesReclaimed int64
|
|
countErr, purgeErr error
|
|
}
|
|
|
|
func (f *fakeStorageService) CountManualPurgeCandidates(_ context.Context, categories []domain.MediaCategory, includeAvatars bool, before *time.Time) (int, int, error) {
|
|
f.countCalls++
|
|
f.lastCategories = categories
|
|
f.lastIncludeAvatars = includeAvatars
|
|
f.lastBefore = before
|
|
return f.countDocs, f.countPhotos, f.countErr
|
|
}
|
|
|
|
func (f *fakeStorageService) ManualPurge(_ context.Context, categories []domain.MediaCategory, includeAvatars bool, before *time.Time, _ int) (int, int, int64, error) {
|
|
f.purgeCalls++
|
|
f.lastCategories = categories
|
|
f.lastIncludeAvatars = includeAvatars
|
|
f.lastBefore = before
|
|
return f.purgedDocs, f.purgedPhotos, f.bytesReclaimed, f.purgeErr
|
|
}
|
|
|
|
func TestManualPurgeStorageDryRunReportsCounts(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := newMemoryCommandRepo()
|
|
storage := &fakeStorageService{countDocs: 3, countPhotos: 2}
|
|
svc := NewService(Dependencies{Commands: repo, Storage: storage, Now: fixedNow})
|
|
|
|
req := ManualPurgeStorageRequest{
|
|
CommandMeta: CommandMeta{CommandID: "purge-dry-1", Actor: "ops", Reason: "cleanup", DryRun: true},
|
|
Categories: []string{"video", "Music"},
|
|
}
|
|
result, err := svc.ManualPurgeStorage(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("ManualPurgeStorage(dry-run): %v", err)
|
|
}
|
|
if !result.DryRun {
|
|
t.Fatalf("result.DryRun = false, want true")
|
|
}
|
|
if result.Details["would_delete_documents"] != 3 || result.Details["would_delete_photos"] != 2 {
|
|
t.Fatalf("details = %+v, want would_delete_documents=3, would_delete_photos=2", result.Details)
|
|
}
|
|
if storage.countCalls != 1 || storage.purgeCalls != 0 {
|
|
t.Fatalf("countCalls=%d purgeCalls=%d, want 1/0 for a dry run", storage.countCalls, storage.purgeCalls)
|
|
}
|
|
want := map[domain.MediaCategory]bool{domain.MediaCategoryVideo: true, domain.MediaCategoryMusic: true}
|
|
if len(storage.lastCategories) != 2 || !want[storage.lastCategories[0]] || !want[storage.lastCategories[1]] {
|
|
t.Fatalf("lastCategories = %v, want [Video, Music] (case-insensitively parsed)", storage.lastCategories)
|
|
}
|
|
if storage.lastBefore != nil {
|
|
t.Fatalf("lastBefore = %v, want nil (no created_before supplied)", storage.lastBefore)
|
|
}
|
|
}
|
|
|
|
func TestManualPurgeStorageConfirmReportsPurgedCounts(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := newMemoryCommandRepo()
|
|
storage := &fakeStorageService{purgedDocs: 5, purgedPhotos: 1, bytesReclaimed: 4096}
|
|
svc := NewService(Dependencies{Commands: repo, Storage: storage, Now: fixedNow})
|
|
|
|
before := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
req := ManualPurgeStorageRequest{
|
|
CommandMeta: CommandMeta{CommandID: "purge-confirm-1", Actor: "ops", Reason: "cleanup"},
|
|
Categories: []string{"file"},
|
|
CreatedBefore: &before,
|
|
}
|
|
result, err := svc.ManualPurgeStorage(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("ManualPurgeStorage(confirm): %v", err)
|
|
}
|
|
if result.Details["purged_documents"] != 5 || result.Details["purged_photos"] != 1 || result.Details["bytes_reclaimed"] != int64(4096) {
|
|
t.Fatalf("details = %+v, want purged_documents=5, purged_photos=1, bytes_reclaimed=4096", result.Details)
|
|
}
|
|
if storage.purgeCalls != 1 || storage.countCalls != 0 {
|
|
t.Fatalf("purgeCalls=%d countCalls=%d, want 1/0 for a confirm", storage.purgeCalls, storage.countCalls)
|
|
}
|
|
if storage.lastBefore == nil || !storage.lastBefore.Equal(before) {
|
|
t.Fatalf("lastBefore = %v, want %v", storage.lastBefore, before)
|
|
}
|
|
}
|
|
|
|
// TestManualPurgeStorageAvatarIsNotAMediaCategory guards the "avatar" key's
|
|
// special handling: it must flip IncludeAvatars and must NEVER appear in the
|
|
// categories slice handed to the files.Service layer, since Avatar is not a
|
|
// domain.MediaCategory (see files.Service.ManualPurge's doc comment).
|
|
func TestManualPurgeStorageAvatarIsNotAMediaCategory(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := newMemoryCommandRepo()
|
|
storage := &fakeStorageService{}
|
|
svc := NewService(Dependencies{Commands: repo, Storage: storage, Now: fixedNow})
|
|
|
|
req := ManualPurgeStorageRequest{
|
|
CommandMeta: CommandMeta{CommandID: "purge-avatar-1", Actor: "ops", Reason: "cleanup", DryRun: true},
|
|
Categories: []string{"photo"},
|
|
IncludeAvatars: true,
|
|
}
|
|
if _, err := svc.ManualPurgeStorage(ctx, req); err != nil {
|
|
t.Fatalf("ManualPurgeStorage: %v", err)
|
|
}
|
|
if !storage.lastIncludeAvatars {
|
|
t.Fatal("lastIncludeAvatars = false, want true")
|
|
}
|
|
if len(storage.lastCategories) != 1 || storage.lastCategories[0] != domain.MediaCategoryPhoto {
|
|
t.Fatalf("lastCategories = %v, want exactly [Photo]", storage.lastCategories)
|
|
}
|
|
}
|
|
|
|
// TestManualPurgeStorageRejectsUnknownCategory guards the admin-boundary
|
|
// validation: an unknown category key must be rejected with a clear error,
|
|
// not silently dropped, and must never reach the underlying service.
|
|
func TestManualPurgeStorageRejectsUnknownCategory(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := newMemoryCommandRepo()
|
|
storage := &fakeStorageService{}
|
|
svc := NewService(Dependencies{Commands: repo, Storage: storage, Now: fixedNow})
|
|
|
|
req := ManualPurgeStorageRequest{
|
|
CommandMeta: CommandMeta{CommandID: "purge-bad-1", Actor: "ops", Reason: "cleanup", DryRun: true},
|
|
Categories: []string{"sticker"},
|
|
}
|
|
if _, err := svc.ManualPurgeStorage(ctx, req); err == nil {
|
|
t.Fatal("ManualPurgeStorage with an unknown category = nil error, want an error")
|
|
}
|
|
if storage.countCalls != 0 {
|
|
t.Fatalf("countCalls = %d, want 0 (validation must fail before reaching the service)", storage.countCalls)
|
|
}
|
|
}
|
|
|
|
// TestManualPurgeStorageRequiresACategoryOrAvatars guards against a request
|
|
// that would purge nothing at all (no categories, avatars not included)
|
|
// being silently accepted as a no-op success.
|
|
func TestManualPurgeStorageRequiresACategoryOrAvatars(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := newMemoryCommandRepo()
|
|
storage := &fakeStorageService{}
|
|
svc := NewService(Dependencies{Commands: repo, Storage: storage, Now: fixedNow})
|
|
|
|
req := ManualPurgeStorageRequest{
|
|
CommandMeta: CommandMeta{CommandID: "purge-empty-1", Actor: "ops", Reason: "cleanup", DryRun: true},
|
|
}
|
|
if _, err := svc.ManualPurgeStorage(ctx, req); err == nil {
|
|
t.Fatal("ManualPurgeStorage with no categories and no avatars = nil error, want an error")
|
|
}
|
|
}
|