package store import ( "context" "sync" "testing" ) func TestMemoryConcurrentLastAdminDemotion(t *testing.T) { m := NewMemory() ctx := context.Background() a := &User{Username: "admin_a", PasswordHash: "x", Role: RoleUser} b := &User{Username: "admin_b", PasswordHash: "x", Role: RoleUser} if err := m.CreateUser(ctx, a); err != nil { t.Fatal(err) } if err := m.CreateUser(ctx, b); err != nil { t.Fatal(err) } if err := m.SetUserRole(ctx, a.ID, RoleAdmin); err != nil { t.Fatal(err) } if err := m.SetUserRole(ctx, b.ID, RoleAdmin); err != nil { t.Fatal(err) } var wg sync.WaitGroup errs := make(chan error, 2) wg.Add(2) go func() { defer wg.Done() errs <- m.SetUserRole(ctx, a.ID, RoleUser) }() go func() { defer wg.Done() errs <- m.SetUserRole(ctx, b.ID, RoleUser) }() wg.Wait() close(errs) var ok, lastAdmin int for err := range errs { switch err { case nil: ok++ case ErrLastAdmin: lastAdmin++ default: t.Fatalf("unexpected error: %v", err) } } if ok != 1 || lastAdmin != 1 { t.Fatalf("want 1 success and 1 ErrLastAdmin, got ok=%d lastAdmin=%d", ok, lastAdmin) } n, err := m.CountAdmins(ctx) if err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("admins remaining = %d, want 1", n) } } func TestMemoryConcurrentBootstrapAdmin(t *testing.T) { m := NewMemory() ctx := context.Background() a := &User{Username: "boot_a", PasswordHash: "x", Role: RoleAdmin} b := &User{Username: "boot_b", PasswordHash: "x", Role: RoleAdmin} var wg sync.WaitGroup errs := make(chan error, 2) wg.Add(2) go func() { defer wg.Done() errs <- m.CreateUser(ctx, a) }() go func() { defer wg.Done() errs <- m.CreateUser(ctx, b) }() wg.Wait() close(errs) for err := range errs { if err != nil { t.Fatal(err) } } n, err := m.CountAdmins(ctx) if err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("bootstrap race left %d admins, want 1", n) } if a.Role == RoleAdmin && b.Role == RoleAdmin { t.Fatal("both users kept RoleAdmin") } if a.Role != RoleAdmin && b.Role != RoleAdmin { t.Fatal("neither user is admin") } }