Files
sxchnge/backend/edge/shared/storage/organization/provisioning_test.go
T

181 lines
7.3 KiB
Go

package organization
import (
"context"
"testing"
"time"
"git.sendico.io/tech/sxchnge/bff/pkg/model"
"go.mongodb.org/mongo-driver/v2/bson"
)
func provisionedOrganization(t *testing.T, db *OrganizationDB, ownerRef bson.ObjectID, name, leaseOwner string) *model.Organization {
t.Helper()
org := &model.Organization{Members: []bson.ObjectID{}}
org.SetID(bson.NewObjectID())
org.Name = name
org.SignupState = model.OrganizationSignupStatePending
org.Provisioning = &model.OrganizationProvisioning{
OwnerRef: ownerRef,
Name: name,
Origin: "backoffice",
StartedAt: time.Now().UTC(),
LeaseOwner: leaseOwner,
LeaseExpiresAt: time.Now().UTC().Add(10 * time.Minute),
}
if err := db.Unprotected().Create(context.Background(), org); err != nil {
t.Fatalf("failed to store organization: %v", err)
}
return org
}
func TestAddMemberIsAPointPatch(t *testing.T) {
db := newTestOrganizationDB(t)
ownerRef := bson.NewObjectID()
org := provisionedOrganization(t, db, ownerRef, "ProCorp", "operator/attempt-1")
memberRef := bson.NewObjectID()
added, err := db.AddMember(context.Background(), org.ID, memberRef)
if err != nil || !added {
t.Fatalf("first add must report success: added=%v err=%v", added, err)
}
// The second add must be a no-op, not an error: transaction replays and
// concurrent joins both hit this path.
added, err = db.AddMember(context.Background(), org.ID, memberRef)
if err != nil || added {
t.Fatalf("second add must report an existing member: added=%v err=%v", added, err)
}
stored := &model.Organization{}
if err := db.Unprotected().Get(context.Background(), org.ID, stored); err != nil {
t.Fatalf("failed to read organization back: %v", err)
}
if len(stored.Members) != 1 || stored.Members[0] != memberRef {
t.Fatalf("members mismatch: got=%v", stored.Members)
}
// The membership write must not touch the provisioning lease: the fence in
// FinalizeProvisioning checks the stored lease, not this attempt's copy.
if stored.Provisioning == nil || stored.Provisioning.LeaseOwner != "operator/attempt-1" {
t.Fatalf("membership write clobbered the provisioning lease: %+v", stored.Provisioning)
}
}
func TestFinalizeProvisioningKeepsProvenance(t *testing.T) {
db := newTestOrganizationDB(t)
ownerRef := bson.NewObjectID()
org := provisionedOrganization(t, db, ownerRef, "ProCorp", "operator/attempt-1")
finalized, err := db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1")
if err != nil || !finalized {
t.Fatalf("finalization by the lease owner must succeed: finalized=%v err=%v", finalized, err)
}
stored := &model.Organization{}
if err := db.Unprotected().Get(context.Background(), org.ID, stored); err != nil {
t.Fatalf("failed to read organization back: %v", err)
}
if stored.SignupState != model.OrganizationSignupStateActive {
t.Fatalf("organization must be active, got=%s", stored.SignupState)
}
// Provenance survives activation so a retry after a lost success response
// can recognize the organization instead of provisioning a duplicate.
if stored.Provisioning == nil || stored.Provisioning.OwnerRef != ownerRef {
t.Fatalf("provisioning provenance must survive activation: %+v", stored.Provisioning)
}
if stored.Provisioning.LeaseOwner != "" || !stored.Provisioning.LeaseExpiresAt.IsZero() {
t.Fatalf("lease must be cleared on activation: %+v", stored.Provisioning)
}
// A replayed finalization must not match: the organization left pending.
finalized, err = db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1")
if err != nil || finalized {
t.Fatalf("an active organization must not finalize again: finalized=%v err=%v", finalized, err)
}
}
func TestFinalizeProvisioningRejectsTakenOverLease(t *testing.T) {
db := newTestOrganizationDB(t)
org := provisionedOrganization(t, db, bson.NewObjectID(), "ProCorp", "operator/attempt-2")
finalized, err := db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1")
if err != nil || finalized {
t.Fatalf("an attempt whose lease was taken over must not finalize: finalized=%v err=%v", finalized, err)
}
}
func TestFindActiveProvisioned(t *testing.T) {
db := newTestOrganizationDB(t)
ownerRef := bson.NewObjectID()
org := provisionedOrganization(t, db, ownerRef, "ProCorp", "operator/attempt-1")
// Pending organizations belong to FindPendingProvisioning, not here.
found, err := db.FindActiveProvisioned(context.Background(), ownerRef, "ProCorp")
if err != nil || found != nil {
t.Fatalf("a pending organization must not be reported as activated: found=%v err=%v", found, err)
}
if _, err := db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1"); err != nil {
t.Fatalf("failed to finalize: %v", err)
}
found, err = db.FindActiveProvisioned(context.Background(), ownerRef, "ProCorp")
if err != nil || found == nil || found.ID != org.ID {
t.Fatalf("the activated organization must be found: found=%v err=%v", found, err)
}
if found, err = db.FindActiveProvisioned(context.Background(), ownerRef, "Other"); err != nil || found != nil {
t.Fatalf("a different name must not match: found=%v err=%v", found, err)
}
}
// The dedup key is the provisioning name snapshot: renaming an activated
// organization must not detach it from a retry of the create that produced it,
// or the retry would provision a duplicate.
func TestFindActiveProvisionedSurvivesRename(t *testing.T) {
db := newTestOrganizationDB(t)
ownerRef := bson.NewObjectID()
org := provisionedOrganization(t, db, ownerRef, "ProCorp", "operator/attempt-1")
if _, err := db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1"); err != nil {
t.Fatalf("failed to finalize: %v", err)
}
stored := &model.Organization{}
if err := db.Unprotected().Get(context.Background(), org.ID, stored); err != nil {
t.Fatalf("failed to read organization back: %v", err)
}
stored.Name = "Renamed Corp"
if err := db.Unprotected().Update(context.Background(), stored); err != nil {
t.Fatalf("failed to rename organization: %v", err)
}
found, err := db.FindActiveProvisioned(context.Background(), ownerRef, "ProCorp")
if err != nil || found == nil || found.ID != org.ID {
t.Fatalf("the renamed organization must still match its provisioning name: found=%v err=%v", found, err)
}
}
// Archiving is how an operator retires a name: an archived organization must
// stop blocking the creation of a fresh one with the same owner and name.
func TestFindActiveProvisionedReleasesArchivedName(t *testing.T) {
db := newTestOrganizationDB(t)
ownerRef := bson.NewObjectID()
org := provisionedOrganization(t, db, ownerRef, "ProCorp", "operator/attempt-1")
if _, err := db.FinalizeProvisioning(context.Background(), org.ID, "operator/attempt-1"); err != nil {
t.Fatalf("failed to finalize: %v", err)
}
stored := &model.Organization{}
if err := db.Unprotected().Get(context.Background(), org.ID, stored); err != nil {
t.Fatalf("failed to read organization back: %v", err)
}
stored.Archived = true
if err := db.Unprotected().Update(context.Background(), stored); err != nil {
t.Fatalf("failed to archive organization: %v", err)
}
found, err := db.FindActiveProvisioned(context.Background(), ownerRef, "ProCorp")
if err != nil || found != nil {
t.Fatalf("an archived organization must release its name: found=%v err=%v", found, err)
}
}