Files
member-console/internal/provisioning/workspace_creation_test.go
T
cgalo5758 ad7a219adf Enforce schema and boot invariants
Enforce 10j's verified gaps (schema-hardening change):

- Migration 00010: partial unique indexes for one default pool and one
  primary assignment per workspace, plus CHECKs pinning
  pool/provider/subscription vocabularies and provider lifecycle
  timestamps.
- Workspace creation shares a transactional provisioning function;
  extension validates its target pool; last-tier deletion of a defaulted
  ladder is guarded; signup completes plan-less on a broken ladder.
- Boot asserts integration slug parity and validates declared config
  enums; Stripe invoice amounts are range-checked; domain cancellation
  runs a final evidence probe; rule authoring is additive-only.
2026-08-22 18:02:46 -05:00

157 lines
5.3 KiB
Go

package provisioning_test
// Tests for CreateWorkspaceWithPrimaryAssignment (schema-hardening tasks 2.1 +
// 2.5): the function both AutoProvision and the member "create workspace"
// handler now share. AutoProvision's existing tests already exercise the
// "no pool yet" (create) branch end to end; these tests focus on the
// "pool already exists" (resolve) branch the member handler relies on, and on
// the atomicity guarantee itself.
import (
"context"
"database/sql"
"testing"
"git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements"
"git.coopcloud.tech/wiki-cafe/member-console/internal/identity"
"git.coopcloud.tech/wiki-cafe/member-console/internal/organization"
"git.coopcloud.tech/wiki-cafe/member-console/internal/provisioning"
"github.com/google/uuid"
)
// wcOrg creates a committed organization (no pool) for these tests.
func wcOrg(t *testing.T, database *sql.DB, name string) string {
t.Helper()
ctx := context.Background()
tx, err := database.BeginTx(ctx, nil)
if err != nil {
t.Fatalf("begin: %v", err)
}
defer tx.Rollback()
iq := identity.New(tx)
oq := organization.New(tx)
user, err := iq.CreateUser(ctx, "wc-u-"+uuid.NewString())
if err != nil {
t.Fatalf("user: %v", err)
}
person, err := iq.CreatePerson(ctx, identity.CreatePersonParams{
UserID: user.UserID, DisplayName: name,
PrimaryEmail: "wc-" + uuid.New().String()[:8] + "@example.com", PrimaryEmailVerified: true,
})
if err != nil {
t.Fatalf("person: %v", err)
}
org, err := oq.CreateOrganization(ctx, organization.CreateOrganizationParams{
Name: name, Slug: "wc-" + uuid.New().String()[:12], OrgType: "personal", OwnerPersonID: person.PersonID,
})
if err != nil {
t.Fatalf("org: %v", err)
}
if err := tx.Commit(); err != nil {
t.Fatalf("commit: %v", err)
}
return org.OrgID
}
// A second workspace for the same org resolves the org's existing default
// pool instead of creating a second one -- the branch AutoProvision itself
// never exercises (it always provisions a brand-new org) but the member
// "create workspace" handler relies on every time.
func TestCreateWorkspaceWithPrimaryAssignment_ResolvesExistingPool(t *testing.T) {
database := testDB(t)
ctx := context.Background()
orgID := wcOrg(t, database, "WC Resolve Org")
tx1, err := database.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
res1, err := provisioning.CreateWorkspaceWithPrimaryAssignment(ctx, tx1, orgID, "First", "first")
if err != nil {
t.Fatalf("first create: %v", err)
}
if err := tx1.Commit(); err != nil {
t.Fatalf("commit first: %v", err)
}
if res1.Pool.PoolID == "" {
t.Fatal("expected a pool to be created for the org's first workspace")
}
tx2, err := database.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
res2, err := provisioning.CreateWorkspaceWithPrimaryAssignment(ctx, tx2, orgID, "Second", "second")
if err != nil {
t.Fatalf("second create: %v", err)
}
if err := tx2.Commit(); err != nil {
t.Fatalf("commit second: %v", err)
}
if res2.Pool.PoolID != res1.Pool.PoolID {
t.Errorf("second workspace got pool %s, want the org's existing default pool %s", res2.Pool.PoolID, res1.Pool.PoolID)
}
if res2.Workspace.WorkspaceID == res1.Workspace.WorkspaceID {
t.Fatal("expected two distinct workspaces")
}
if !res2.PoolAssignment.IsPrimary {
t.Error("expected the second assignment to be primary too (each workspace gets its own primary assignment)")
}
entQ := entitlements.New(database)
pools, err := entQ.GetResourcePoolsByOrgID(ctx, orgID)
if err != nil {
t.Fatalf("list pools: %v", err)
}
if len(pools) != 1 {
t.Errorf("org has %d pools after two workspace creations, want exactly 1 (resolution, not a second creation)", len(pools))
}
}
// A failure partway through (here: the default-pool insert collides with a
// pre-existing, differently-typed pool at the same org_id+slug) rolls back
// the whole transaction -- no orphan workspace row survives with no pool
// assignment.
func TestCreateWorkspaceWithPrimaryAssignment_FailureRollsBackWorkspaceInsert(t *testing.T) {
database := testDB(t)
ctx := context.Background()
orgID := wcOrg(t, database, "WC Rollback Org")
// Poison the default-pool creation branch: GetDefaultPoolByOrgID filters
// pool_type = 'default', so a pre-existing 'shared' pool at the same
// org_id+slug is invisible to resolution but still collides on the
// pre-existing uq_resource_pools_org_id_slug constraint (migration
// 00001, not the concurrently-authored 00010) when the function falls
// through to CreateResourcePool.
entQ := entitlements.New(database)
if _, err := entQ.CreateResourcePool(ctx, entitlements.CreateResourcePoolParams{
OrgID: orgID, Name: "Decoy", Slug: "default", PoolType: "shared", IsAutoManaged: false,
}); err != nil {
t.Fatalf("seed decoy pool: %v", err)
}
tx, err := database.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
_, err = provisioning.CreateWorkspaceWithPrimaryAssignment(ctx, tx, orgID, "Doomed", "default")
if err == nil {
t.Fatal("expected the pool-creation collision to fail")
}
if rbErr := tx.Rollback(); rbErr != nil {
t.Fatalf("rollback: %v", rbErr)
}
oq := organization.New(database)
workspaces, err := oq.GetWorkspacesByOrgID(ctx, orgID)
if err != nil {
t.Fatalf("list workspaces: %v", err)
}
if len(workspaces) != 0 {
t.Errorf("workspaces = %d after a failed creation, want 0 (rolled back, not orphaned)", len(workspaces))
}
}