owpengram-server/internal/store/postgres/media_refs_integration_test.go
2026-08-04 23:09:11 +03:00

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
}