internal/store/postgres/gif_catalog_integration_test.go
Rama main · réplica de lectura del repositorio original.
package postgres
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"telesrv/internal/domain"
)
func TestGifCatalogCapacityIsAtomic(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
base := time.Now().UnixNano()
const extraDocuments = 2
t.Cleanup(func() {
_, _ = pool.Exec(ctx, `DELETE FROM gif_catalog WHERE id >= $1 AND id < $2`, base, base+domain.MaxGifCatalogEntries+extraDocuments)
_, _ = pool.Exec(ctx, `DELETE FROM documents WHERE id >= $1 AND id < $2`, base, base+domain.MaxGifCatalogEntries+extraDocuments)
})
var existing, reserved int
if err := pool.QueryRow(ctx, `
SELECT (SELECT count(*) FROM gif_catalog), entry_count
FROM gif_catalog_capacity WHERE singleton`).Scan(&existing, &reserved); err != nil {
t.Fatalf("load initial gif catalog capacity: %v", err)
}
if existing != 0 || reserved != 0 {
t.Fatalf("dedicated test database has gif catalog rows: count=%d reserved=%d", existing, reserved)
}
media := NewMediaStore(pool)
for i := 0; i < domain.MaxGifCatalogEntries+extraDocuments; i++ {
if err := media.PutDocument(ctx, domain.Document{
ID: base + int64(i), MimeType: "video/mp4", Size: 1, DCID: 2,
}); err != nil {
t.Fatalf("put document %d: %v", i, err)
}
}
type result struct {
entry domain.GifCatalogEntry
err error
}
results := make(chan result, domain.MaxGifCatalogEntries+1)
start := make(chan struct{})
var wg sync.WaitGroup
for i := 0; i < domain.MaxGifCatalogEntries+1; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
<-start
entry, err := NewGifCatalogStore(pool).CreateGifCatalogEntry(ctx, domain.GifCatalogEntry{
ID: base + int64(i), Title: fmt.Sprintf("gif-%02d", i), DocumentID: base + int64(i),
})
results <- result{entry: entry, err: err}
}(i)
}
close(start)
wg.Wait()
close(results)
successes, full := make([]domain.GifCatalogEntry, 0, domain.MaxGifCatalogEntries), 0
for got := range results {
switch {
case got.err == nil:
successes = append(successes, got.entry)
case errors.Is(got.err, domain.ErrGifCatalogFull):
full++
default:
t.Fatalf("concurrent create: %v", got.err)
}
}
if len(successes) != domain.MaxGifCatalogEntries || full != 1 {
t.Fatalf("concurrent creates: success=%d full=%d", len(successes), full)
}
assertGifCatalogCapacity(t, ctx, pool, domain.MaxGifCatalogEntries)
if changed, err := NewGifCatalogStore(pool).DeleteGifCatalogEntry(ctx, successes[0].ID); err != nil || !changed {
t.Fatalf("delete catalog entry: changed=%v err=%v", changed, err)
}
assertGifCatalogCapacity(t, ctx, pool, domain.MaxGifCatalogEntries-1)
last := base + domain.MaxGifCatalogEntries + 1
if _, err := NewGifCatalogStore(pool).CreateGifCatalogEntry(ctx, domain.GifCatalogEntry{
ID: last, Title: "replacement", DocumentID: last,
}); err != nil {
t.Fatalf("create after release: %v", err)
}
assertGifCatalogCapacity(t, ctx, pool, domain.MaxGifCatalogEntries)
}
func TestGifCatalogCategoryOverrideAndAutoRestore(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
id := time.Now().UnixNano()
t.Cleanup(func() {
_, _ = pool.Exec(ctx, `DELETE FROM gif_catalog WHERE id=$1`, id)
_, _ = pool.Exec(ctx, `DELETE FROM documents WHERE id=$1`, id)
})
if err := NewMediaStore(pool).PutDocument(ctx, domain.Document{ID: id, MimeType: "video/mp4", Size: 1, DCID: 2}); err != nil {
t.Fatal(err)
}
store := NewGifCatalogStore(pool)
entry, err := store.CreateGifCatalogEntry(ctx, domain.GifCatalogEntry{ID: id, Title: "Untitled", FileName: "cat.gif", DocumentID: id})
if err != nil || entry.Category != domain.GifCategoryAnimals || entry.CategoryManual {
t.Fatalf("auto entry = %+v err=%v", entry, err)
}
if changed, err := store.SetGifCatalogCategory(ctx, id, domain.GifCategorySports); err != nil || !changed {
t.Fatalf("override changed=%v err=%v", changed, err)
}
entries, err := store.ListGifCatalog(ctx, false)
if err != nil {
t.Fatal(err)
}
var found domain.GifCatalogEntry
for _, candidate := range entries {
if candidate.ID == id {
found = candidate
}
}
if found.Category != domain.GifCategorySports || !found.CategoryManual {
t.Fatalf("manual entry = %+v", found)
}
if changed, err := store.SetGifCatalogCategory(ctx, id, ""); err != nil || !changed {
t.Fatalf("restore changed=%v err=%v", changed, err)
}
entries, err = store.ListGifCatalog(ctx, false)
if err != nil {
t.Fatal(err)
}
for _, candidate := range entries {
if candidate.ID == id && (candidate.Category != domain.GifCategoryAnimals || candidate.CategoryManual) {
t.Fatalf("restored entry = %+v", candidate)
}
}
}
func assertGifCatalogCapacity(t *testing.T, ctx context.Context, pool *pgxpool.Pool, want int) {
t.Helper()
var rows, reserved int
if err := pool.QueryRow(ctx, `
SELECT (SELECT count(*) FROM gif_catalog), entry_count
FROM gif_catalog_capacity WHERE singleton`).Scan(&rows, &reserved); err != nil {
t.Fatalf("load gif catalog capacity: %v", err)
}
if rows != want || reserved != want {
t.Fatalf("gif catalog capacity: rows=%d reserved=%d want=%d", rows, reserved, want)
}
}