91 lines
2.9 KiB
Go
91 lines
2.9 KiB
Go
package controlplane
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func testUserStore(t *testing.T) *UserStore {
|
|
t.Helper()
|
|
db, err := openSQLite(filepath.Join(t.TempDir(), "users.sqlite"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
store, err := NewUserStore(db)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return store
|
|
}
|
|
|
|
func TestUserStoreBootstrapAndAuthentication(t *testing.T) {
|
|
store := testUserStore(t)
|
|
ctx := context.Background()
|
|
if err := store.BootstrapAdmins(ctx, map[string]string{"root": "bootstrap-password"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
user, ok, err := store.Authenticate(ctx, "ROOT", "bootstrap-password")
|
|
if err != nil || !ok {
|
|
t.Fatalf("expected case-insensitive bootstrap login, got ok=%v err=%v", ok, err)
|
|
}
|
|
if user.Role != RoleAdmin || !user.Active {
|
|
t.Fatalf("unexpected bootstrap user: %+v", user)
|
|
}
|
|
if _, ok, err := store.Authenticate(ctx, "root", "wrong-password"); err != nil || ok {
|
|
t.Fatalf("expected invalid password, got ok=%v err=%v", ok, err)
|
|
}
|
|
if err := store.BootstrapAdmins(ctx, map[string]string{"root": "changed-password"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok, err := store.Authenticate(ctx, "root", "bootstrap-password"); err != nil || !ok {
|
|
t.Fatalf("bootstrap must not overwrite persisted credentials: ok=%v err=%v", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestUserStoreAssignmentsAndLastAdministrator(t *testing.T) {
|
|
store := testUserStore(t)
|
|
ctx := context.Background()
|
|
if err := store.BootstrapAdmins(ctx, map[string]string{"root": "bootstrap-password"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
member, err := store.Create(ctx, WebUser{
|
|
Username: "operator-1", DisplayName: "Operator One", Role: RoleMember,
|
|
NodeIDs: []string{"node-a", "node-a"},
|
|
}, "member-password-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(member.NodeIDs) != 1 || member.NodeIDs[0] != "node-a" {
|
|
t.Fatalf("expected de-duplicated Node assignment, got %v", member.NodeIDs)
|
|
}
|
|
if _, err := store.Create(ctx, WebUser{Username: "OPERATOR-1", DisplayName: "Duplicate", Role: RoleMember}, "member-password-2"); !errors.Is(err, ErrUserAlreadyExists) {
|
|
t.Fatalf("expected case-insensitive duplicate rejection, got %v", err)
|
|
}
|
|
if _, err := store.Update(ctx, member.ID, UserUpdate{
|
|
DisplayName: "Operator Updated", Role: RoleMember, Active: false, NodeIDs: []string{"node-b"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
updated, err := store.Get(ctx, member.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if updated.Active || len(updated.NodeIDs) != 1 || updated.NodeIDs[0] != "node-b" {
|
|
t.Fatalf("user update did not persist: %+v", updated)
|
|
}
|
|
admins, err := store.List(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, admin := range admins {
|
|
if admin.Role == RoleAdmin {
|
|
if _, err := store.Update(ctx, admin.ID, UserUpdate{DisplayName: admin.DisplayName, Role: RoleMember, Active: true}); !errors.Is(err, ErrLastAdministrator) {
|
|
t.Fatalf("expected last administrator protection, got %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|