109 lines
4.1 KiB
Go
109 lines
4.1 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// TestMediaReferenceOrphanTransitions proves the core storage-retention
|
|
// safety invariant: a document's orphaned_at is set only once every
|
|
// reference to it is gone, and cleared the instant a new one appears --
|
|
// so the retention sweep never targets media still visible in a
|
|
// conversation, regardless of how many places reference it.
|
|
func TestMediaReferenceOrphanTransitions(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
s := NewMediaStore(pool)
|
|
|
|
const docID = int64(9100000000000000101)
|
|
t.Cleanup(func() {
|
|
_, _ = pool.Exec(context.Background(), `DELETE FROM media_references WHERE media_kind = 'document' AND media_id = $1`, docID)
|
|
_, _ = pool.Exec(context.Background(), `DELETE FROM documents WHERE id = $1`, docID)
|
|
})
|
|
|
|
if err := s.PutDocument(ctx, domain.Document{ID: docID, MimeType: "text/plain", Size: 10}); err != nil {
|
|
t.Fatalf("put document: %v", err)
|
|
}
|
|
|
|
media := &domain.MessageMedia{Kind: domain.MessageMediaKindDocument, Document: &domain.Document{ID: docID}}
|
|
|
|
// A freshly created document has no orphaned_at yet either way -- it's
|
|
// simply unreferenced until a message send registers the first
|
|
// reference, at which point normal tracking takes over.
|
|
orphaned, err := documentOrphanedAt(ctx, pool, docID)
|
|
if err != nil {
|
|
t.Fatalf("query orphaned_at: %v", err)
|
|
}
|
|
if orphaned {
|
|
t.Fatal("expected a freshly inserted document to not be marked orphaned yet")
|
|
}
|
|
|
|
// Adding a reference (as if a message carrying it was sent) clears it.
|
|
mustAddRef(t, pool, media, domain.MediaRefKindMessageBox, "user:1:box:1")
|
|
if orphaned, err := documentOrphanedAt(ctx, pool, docID); err != nil || orphaned {
|
|
t.Fatalf("expected referenced document to not be orphaned, orphaned=%v err=%v", orphaned, err)
|
|
}
|
|
|
|
// A second, independent reference (e.g. forwarded to another box).
|
|
mustAddRef(t, pool, media, domain.MediaRefKindMessageBox, "user:2:box:5")
|
|
|
|
// Removing only one of the two references must NOT orphan the document.
|
|
mustRemoveRefsByKey(t, pool, domain.MediaRefKindMessageBox, "user:1:box:1")
|
|
if orphaned, err := documentOrphanedAt(ctx, pool, docID); err != nil || orphaned {
|
|
t.Fatalf("expected document with a remaining reference to survive, orphaned=%v err=%v", orphaned, err)
|
|
}
|
|
|
|
// Removing the last reference orphans it.
|
|
mustRemoveRefsByKey(t, pool, domain.MediaRefKindMessageBox, "user:2:box:5")
|
|
if orphaned, err := documentOrphanedAt(ctx, pool, docID); err != nil || !orphaned {
|
|
t.Fatalf("expected document with no remaining reference to be orphaned, orphaned=%v err=%v", orphaned, err)
|
|
}
|
|
|
|
// A reference reappearing after orphaning (e.g. re-sent) clears it again.
|
|
mustAddRef(t, pool, media, domain.MediaRefKindMessageBox, "user:3:box:9")
|
|
if orphaned, err := documentOrphanedAt(ctx, pool, docID); err != nil || orphaned {
|
|
t.Fatalf("expected re-referenced document to no longer be orphaned, orphaned=%v err=%v", orphaned, err)
|
|
}
|
|
}
|
|
|
|
func mustAddRef(t *testing.T, pool *pgxpool.Pool, media *domain.MessageMedia, refKind domain.MediaRefKind, refKey string) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
tx, err := pool.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatalf("begin tx: %v", err)
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
if err := addMediaReferencesTx(ctx, tx, media, refKind, refKey); err != nil {
|
|
t.Fatalf("add media reference: %v", err)
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
t.Fatalf("commit: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustRemoveRefsByKey(t *testing.T, pool *pgxpool.Pool, refKind domain.MediaRefKind, refKey string) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
tx, err := pool.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatalf("begin tx: %v", err)
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
if err := removeMediaReferencesByKeyTx(ctx, tx, refKind, refKey); err != nil {
|
|
t.Fatalf("remove media references: %v", err)
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
t.Fatalf("commit: %v", err)
|
|
}
|
|
}
|
|
|
|
func documentOrphanedAt(ctx context.Context, pool *pgxpool.Pool, id int64) (bool, error) {
|
|
var orphaned bool
|
|
err := pool.QueryRow(ctx, `SELECT orphaned_at IS NOT NULL FROM documents WHERE id = $1`, id).Scan(&orphaned)
|
|
return orphaned, err
|
|
}
|