mirror of
https://github.com/dnote/dnote
synced 2026-03-14 14:35:50 +01:00
Add user list command (#714)
This commit is contained in:
parent
d5e11c23f6
commit
5c416e3a32
6 changed files with 176 additions and 5 deletions
|
|
@ -331,3 +331,23 @@ func TestServerUserCreateHelp(t *testing.T) {
|
|||
assert.Equal(t, strings.Contains(outputStr, "--password"), true, "help should show --password (double dash)")
|
||||
assert.Equal(t, strings.Contains(outputStr, "--dbPath"), true, "help should show --dbPath (double dash)")
|
||||
}
|
||||
|
||||
func TestServerUserList(t *testing.T) {
|
||||
tmpDB := t.TempDir() + "/test.db"
|
||||
|
||||
// Create two users
|
||||
exec.Command(testServerBinary, "user", "create", "--dbPath", tmpDB, "--email", "alice@example.com", "--password", "password123").CombinedOutput()
|
||||
exec.Command(testServerBinary, "user", "create", "--dbPath", tmpDB, "--email", "bob@example.com", "--password", "password123").CombinedOutput()
|
||||
|
||||
// List users
|
||||
listCmd := exec.Command(testServerBinary, "user", "list", "--dbPath", tmpDB)
|
||||
output, err := listCmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("user list failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
outputStr := string(output)
|
||||
assert.Equal(t, strings.Contains(outputStr, "alice@example.com"), true, "output should have alice")
|
||||
assert.Equal(t, strings.Contains(outputStr, "bob@example.com"), true, "output should have bob")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,17 @@ func (a *App) GetUserByEmail(email string) (*database.User, error) {
|
|||
return &user, nil
|
||||
}
|
||||
|
||||
// GetAllUsers retrieves all users from the database
|
||||
func (a *App) GetAllUsers() ([]database.User, error) {
|
||||
var users []database.User
|
||||
err := a.DB.Find(&users).Error
|
||||
if err != nil {
|
||||
return nil, pkgErrors.Wrap(err, "finding users")
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Authenticate authenticates a user
|
||||
func (a *App) Authenticate(email, password string) (*database.User, error) {
|
||||
user, err := a.GetUserByEmail(email)
|
||||
|
|
|
|||
|
|
@ -108,6 +108,72 @@ func TestGetUserByEmail(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGetAllUsers(t *testing.T) {
|
||||
t.Run("success with multiple users", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
user1 := testutils.SetupUserData(db, "alice@example.com", "password123")
|
||||
user2 := testutils.SetupUserData(db, "bob@example.com", "password123")
|
||||
user3 := testutils.SetupUserData(db, "charlie@example.com", "password123")
|
||||
|
||||
a := NewTest()
|
||||
a.DB = db
|
||||
|
||||
users, err := a.GetAllUsers()
|
||||
|
||||
assert.Equal(t, err, nil, "should not error")
|
||||
assert.Equal(t, len(users), 3, "should return 3 users")
|
||||
|
||||
// Verify all users are returned
|
||||
emails := make(map[string]bool)
|
||||
for _, user := range users {
|
||||
emails[user.Email.String] = true
|
||||
}
|
||||
assert.Equal(t, emails["alice@example.com"], true, "alice should be in results")
|
||||
assert.Equal(t, emails["bob@example.com"], true, "bob should be in results")
|
||||
assert.Equal(t, emails["charlie@example.com"], true, "charlie should be in results")
|
||||
|
||||
// Verify user details match
|
||||
for _, user := range users {
|
||||
if user.Email.String == "alice@example.com" {
|
||||
assert.Equal(t, user.ID, user1.ID, "alice ID mismatch")
|
||||
} else if user.Email.String == "bob@example.com" {
|
||||
assert.Equal(t, user.ID, user2.ID, "bob ID mismatch")
|
||||
} else if user.Email.String == "charlie@example.com" {
|
||||
assert.Equal(t, user.ID, user3.ID, "charlie ID mismatch")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty database", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
a := NewTest()
|
||||
a.DB = db
|
||||
|
||||
users, err := a.GetAllUsers()
|
||||
|
||||
assert.Equal(t, err, nil, "should not error")
|
||||
assert.Equal(t, len(users), 0, "should return 0 users")
|
||||
})
|
||||
|
||||
t.Run("single user", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
user := testutils.SetupUserData(db, "alice@example.com", "password123")
|
||||
|
||||
a := NewTest()
|
||||
a.DB = db
|
||||
|
||||
users, err := a.GetAllUsers()
|
||||
|
||||
assert.Equal(t, err, nil, "should not error")
|
||||
assert.Equal(t, len(users), 1, "should return 1 user")
|
||||
assert.Equal(t, users[0].Email.String, "alice@example.com", "email mismatch")
|
||||
assert.Equal(t, users[0].ID, user.ID, "user ID mismatch")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@ func requireString(fs *flag.FlagSet, value, fieldName string) {
|
|||
}
|
||||
}
|
||||
|
||||
// setupAppWithDB creates config, initializes app, and returns cleanup function
|
||||
func setupAppWithDB(fs *flag.FlagSet, dbPath string) (*app.App, func()) {
|
||||
// createApp creates config, initializes app, and returns cleanup function
|
||||
func createApp(fs *flag.FlagSet, dbPath string) (*app.App, func()) {
|
||||
cfg, err := config.New(config.Params{
|
||||
DBPath: dbPath,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func userCreateCmd(args []string) {
|
|||
requireString(fs, *email, "email")
|
||||
requireString(fs, *password, "password")
|
||||
|
||||
a, cleanup := setupAppWithDB(fs, *dbPath)
|
||||
a, cleanup := createApp(fs, *dbPath)
|
||||
defer cleanup()
|
||||
|
||||
_, err := a.CreateUser(*email, *password, *password)
|
||||
|
|
@ -74,7 +74,7 @@ func userRemoveCmd(args []string, stdin io.Reader) {
|
|||
|
||||
requireString(fs, *email, "email")
|
||||
|
||||
a, cleanup := setupAppWithDB(fs, *dbPath)
|
||||
a, cleanup := createApp(fs, *dbPath)
|
||||
defer cleanup()
|
||||
|
||||
// Check if user exists first
|
||||
|
|
@ -127,7 +127,7 @@ func userResetPasswordCmd(args []string) {
|
|||
requireString(fs, *email, "email")
|
||||
requireString(fs, *password, "password")
|
||||
|
||||
a, cleanup := setupAppWithDB(fs, *dbPath)
|
||||
a, cleanup := createApp(fs, *dbPath)
|
||||
defer cleanup()
|
||||
|
||||
// Find the user
|
||||
|
|
@ -151,6 +151,27 @@ func userResetPasswordCmd(args []string) {
|
|||
fmt.Printf("Email: %s\n", *email)
|
||||
}
|
||||
|
||||
func userListCmd(args []string, output io.Writer) {
|
||||
fs := setupFlagSet("list", "dnote-server user list")
|
||||
|
||||
dbPath := fs.String("dbPath", "", "Path to SQLite database file (env: DBPath, default: $XDG_DATA_HOME/dnote/server.db)")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
a, cleanup := createApp(fs, *dbPath)
|
||||
defer cleanup()
|
||||
|
||||
users, err := a.GetAllUsers()
|
||||
if err != nil {
|
||||
log.ErrorWrap(err, "listing users")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
fmt.Fprintf(output, "%s,%s,%s\n", user.UUID, user.Email.String, user.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
}
|
||||
}
|
||||
|
||||
func userCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Println(`Usage:
|
||||
|
|
@ -158,6 +179,7 @@ func userCmd(args []string) {
|
|||
|
||||
Available commands:
|
||||
create: Create a new user
|
||||
list: List all users
|
||||
remove: Remove a user
|
||||
reset-password: Reset a user's password`)
|
||||
os.Exit(1)
|
||||
|
|
@ -172,6 +194,8 @@ Available commands:
|
|||
switch subcommand {
|
||||
case "create":
|
||||
userCreateCmd(subArgs)
|
||||
case "list":
|
||||
userListCmd(subArgs, os.Stdout)
|
||||
case "remove":
|
||||
userRemoveCmd(subArgs, os.Stdin)
|
||||
case "reset-password":
|
||||
|
|
@ -180,6 +204,7 @@ Available commands:
|
|||
fmt.Printf("Unknown subcommand: %s\n\n", subcommand)
|
||||
fmt.Println(`Available commands:
|
||||
create: Create a new user
|
||||
list: List all users
|
||||
remove: Remove a user (only if they have no notes or books)
|
||||
reset-password: Reset a user's password`)
|
||||
os.Exit(1)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -107,3 +109,50 @@ func TestUserResetPasswordCmd(t *testing.T) {
|
|||
err = bcrypt.CompareHashAndPassword([]byte(updatedUser.Password.String), []byte("oldpassword123"))
|
||||
assert.Equal(t, err != nil, true, "old password should not match")
|
||||
}
|
||||
|
||||
func TestUserListCmd(t *testing.T) {
|
||||
t.Run("multiple users", func(t *testing.T) {
|
||||
tmpDB := t.TempDir() + "/test.db"
|
||||
|
||||
// Create multiple users
|
||||
db := testutils.InitDB(tmpDB)
|
||||
user1 := testutils.SetupUserData(db, "alice@example.com", "password123")
|
||||
user2 := testutils.SetupUserData(db, "bob@example.com", "password123")
|
||||
user3 := testutils.SetupUserData(db, "charlie@example.com", "password123")
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.Close()
|
||||
|
||||
// Capture output
|
||||
var buf bytes.Buffer
|
||||
userListCmd([]string{"--dbPath", tmpDB}, &buf)
|
||||
|
||||
// Verify output matches expected format
|
||||
output := strings.TrimSpace(buf.String())
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
expectedLine1 := fmt.Sprintf("%s,alice@example.com,%s", user1.UUID, user1.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
expectedLine2 := fmt.Sprintf("%s,bob@example.com,%s", user2.UUID, user2.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
expectedLine3 := fmt.Sprintf("%s,charlie@example.com,%s", user3.UUID, user3.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
|
||||
assert.Equal(t, lines[0], expectedLine1, "line 1 should match")
|
||||
assert.Equal(t, lines[1], expectedLine2, "line 2 should match")
|
||||
assert.Equal(t, lines[2], expectedLine3, "line 3 should match")
|
||||
})
|
||||
|
||||
t.Run("empty database", func(t *testing.T) {
|
||||
tmpDB := t.TempDir() + "/test.db"
|
||||
|
||||
// Initialize empty database
|
||||
db := testutils.InitDB(tmpDB)
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.Close()
|
||||
|
||||
// Capture output
|
||||
var buf bytes.Buffer
|
||||
userListCmd([]string{"--dbPath", tmpDB}, &buf)
|
||||
|
||||
// Verify no output
|
||||
output := buf.String()
|
||||
assert.Equal(t, output, "", "should have no output for empty database")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue