owpengram-server/internal/admin/manual_purge_storage_test.go
2026-09-03 08:33:06 +03:00

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")
}
}