mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-01-15 17:21:46 +01:00
Improve documentation and add more tests
This commit is contained in:
596
internal/repository/user_test.go
Normal file
596
internal/repository/user_test.go
Normal file
@@ -0,0 +1,596 @@
|
||||
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||
// All rights reserved. This file is part of cc-backend.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestAddUser(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
t.Run("add user with all fields", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "testuser1",
|
||||
Name: "Test User One",
|
||||
Email: "test1@example.com",
|
||||
Password: "testpassword123",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{"project1", "project2"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrievedUser, err := r.GetUser("testuser1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.Username, retrievedUser.Username)
|
||||
assert.Equal(t, user.Name, retrievedUser.Name)
|
||||
assert.Equal(t, user.Email, retrievedUser.Email)
|
||||
assert.Equal(t, user.Roles, retrievedUser.Roles)
|
||||
assert.Equal(t, user.Projects, retrievedUser.Projects)
|
||||
assert.NotEmpty(t, retrievedUser.Password)
|
||||
err = bcrypt.CompareHashAndPassword([]byte(retrievedUser.Password), []byte("testpassword123"))
|
||||
assert.NoError(t, err, "Password should be hashed correctly")
|
||||
|
||||
err = r.DelUser("testuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add user with minimal fields", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "testuser2",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLDAP,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrievedUser, err := r.GetUser("testuser2")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.Username, retrievedUser.Username)
|
||||
assert.Equal(t, "", retrievedUser.Name)
|
||||
assert.Equal(t, "", retrievedUser.Email)
|
||||
assert.Equal(t, "", retrievedUser.Password)
|
||||
|
||||
err = r.DelUser("testuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add duplicate user fails", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "testuser3",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddUser(user)
|
||||
assert.Error(t, err, "Adding duplicate user should fail")
|
||||
|
||||
err = r.DelUser("testuser3")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
t.Run("get existing user", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "getuser1",
|
||||
Name: "Get User",
|
||||
Email: "getuser@example.com",
|
||||
Roles: []string{"user", "admin"},
|
||||
Projects: []string{"proj1"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("getuser1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.Username, retrieved.Username)
|
||||
assert.Equal(t, user.Name, retrieved.Name)
|
||||
assert.Equal(t, user.Email, retrieved.Email)
|
||||
assert.ElementsMatch(t, user.Roles, retrieved.Roles)
|
||||
assert.ElementsMatch(t, user.Projects, retrieved.Projects)
|
||||
|
||||
err = r.DelUser("getuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("get non-existent user", func(t *testing.T) {
|
||||
_, err := r.GetUser("nonexistent")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
t.Run("update user name", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "updateuser1",
|
||||
Name: "Original Name",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
dbUser, err := r.GetUser("updateuser1")
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedUser := &schema.User{
|
||||
Username: "updateuser1",
|
||||
Name: "Updated Name",
|
||||
}
|
||||
|
||||
err = r.UpdateUser(dbUser, updatedUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("updateuser1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Name", retrieved.Name)
|
||||
|
||||
err = r.DelUser("updateuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("update with no changes", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "updateuser2",
|
||||
Name: "Same Name",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
dbUser, err := r.GetUser("updateuser2")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.UpdateUser(dbUser, dbUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = r.DelUser("updateuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelUser(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
t.Run("delete existing user", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "deluser1",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.DelUser("deluser1")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.GetUser("deluser1")
|
||||
assert.Error(t, err, "User should not exist after deletion")
|
||||
})
|
||||
|
||||
t.Run("delete non-existent user", func(t *testing.T) {
|
||||
err := r.DelUser("nonexistent")
|
||||
assert.NoError(t, err, "Deleting non-existent user should not error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestListUsers(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
user1 := &schema.User{
|
||||
Username: "listuser1",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
user2 := &schema.User{
|
||||
Username: "listuser2",
|
||||
Roles: []string{"admin"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
user3 := &schema.User{
|
||||
Username: "listuser3",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{"proj1"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user1)
|
||||
require.NoError(t, err)
|
||||
err = r.AddUser(user2)
|
||||
require.NoError(t, err)
|
||||
err = r.AddUser(user3)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("list all users", func(t *testing.T) {
|
||||
users, err := r.ListUsers(false)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(users), 3)
|
||||
|
||||
usernames := make([]string, len(users))
|
||||
for i, u := range users {
|
||||
usernames[i] = u.Username
|
||||
}
|
||||
assert.Contains(t, usernames, "listuser1")
|
||||
assert.Contains(t, usernames, "listuser2")
|
||||
assert.Contains(t, usernames, "listuser3")
|
||||
})
|
||||
|
||||
t.Run("list special users only", func(t *testing.T) {
|
||||
users, err := r.ListUsers(true)
|
||||
require.NoError(t, err)
|
||||
|
||||
usernames := make([]string, len(users))
|
||||
for i, u := range users {
|
||||
usernames[i] = u.Username
|
||||
}
|
||||
assert.Contains(t, usernames, "listuser2")
|
||||
assert.Contains(t, usernames, "listuser3")
|
||||
})
|
||||
|
||||
err = r.DelUser("listuser1")
|
||||
require.NoError(t, err)
|
||||
err = r.DelUser("listuser2")
|
||||
require.NoError(t, err)
|
||||
err = r.DelUser("listuser3")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetLdapUsernames(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
|
||||
ldapUser := &schema.User{
|
||||
Username: "ldapuser1",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLDAP,
|
||||
}
|
||||
localUser := &schema.User{
|
||||
Username: "localuser1",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(ldapUser)
|
||||
require.NoError(t, err)
|
||||
err = r.AddUser(localUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
usernames, err := r.GetLdapUsernames()
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, usernames, "ldapuser1")
|
||||
assert.NotContains(t, usernames, "localuser1")
|
||||
|
||||
err = r.DelUser("ldapuser1")
|
||||
require.NoError(t, err)
|
||||
err = r.DelUser("localuser1")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAddRole(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("add valid role", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "roleuser1",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddRole(ctx, "roleuser1", "admin")
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("roleuser1")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, retrieved.Roles, "admin")
|
||||
assert.Contains(t, retrieved.Roles, "user")
|
||||
|
||||
err = r.DelUser("roleuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add duplicate role", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "roleuser2",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddRole(ctx, "roleuser2", "user")
|
||||
assert.Error(t, err, "Adding duplicate role should fail")
|
||||
assert.Contains(t, err.Error(), "already has role")
|
||||
|
||||
err = r.DelUser("roleuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add invalid role", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "roleuser3",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddRole(ctx, "roleuser3", "invalidrole")
|
||||
assert.Error(t, err, "Adding invalid role should fail")
|
||||
assert.Contains(t, err.Error(), "no valid option")
|
||||
|
||||
err = r.DelUser("roleuser3")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveRole(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("remove existing role", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmroleuser1",
|
||||
Roles: []string{"user", "admin"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveRole(ctx, "rmroleuser1", "admin")
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("rmroleuser1")
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, retrieved.Roles, "admin")
|
||||
assert.Contains(t, retrieved.Roles, "user")
|
||||
|
||||
err = r.DelUser("rmroleuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("remove non-existent role", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmroleuser2",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveRole(ctx, "rmroleuser2", "admin")
|
||||
assert.Error(t, err, "Removing non-existent role should fail")
|
||||
assert.Contains(t, err.Error(), "already deleted")
|
||||
|
||||
err = r.DelUser("rmroleuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("remove manager role with projects", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmroleuser3",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{"proj1", "proj2"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveRole(ctx, "rmroleuser3", "manager")
|
||||
assert.Error(t, err, "Removing manager role with projects should fail")
|
||||
assert.Contains(t, err.Error(), "still has assigned project")
|
||||
|
||||
err = r.DelUser("rmroleuser3")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("add project to manager", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "projuser1",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddProject(ctx, "projuser1", "newproject")
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("projuser1")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, retrieved.Projects, "newproject")
|
||||
|
||||
err = r.DelUser("projuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add project to non-manager", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "projuser2",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddProject(ctx, "projuser2", "newproject")
|
||||
assert.Error(t, err, "Adding project to non-manager should fail")
|
||||
assert.Contains(t, err.Error(), "not a manager")
|
||||
|
||||
err = r.DelUser("projuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("add duplicate project", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "projuser3",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{"existingproject"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.AddProject(ctx, "projuser3", "existingproject")
|
||||
assert.Error(t, err, "Adding duplicate project should fail")
|
||||
assert.Contains(t, err.Error(), "already manages")
|
||||
|
||||
err = r.DelUser("projuser3")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveProject(t *testing.T) {
|
||||
_ = setup(t)
|
||||
r := GetUserRepository()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("remove existing project", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmprojuser1",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{"proj1", "proj2"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveProject(ctx, "rmprojuser1", "proj1")
|
||||
require.NoError(t, err)
|
||||
|
||||
retrieved, err := r.GetUser("rmprojuser1")
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, retrieved.Projects, "proj1")
|
||||
assert.Contains(t, retrieved.Projects, "proj2")
|
||||
|
||||
err = r.DelUser("rmprojuser1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("remove non-existent project", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmprojuser2",
|
||||
Roles: []string{"manager"},
|
||||
Projects: []string{"proj1"},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveProject(ctx, "rmprojuser2", "nonexistent")
|
||||
assert.Error(t, err, "Removing non-existent project should fail")
|
||||
|
||||
err = r.DelUser("rmprojuser2")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("remove project from non-manager", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "rmprojuser3",
|
||||
Roles: []string{"user"},
|
||||
Projects: []string{},
|
||||
AuthSource: schema.AuthViaLocalPassword,
|
||||
}
|
||||
|
||||
err := r.AddUser(user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = r.RemoveProject(ctx, "rmprojuser3", "proj1")
|
||||
assert.Error(t, err, "Removing project from non-manager should fail")
|
||||
assert.Contains(t, err.Error(), "not a manager")
|
||||
|
||||
err = r.DelUser("rmprojuser3")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetUserFromContext(t *testing.T) {
|
||||
t.Run("get user from context", func(t *testing.T) {
|
||||
user := &schema.User{
|
||||
Username: "contextuser",
|
||||
Roles: []string{"user"},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), ContextUserKey, user)
|
||||
retrieved := GetUserFromContext(ctx)
|
||||
|
||||
require.NotNil(t, retrieved)
|
||||
assert.Equal(t, user.Username, retrieved.Username)
|
||||
})
|
||||
|
||||
t.Run("get user from empty context", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
retrieved := GetUserFromContext(ctx)
|
||||
|
||||
assert.Nil(t, retrieved)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user