122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package processing
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/billing"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestCleanupOrphanProcessedReportAndDelete(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
|
||
|
|
// Prefer a non-A1 company so the A1 cohort guard does not block the delete path.
|
||
|
|
err = pg.QueryRow(ctx, `
|
||
|
|
SELECT id FROM companies
|
||
|
|
WHERE lower(trim(coalesce(legacy_company_id, ''))) <> lower($1)
|
||
|
|
ORDER BY created_at DESC
|
||
|
|
LIMIT 1`, billing.A1LegacyCompanyID).Scan(&companyID)
|
||
|
|
if errorsIsNoRows(err) {
|
||
|
|
t.Skip("no non-A1 companies available")
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
rawID := uuid.New()
|
||
|
|
if _, err := pg.Exec(ctx, `
|
||
|
|
INSERT INTO raw_products (
|
||
|
|
id, company_id, gtin, raw_data, mapped_data, is_processed, processing_status
|
||
|
|
) VALUES ($1, $2, $3, '{}'::jsonb, '{}'::jsonb, false, 'unprocessed')`,
|
||
|
|
rawID, companyID, "orphan-test-"+rawID.String()[:8]); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_, _ = pg.Exec(context.Background(), `DELETE FROM processed_products WHERE raw_product_id = $1`, rawID)
|
||
|
|
_, _ = pg.Exec(context.Background(), `DELETE FROM raw_products WHERE id = $1`, rawID)
|
||
|
|
}()
|
||
|
|
|
||
|
|
var processedID uuid.UUID
|
||
|
|
err = pg.QueryRow(ctx, `
|
||
|
|
INSERT INTO processed_products (
|
||
|
|
company_id, raw_product_id, product_id, name, status
|
||
|
|
) VALUES ($1, $2, $3, 'orphan cleanup fixture', 'needs_review')
|
||
|
|
RETURNING id`, companyID, rawID, "orphan-gtin").Scan(&processedID)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
report, err := ReportOrphanProcessed(ctx, pg)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if report.Total < 1 {
|
||
|
|
t.Fatalf("report total=%d want >= 1", report.Total)
|
||
|
|
}
|
||
|
|
|
||
|
|
dry, err := CleanupOrphanProcessed(ctx, pg, false)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if dry.Confirmed {
|
||
|
|
t.Fatal("dry-run should leave confirmed=false")
|
||
|
|
}
|
||
|
|
if dry.Deleted != 0 {
|
||
|
|
t.Fatalf("dry-run deleted=%d want 0", dry.Deleted)
|
||
|
|
}
|
||
|
|
|
||
|
|
var stillThere int
|
||
|
|
if err := pg.QueryRow(ctx, `SELECT COUNT(*) FROM processed_products WHERE id = $1`, processedID).Scan(&stillThere); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if stillThere != 1 {
|
||
|
|
t.Fatalf("fixture row missing before confirm delete")
|
||
|
|
}
|
||
|
|
|
||
|
|
res, err := CleanupOrphanProcessed(ctx, pg, true)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if res.Deleted < 1 {
|
||
|
|
t.Fatalf("deleted=%d want >= 1", res.Deleted)
|
||
|
|
}
|
||
|
|
if err := pg.QueryRow(ctx, `SELECT COUNT(*) FROM processed_products WHERE id = $1`, processedID).Scan(&stillThere); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if stillThere != 0 {
|
||
|
|
t.Fatalf("fixture row still present after confirm delete")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fail-closed: confirm with zero remaining orphans must refuse.
|
||
|
|
after, err := ReportOrphanProcessed(ctx, pg)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if after.Total != 0 {
|
||
|
|
t.Logf("skip empty-refuse assert: other orphans remain total=%d", after.Total)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
_, err = CleanupOrphanProcessed(ctx, pg, true)
|
||
|
|
if !errors.Is(err, ErrOrphanCleanupEmpty) {
|
||
|
|
t.Fatalf("empty confirm err=%v want ErrOrphanCleanupEmpty", err)
|
||
|
|
}
|
||
|
|
}
|