package processing import ( "context" "os" "sync" "testing" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) func TestClaimNextConcurrentDistinctJobs(t *testing.T) { dsn := os.Getenv("DATABASE_URL") if dsn == "" { t.Skip("DATABASE_URL not set") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() pg, err := pgxpool.New(ctx, dsn) if err != nil { t.Fatal(err) } defer pg.Close() var companyID, userID uuid.UUID err = pg.QueryRow(ctx, ` SELECT company_id FROM raw_products WHERE company_id IS NOT NULL ORDER BY updated_at DESC LIMIT 1`).Scan(&companyID) if errorsIsNoRows(err) { t.Skip("no raw_products rows available") } if err != nil { t.Fatal(err) } err = pg.QueryRow(ctx, `SELECT id FROM users ORDER BY created_at DESC LIMIT 1`).Scan(&userID) if errorsIsNoRows(err) { t.Skip("no users rows available") } if err != nil { t.Fatal(err) } const n = 4 jobIDs := make([]uuid.UUID, 0, n) for i := 0; i < n; i++ { var id uuid.UUID err = pg.QueryRow(ctx, ` INSERT INTO processing_jobs ( company_id, user_id, status, total_products, processed_products, processing_type, priority, created_at, updated_at ) VALUES ($1, $2, 'pending', 0, 0, 'full', 10, now(), now()) RETURNING id`, companyID, userID).Scan(&id) if err != nil { t.Fatal(err) } jobIDs = append(jobIDs, id) } defer func() { for _, id := range jobIDs { _, _ = pg.Exec(context.Background(), `DELETE FROM processing_jobs WHERE id = $1`, id) } }() p := NewPipeline(pg) claimed := make([]uuid.UUID, n) var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func(i int) { defer wg.Done() id, err := p.ClaimNext(ctx) if err != nil { t.Errorf("ClaimNext: %v", err) return } claimed[i] = id }(i) } wg.Wait() seen := make(map[uuid.UUID]struct{}, n) for _, id := range claimed { if id == uuid.Nil { t.Fatal("nil claim") } if _, ok := seen[id]; ok { t.Fatalf("duplicate claim %s (SKIP LOCKED failed)", id) } seen[id] = struct{}{} } // Shared DBs may have other pending jobs; uniqueness of the concurrent claims is the contract under test. _, _ = p.ClaimNext(ctx) }