package processing import ( "context" "os" "testing" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) func TestCleanupExpiredDeletesTerminalJobsAndCascadesProducts(t *testing.T) { dsn := os.Getenv("DATABASE_URL") if dsn == "" { t.Skip("DATABASE_URL not set") } ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() pg, err := pgxpool.New(ctx, dsn) if err != nil { t.Fatal(err) } defer pg.Close() var companyID 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) } var userID uuid.UUID 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) } var rawID uuid.UUID err = pg.QueryRow(ctx, ` SELECT id FROM raw_products WHERE company_id = $1 ORDER BY updated_at DESC LIMIT 1`, companyID).Scan(&rawID) if errorsIsNoRows(err) { t.Skip("no raw_products for selected company") } if err != nil { t.Fatal(err) } insertJob := func(status string, age string) uuid.UUID { t.Helper() var id uuid.UUID err := pg.QueryRow(ctx, ` INSERT INTO processing_jobs ( company_id, user_id, status, total_products, processed_products, processing_type, started_at, completed_at, updated_at, created_at ) VALUES ( $1, $2, $3, 1, 1, 'full', now() - interval '`+age+`', now() - interval '`+age+`', now() - interval '`+age+`', now() - interval '`+age+`' ) RETURNING id`, companyID, userID, status).Scan(&id) if err != nil { t.Fatal(err) } return id } oldCompleted := insertJob("completed", "45 days") oldFailed := insertJob("failed", "45 days") oldRunning := insertJob("running", "45 days") freshCompleted := insertJob("completed", "1 day") defer func() { for _, id := range []uuid.UUID{oldCompleted, oldFailed, oldRunning, freshCompleted} { _, _ = pg.Exec(context.Background(), `DELETE FROM processing_job_products WHERE job_id = $1`, id) _, _ = pg.Exec(context.Background(), `DELETE FROM processing_jobs WHERE id = $1`, id) } }() if _, err := pg.Exec(ctx, ` INSERT INTO processing_job_products (job_id, raw_product_id, status, updated_at, created_at) VALUES ($1, $2, 'processed', now() - interval '45 days', now() - interval '45 days')`, oldCompleted, rawID); err != nil { t.Fatal(err) } res, err := CleanupExpired(ctx, pg) if err != nil { t.Fatal(err) } if res.JobsDeleted < 2 { t.Fatalf("jobs_deleted=%d want >= 2", res.JobsDeleted) } assertGone := func(id uuid.UUID, label string) { t.Helper() var n int if err := pg.QueryRow(ctx, `SELECT COUNT(*) FROM processing_jobs WHERE id = $1`, id).Scan(&n); err != nil { t.Fatal(err) } if n != 0 { t.Fatalf("%s job still present", label) } } assertPresent := func(id uuid.UUID, label string) { t.Helper() var n int if err := pg.QueryRow(ctx, `SELECT COUNT(*) FROM processing_jobs WHERE id = $1`, id).Scan(&n); err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("%s job missing", label) } } assertGone(oldCompleted, "old completed") assertGone(oldFailed, "old failed") assertPresent(oldRunning, "old running") assertPresent(freshCompleted, "fresh completed") var productCount int if err := pg.QueryRow(ctx, ` SELECT COUNT(*) FROM processing_job_products WHERE job_id = $1`, oldCompleted).Scan(&productCount); err != nil { t.Fatal(err) } if productCount != 0 { t.Fatalf("cascaded products remaining=%d want 0", productCount) } }