Files
kandev/apps/backend/internal/orgunit/backfill_test.go
T
João Salavisa 481359ddb2 feat(auth): organization units, tenancy, roles and scopes for team access (#2919)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 13:40:43 +00:00

158 lines
5.1 KiB
Go

package orgunit
import (
"context"
"testing"
)
type stubAccounts struct{ users []UserRef }
func (s stubAccounts) ListUnitUsers(context.Context) ([]UserRef, error) { return s.users, nil }
type stubPlacer struct {
pending []WorkspaceRef
placed map[string]string
}
func (s *stubPlacer) UnplacedWorkspaces(context.Context) ([]WorkspaceRef, error) {
return s.pending, nil
}
func (s *stubPlacer) PlaceWorkspace(_ context.Context, workspaceID, unitID string) error {
if s.placed == nil {
s.placed = map[string]string{}
}
s.placed[workspaceID] = unitID
return nil
}
// The rule an upgrade lives or dies by: a workspace that only its owner could
// reach must land somewhere only its owner reaches. Landing it under the root
// would hand the whole organization a board that was private a moment earlier,
// and nothing in the product would report that it had happened.
func TestBackfillDoesNotWidenReach(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
accounts := stubAccounts{users: []UserRef{
{ID: "ada", OrgID: "org-1", DisplayName: "Ada Lovelace"},
{ID: "grace", OrgID: "org-1", DisplayName: "Grace Hopper"},
}}
placer := &stubPlacer{pending: []WorkspaceRef{
{ID: "ws-ada", OrgID: "org-1", OwnerID: "ada"},
{ID: "ws-grace", OrgID: "org-1", OwnerID: "grace"},
{ID: "ws-unowned", OrgID: "org-1", OwnerID: ""},
}}
result, err := svc.Backfill(ctx, accounts, placer)
if err != nil {
t.Fatalf("backfill: %v", err)
}
if result.Placed != 3 {
t.Fatalf("placed = %d, want 3", result.Placed)
}
adaUnit, err := svc.Store().Personal(ctx, "ada")
if err != nil {
t.Fatalf("ada personal unit: %v", err)
}
graceUnit, err := svc.Store().Personal(ctx, "grace")
if err != nil {
t.Fatalf("grace personal unit: %v", err)
}
root, err := svc.Store().Root(ctx, "org-1")
if err != nil {
t.Fatalf("root: %v", err)
}
if placer.placed["ws-ada"] != adaUnit.ID {
t.Fatalf("an owned workspace landed in %q, want the owner's personal unit", placer.placed["ws-ada"])
}
if placer.placed["ws-grace"] != graceUnit.ID {
t.Fatalf("an owned workspace landed in %q, want the owner's personal unit", placer.placed["ws-grace"])
}
// A workspace with no owner predates authentication and everyone reaches it
// already, so the root changes nothing for it.
if placer.placed["ws-unowned"] != root.ID {
t.Fatalf("an unowned workspace landed in %q, want the root", placer.placed["ws-unowned"])
}
if placer.placed["ws-ada"] == root.ID || placer.placed["ws-grace"] == root.ID {
t.Fatal("an owned workspace was placed under the root, which widens reach to the whole organization")
}
}
// An owner whose account is gone must not hand their workspace to the
// organization. A dangling id still gets a unit of its own.
func TestBackfillIsolatesWorkspacesOfMissingOwners(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
placer := &stubPlacer{pending: []WorkspaceRef{
{ID: "ws-orphan", OrgID: "org-1", OwnerID: "deleted-user"},
}}
if _, err := svc.Backfill(ctx, stubAccounts{}, placer); err != nil {
t.Fatalf("backfill: %v", err)
}
root, err := svc.Store().Root(ctx, "org-1")
if err != nil {
t.Fatalf("root: %v", err)
}
if placer.placed["ws-orphan"] == root.ID {
t.Fatal("a workspace owned by a missing account was placed under the root")
}
if _, err := svc.Store().Personal(ctx, "deleted-user"); err != nil {
t.Fatalf("expected a personal unit for the dangling owner: %v", err)
}
}
// Boot runs this every time, so a second pass must not create a second root, a
// second personal unit, or move a workspace that already has a home.
func TestBackfillIsIdempotent(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
accounts := stubAccounts{users: []UserRef{{ID: "ada", OrgID: "org-1"}}}
first := &stubPlacer{pending: []WorkspaceRef{{ID: "ws-ada", OrgID: "org-1", OwnerID: "ada"}}}
if _, err := svc.Backfill(ctx, accounts, first); err != nil {
t.Fatalf("first backfill: %v", err)
}
// Second pass: the workspace now has a unit, so it is no longer pending.
second := &stubPlacer{pending: nil}
result, err := svc.Backfill(ctx, accounts, second)
if err != nil {
t.Fatalf("second backfill: %v", err)
}
if result.Placed != 0 {
t.Fatalf("second pass placed %d workspaces, want 0", result.Placed)
}
units, err := svc.Store().ListByOrg(ctx, "org-1")
if err != nil {
t.Fatalf("list units: %v", err)
}
if len(units) != 2 {
t.Fatalf("units = %d, want a root and one personal unit", len(units))
}
}
// With organizations switched off there are no org rows and every id is empty,
// but the tree is still the reach model, so the implicit organization needs a
// root like any other.
func TestBackfillCreatesRootWithTenancyOff(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
placer := &stubPlacer{pending: []WorkspaceRef{{ID: "ws-1", OwnerID: "ada"}}}
if _, err := svc.Backfill(ctx, stubAccounts{users: []UserRef{{ID: "ada"}}}, placer); err != nil {
t.Fatalf("backfill: %v", err)
}
if _, err := svc.Store().Root(ctx, ""); err != nil {
t.Fatalf("no root for the implicit organization: %v", err)
}
if placer.placed["ws-1"] == "" {
t.Fatal("workspace was left unplaced")
}
}