150 lines
6.2 KiB
Go
150 lines
6.2 KiB
Go
package files
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store"
|
|
)
|
|
|
|
// fakeReapStore is a minimal mediaRetentionStore fake exercising only
|
|
// notifyRetentionPurge's new reap step: ListMediaReferences reports one
|
|
// message_box reference, and orphanedDocument/orphanedPhoto control what
|
|
// OrphanDocumentIfUnreferenced/OrphanPhotoIfUnreferenced report -- simulating
|
|
// "the edit above dropped the last reference" (true) vs "something else
|
|
// still references it" (false, e.g. a live avatar or a failed edit). Every
|
|
// other mediaRetentionStore method is a stub never expected to be called by
|
|
// these tests -- but must still exist, or the type assertion
|
|
// s.media.(mediaRetentionStore) inside notifyRetentionPurge silently fails
|
|
// and the whole test becomes a no-op instead of exercising anything.
|
|
type fakeReapStore struct {
|
|
store.MediaStore
|
|
orphanedDocument bool
|
|
orphanedPhoto bool
|
|
deletedDocumentIDs []int64
|
|
deletedPhotoIDs []int64
|
|
}
|
|
|
|
func (f *fakeReapStore) ListMediaReferences(context.Context, domain.MediaKind, int64) ([]domain.MediaReference, error) {
|
|
return []domain.MediaReference{{RefKind: domain.MediaRefKindMessageBox, RefKey: "user:1:box:2"}}, nil
|
|
}
|
|
func (f *fakeReapStore) OrphanDocumentIfUnreferenced(context.Context, int64) (bool, error) {
|
|
return f.orphanedDocument, nil
|
|
}
|
|
func (f *fakeReapStore) OrphanPhotoIfUnreferenced(context.Context, int64) (bool, error) {
|
|
return f.orphanedPhoto, nil
|
|
}
|
|
func (f *fakeReapStore) DeleteDocumentAndBlobs(_ context.Context, id int64) ([]domain.FileBlob, error) {
|
|
f.deletedDocumentIDs = append(f.deletedDocumentIDs, id)
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) DeletePhotoAndBlobs(_ context.Context, id int64) ([]domain.FileBlob, error) {
|
|
f.deletedPhotoIDs = append(f.deletedPhotoIDs, id)
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) ListOrphanedDocumentIDsOlderThan(context.Context, domain.MediaCategory, time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) ListOrphanedPhotoIDsOlderThan(context.Context, time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) ListAvatarOrphanedPhotoIDsOlderThan(context.Context, time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) CountFileBlobRefs(context.Context, string, string) (int, error) {
|
|
return 0, nil
|
|
}
|
|
func (f *fakeReapStore) ListDocumentIDsForHardRetentionOlderThan(context.Context, domain.MediaCategory, *time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) ListPhotoIDsForHardRetentionOlderThan(context.Context, *time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) ListAvatarPhotoIDsForHardRetentionOlderThan(context.Context, *time.Time, int) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) CountDocumentsForHardRetention(context.Context, domain.MediaCategory, *time.Time) (int, error) {
|
|
return 0, nil
|
|
}
|
|
func (f *fakeReapStore) CountPhotosForHardRetention(context.Context, *time.Time) (int, error) {
|
|
return 0, nil
|
|
}
|
|
func (f *fakeReapStore) CountAvatarPhotosForHardRetention(context.Context, *time.Time) (int, error) {
|
|
return 0, nil
|
|
}
|
|
func (f *fakeReapStore) DeleteFileBlobsForDocument(context.Context, int64) ([]domain.FileBlob, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) DeleteFileBlobsForPhoto(context.Context, int64) ([]domain.FileBlob, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeReapStore) SumFileBlobBytes(context.Context) (int64, error) { return 0, nil }
|
|
func (f *fakeReapStore) ListOldestMediaForEviction(context.Context, int) ([]domain.EvictionCandidate, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// fakeReapMessages is the minimal RetentionPurgeMessageEditor: GetMessages
|
|
// resolves a peer so notifyRetentionPurgeMessageBox can build the edit
|
|
// request, and EditMessage always succeeds.
|
|
type fakeReapMessages struct{}
|
|
|
|
func (fakeReapMessages) GetMessages(context.Context, int64, []int) (domain.MessageList, error) {
|
|
return domain.MessageList{Messages: []domain.Message{{ID: 2, Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 3}}}}, nil
|
|
}
|
|
func (fakeReapMessages) EditMessage(context.Context, int64, domain.EditMessageRequest) (domain.EditMessageResult, error) {
|
|
return domain.EditMessageResult{}, nil
|
|
}
|
|
|
|
func newTestServiceForReap(media store.MediaStore) *Service {
|
|
s := &Service{media: media, log: zap.NewNop(), blobCache: newBlobMetaCache(8)}
|
|
s.SetRetentionPurgeNotifier(fakeReapMessages{}, nil)
|
|
return s
|
|
}
|
|
|
|
// TestNotifyRetentionPurgeReapsDocumentWhenFullyUnreferenced guards the fix
|
|
// for "documents/photos rows pile up forever even after the message showing
|
|
// them was already converted to the purge notice": once nothing references a
|
|
// purged document any more, notifyRetentionPurge must delete the row for
|
|
// real instead of always keeping it.
|
|
func TestNotifyRetentionPurgeReapsDocumentWhenFullyUnreferenced(t *testing.T) {
|
|
fake := &fakeReapStore{orphanedDocument: true}
|
|
s := newTestServiceForReap(fake)
|
|
|
|
s.notifyRetentionPurge(context.Background(), domain.MediaKindDocument, 42)
|
|
|
|
if len(fake.deletedDocumentIDs) != 1 || fake.deletedDocumentIDs[0] != 42 {
|
|
t.Fatalf("deleted document ids = %v, want [42]", fake.deletedDocumentIDs)
|
|
}
|
|
}
|
|
|
|
// TestNotifyRetentionPurgeKeepsDocumentWhenStillReferenced is the negative
|
|
// case: OrphanDocumentIfUnreferenced reporting false (something else -- a
|
|
// live avatar, sticker set, or a failed edit -- still references it) must
|
|
// leave the row alone, exactly like the safe orphan-mode deletion this reuses.
|
|
func TestNotifyRetentionPurgeKeepsDocumentWhenStillReferenced(t *testing.T) {
|
|
fake := &fakeReapStore{orphanedDocument: false}
|
|
s := newTestServiceForReap(fake)
|
|
|
|
s.notifyRetentionPurge(context.Background(), domain.MediaKindDocument, 42)
|
|
|
|
if len(fake.deletedDocumentIDs) != 0 {
|
|
t.Fatalf("deleted document ids = %v, want none", fake.deletedDocumentIDs)
|
|
}
|
|
}
|
|
|
|
// TestNotifyRetentionPurgeReapsPhotoWhenFullyUnreferenced is the photo
|
|
// counterpart of the document test above.
|
|
func TestNotifyRetentionPurgeReapsPhotoWhenFullyUnreferenced(t *testing.T) {
|
|
fake := &fakeReapStore{orphanedPhoto: true}
|
|
s := newTestServiceForReap(fake)
|
|
|
|
s.notifyRetentionPurge(context.Background(), domain.MediaKindPhoto, 99)
|
|
|
|
if len(fake.deletedPhotoIDs) != 1 || fake.deletedPhotoIDs[0] != 99 {
|
|
t.Fatalf("deleted photo ids = %v, want [99]", fake.deletedPhotoIDs)
|
|
}
|
|
}
|