package server_test import ( "context" "database/sql" "os" "testing" "git.coopcloud.tech/wiki-cafe/member-console/internal/billing" "git.coopcloud.tech/wiki-cafe/member-console/internal/db" "git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements" "git.coopcloud.tech/wiki-cafe/member-console/internal/migrate" "github.com/google/uuid" _ "github.com/jackc/pgx/v5/stdlib" ) func testDB(t *testing.T) *sql.DB { t.Helper() dsn := os.Getenv("TEST_DATABASE_URL") if dsn == "" { t.Skip("TEST_DATABASE_URL not set, skipping integration test") } database, err := sql.Open("pgx", dsn) if err != nil { t.Fatalf("failed to open database: %v", err) } sources := migrate.Sources() if err := db.RunMigrations(database, sources); err != nil { t.Fatalf("failed to run migrations: %v", err) } t.Cleanup(func() { database.Close() }) return database } // TestPlanLadderOperatorWorkflow verifies the full operator ladder CRUD workflow: // create ladder, add tiers, reorder ranks, and delete ladder. func TestPlanLadderOperatorWorkflow(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() bq := billing.New(tx) eq := entitlements.New(tx) // 1. Create a plan ladder ladder, err := bq.CreatePlanLadder(ctx, billing.CreatePlanLadderParams{ LadderKey: "test-ladder-" + uuid.New().String()[:8], Name: "Test Ladder", Description: sql.NullString{String: "A test ladder", Valid: true}, IsActive: true, }) if err != nil { t.Fatalf("create plan ladder: %v", err) } if ladder.PlanLadderID == "" { t.Fatal("expected ladder ID to be set") } // 2. Create a published product — any published product may be a ladder // tier since doc 41 (display_category is presentation-only). // First we need an entitlement set es, err := eq.CreateEntitlementSet(ctx, entitlements.CreateEntitlementSetParams{ Name: "test-set-" + uuid.New().String()[:8], Description: sql.NullString{String: "Test set", Valid: true}, }) if err != nil { t.Fatalf("create entitlement set: %v", err) } product, err := bq.CreateProduct(ctx, billing.CreateProductParams{ Name: "Test Plan Product", Description: sql.NullString{String: "A test plan product", Valid: true}, IsActive: true, IsPublic: true, EntitlementSetID: uuid.NullUUID{UUID: uuid.MustParse(es.SetID), Valid: true}, LifecycleStatus: "published", }) if err != nil { t.Fatalf("create product: %v", err) } // 3. Add tier to ladder — rank self-assigns append-at-end, so the first // tier of a fresh ladder lands at rank 0. tier, err := bq.CreatePlanLadderTier(ctx, billing.CreatePlanLadderTierParams{ PlanLadderID: ladder.PlanLadderID, ProductID: product.ProductID, }) if err != nil { t.Fatalf("create plan ladder tier: %v", err) } if tier.Rank != 0 { t.Fatalf("expected rank 0, got %d", tier.Rank) } // Verify tier appears in list tiers, err := bq.ListTiersByLadder(ctx, ladder.PlanLadderID) if err != nil { t.Fatalf("list tiers: %v", err) } if len(tiers) != 1 { t.Fatalf("expected 1 tier, got %d", len(tiers)) } // 4. Reorder tier to rank 2 updatedTier, err := bq.UpdateTierRank(ctx, billing.UpdateTierRankParams{ PlanLadderID: ladder.PlanLadderID, ProductID: product.ProductID, Rank: 2, }) if err != nil { t.Fatalf("update tier rank: %v", err) } if updatedTier.Rank != 2 { t.Fatalf("expected rank 2, got %d", updatedTier.Rank) } // 5. Verify delete is blocked when no active attachments exist // (no attachments in this test, so delete should succeed) count, err := eq.CountActiveAttachmentsByLadder(ctx, ladder.PlanLadderID) if err != nil { t.Fatalf("count active attachments: %v", err) } if count != 0 { t.Fatalf("expected 0 active attachments, got %d", count) } // 6. Delete the tier first (should succeed since no active attachments) err = bq.DeleteTier(ctx, billing.DeleteTierParams{ PlanLadderID: ladder.PlanLadderID, ProductID: product.ProductID, }) if err != nil { t.Fatalf("delete tier: %v", err) } // Verify tier is gone tiers, err = bq.ListTiersByLadder(ctx, ladder.PlanLadderID) if err != nil { t.Fatalf("list tiers after delete: %v", err) } if len(tiers) != 0 { t.Fatalf("expected 0 tiers after delete, got %d", len(tiers)) } // 7. Delete the ladder err = bq.DeletePlanLadder(ctx, ladder.PlanLadderID) if err != nil { t.Fatalf("delete plan ladder: %v", err) } // Verify ladder is gone _, err = bq.GetPlanLadderByID(ctx, ladder.PlanLadderID) if err == nil { t.Fatal("expected ladder to be deleted") } }