package entitlements_test import ( "context" "database/sql" "testing" "git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements" "github.com/google/uuid" ) // createBoolResourceKey inserts a platform-owned boolean resource key for the // test transaction (rolled back with it). func createBoolResourceKey(t *testing.T, ctx context.Context, tx *sql.Tx, key string) { t.Helper() _, err := tx.ExecContext(ctx, `INSERT INTO core.resource_keys (resource_key, display_name, unit) VALUES ($1, $2, 'flag')`, key, "Test Boolean "+key, ) if err != nil { t.Fatalf("create resource key %s: %v", key, err) } } // createBoolTestProduct creates a published product whose entitlement set // carries a single boolean rule for the given resource key. func createBoolTestProduct(t *testing.T, ctx context.Context, tx *sql.Tx, name, resourceKey string) testProduct { t.Helper() q := entitlements.New(tx) set, err := q.CreateEntitlementSet(ctx, entitlements.CreateEntitlementSetParams{ Name: name + " Set", IsActive: true, }) if err != nil { t.Fatalf("create entitlement set for %s: %v", name, err) } _, err = q.CreateEntitlementSetRule(ctx, entitlements.CreateEntitlementSetRuleParams{ SetID: set.SetID, RuleType: "boolean", ResourceKey: sql.NullString{String: resourceKey, Valid: true}, }) if err != nil { t.Fatalf("create boolean rule for %s: %v", name, err) } var productID string err = tx.QueryRowContext(ctx, `INSERT INTO core.products (name, is_active, is_public, lifecycle_status, entitlement_set_id) VALUES ($1, TRUE, TRUE, 'published', $2) RETURNING product_id`, name, set.SetID, ).Scan(&productID) if err != nil { t.Fatalf("create product %s: %v", name, err) } return testProduct{productID: productID, setID: set.SetID} } func getBooleanEntitlement(t *testing.T, ctx context.Context, q *entitlements.Queries, poolID, resourceKey string) (entitlements.BooleanEntitlement, error) { t.Helper() return q.GetBooleanEntitlementByPoolAndResource(ctx, entitlements.GetBooleanEntitlementByPoolAndResourceParams{ PoolID: poolID, ResourceKey: resourceKey, }) } // endGrantConferral revokes the grant and ends its conferral, then re-materializes. func endGrantConferral(t *testing.T, ctx context.Context, q *entitlements.Queries, grantID, poolID string) { t.Helper() if _, err := q.RevokeGrant(ctx, entitlements.RevokeGrantParams{GrantID: grantID}); err != nil { t.Fatalf("revoke grant: %v", err) } ended, err := q.EndConferral(ctx, entitlements.EndConferralParams{ GrantID: uuid.NullUUID{UUID: uuid.MustParse(grantID), Valid: true}, }) if err != nil { t.Fatalf("end conferral: %v", err) } if len(ended) != 1 { t.Fatalf("expected 1 ended provision, got %d", len(ended)) } if err := entitlements.MaterializePoolEntitlements(ctx, q, poolID); err != nil { t.Fatalf("re-materialize: %v", err) } } // Scenario: boolean rule materializes on provision activation. func TestBooleanMaterializeOnActivation(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) to := setupTestOrg(t, ctx, tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) product := createBoolTestProduct(t, ctx, tx, "BoolProduct", key) createGrantAndProvision(t, ctx, tx, q, to, product.productID, 1) be, err := getBooleanEntitlement(t, ctx, q, to.pool.PoolID, key) if err != nil { t.Fatalf("get boolean entitlement: %v", err) } if !be.Granted { t.Errorf("expected granted=true after activation, got false") } } // Scenario: OR-aggregation across provisions — one of two carriers ends, // the row stays granted. func TestBooleanORAggregation(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) to := setupTestOrg(t, ctx, tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) // Two distinct products (distinct sets) carrying the same boolean key — // exercises OR-aggregation across sets, not just repeated grants. productA := createBoolTestProduct(t, ctx, tx, "BoolProductA", key) productB := createBoolTestProduct(t, ctx, tx, "BoolProductB", key) grant1 := createGrantAndProvision(t, ctx, tx, q, to, productA.productID, 1) createGrantAndProvision(t, ctx, tx, q, to, productB.productID, 1) endGrantConferral(t, ctx, q, grant1.GrantID, to.pool.PoolID) be, err := getBooleanEntitlement(t, ctx, q, to.pool.PoolID, key) if err != nil { t.Fatalf("get boolean entitlement: %v", err) } if !be.Granted { t.Errorf("expected granted=true while second carrier active, got false") } } // Scenario: last carrying provision ends — row updated to granted=false and // retained, not deleted. func TestBooleanLastCarrierLapse(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) to := setupTestOrg(t, ctx, tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) product := createBoolTestProduct(t, ctx, tx, "BoolProduct", key) grant := createGrantAndProvision(t, ctx, tx, q, to, product.productID, 1) endGrantConferral(t, ctx, q, grant.GrantID, to.pool.PoolID) be, err := getBooleanEntitlement(t, ctx, q, to.pool.PoolID, key) if err != nil { t.Fatalf("expected retained row after lapse, got error: %v", err) } if be.Granted { t.Errorf("expected granted=false after last carrier ended, got true") } } // Scenario: never-conferred key has no row. func TestBooleanNeverConferredNoRow(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) to := setupTestOrg(t, ctx, tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) // Provision a limit-only product; the boolean key is never carried. product := createTestProduct(t, ctx, tx, "LimitOnlyProduct", 5, false) createGrantAndProvision(t, ctx, tx, q, to, product.productID, 1) _, err = getBooleanEntitlement(t, ctx, q, to.pool.PoolID, key) if err != sql.ErrNoRows { t.Errorf("expected sql.ErrNoRows for never-conferred key, got %v", err) } } // Scenario: idempotent boolean materialization. func TestBooleanMaterializeIdempotency(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) to := setupTestOrg(t, ctx, tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) product := createBoolTestProduct(t, ctx, tx, "BoolProduct", key) createGrantAndProvision(t, ctx, tx, q, to, product.productID, 1) var first entitlements.BooleanEntitlement for i := 0; i < 3; i++ { if err := entitlements.MaterializePoolEntitlements(ctx, q, to.pool.PoolID); err != nil { t.Fatalf("materialize iteration %d: %v", i, err) } be, err := getBooleanEntitlement(t, ctx, q, to.pool.PoolID, key) if err != nil { t.Fatalf("get boolean entitlement iteration %d: %v", i, err) } if i == 0 { first = be continue } if be.Granted != first.Granted || be.PoolID != first.PoolID || be.ResourceKey != first.ResourceKey || !be.CreatedAt.Equal(first.CreatedAt) { t.Errorf("iteration %d produced different row: %+v vs %+v", i, be, first) } } rows, err := q.ListBooleanEntitlementsByPoolID(ctx, to.pool.PoolID) if err != nil { t.Fatalf("list boolean entitlements: %v", err) } if len(rows) != 1 { t.Errorf("expected exactly 1 boolean row after repeated materialize, got %d", len(rows)) } } // Scenario: read-surface query returns exactly the pools with active carriers, // each with its owning organization. func TestListPoolsGrantedBooleanKey(t *testing.T) { database := testDB(t) ctx := context.Background() tx, err := database.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer tx.Rollback() q := entitlements.New(tx) key := "test.bool-" + uuid.New().String()[:8] createBoolResourceKey(t, ctx, tx, key) product := createBoolTestProduct(t, ctx, tx, "BoolProduct", key) orgA := setupTestOrg(t, ctx, tx) orgB := setupTestOrg(t, ctx, tx) orgC := setupTestOrg(t, ctx, tx) createGrantAndProvision(t, ctx, tx, q, orgA, product.productID, 1) createGrantAndProvision(t, ctx, tx, q, orgB, product.productID, 1) grantC := createGrantAndProvision(t, ctx, tx, q, orgC, product.productID, 1) endGrantConferral(t, ctx, q, grantC.GrantID, orgC.pool.PoolID) granted, err := q.ListPoolsGrantedBooleanKey(ctx, key) if err != nil { t.Fatalf("list pools granted: %v", err) } if len(granted) != 2 { t.Fatalf("expected 2 granted pools, got %d", len(granted)) } wantPools := map[string]string{ orgA.pool.PoolID: orgA.org.OrgID, orgB.pool.PoolID: orgB.org.OrgID, } for _, row := range granted { wantOrg, ok := wantPools[row.PoolID] if !ok { t.Errorf("unexpected pool %s in results", row.PoolID) continue } if row.OrgID != wantOrg { t.Errorf("pool %s: expected org %s, got %s", row.PoolID, wantOrg, row.OrgID) } } }