Files
GitOcean-Old/internal/app/server_users_test.go
T

196 lines
7.4 KiB
Go

package app
import (
"database/sql"
"path/filepath"
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
)
func TestExtractRequiredDatabaseDSN(t *testing.T) {
dsn, args, err := extractRequiredDatabaseDSN([]string{"list", "--dsn", "db", "extra"})
if err != nil {
t.Fatal(err)
}
if dsn != "db" || strings.Join(args, ",") != "list,extra" {
t.Fatalf("dsn=%q args=%#v", dsn, args)
}
dsn, args, err = extractRequiredDatabaseDSN([]string{"--database-url=mysql", "view", "alice"})
if err != nil || dsn != "mysql" || len(args) != 2 || args[0] != "view" {
t.Fatalf("dsn=%q args=%#v err=%v", dsn, args, err)
}
if _, _, err := extractRequiredDatabaseDSN([]string{"list"}); err == nil || !strings.Contains(err.Error(), "database URL") {
t.Fatalf("expected missing dsn error, got %v", err)
}
if _, _, err := extractRequiredDatabaseDSN([]string{"--dsn"}); err == nil || !strings.Contains(err.Error(), "requires") {
t.Fatalf("expected missing dsn value error, got %v", err)
}
}
func TestParseServerUsersArgs(t *testing.T) {
email, username, password, admin, err := parseServerUsersAddArgs([]string{"--email", "a@example.com", "--username", "alice", "--password", "password123", "--admin"})
if err != nil {
t.Fatal(err)
}
if email != "a@example.com" || username != "alice" || password != "password123" || !admin {
t.Fatalf("unexpected add parse: %q %q %q %v", email, username, password, admin)
}
ref, edit, err := parseServerUsersEditArgs([]string{"alice", "--email", "new@example.com", "--username", "newalice", "--password", "password456", "--admin", "false"})
if err != nil {
t.Fatal(err)
}
if ref != "alice" || edit.Email == nil || *edit.Email != "new@example.com" || edit.Admin == nil || *edit.Admin {
t.Fatalf("unexpected edit parse: ref=%q edit=%#v", ref, edit)
}
if _, _, err := parseServerUsersEditArgs([]string{"alice"}); err == nil || !strings.Contains(err.Error(), "no user changes") {
t.Fatalf("expected no changes error, got %v", err)
}
ref, err = parseServerUsersRemoveArgs([]string{"alice", "--force"})
if err != nil || ref != "alice" {
t.Fatalf("remove parse ref=%q err=%v", ref, err)
}
if _, err := parseServerUsersRemoveArgs([]string{"alice"}); err == nil || !strings.Contains(err.Error(), "--force") {
t.Fatalf("expected force error, got %v", err)
}
}
func TestParseFlexibleBool(t *testing.T) {
for _, v := range []string{"true", "1", "yes", "y"} {
got, err := parseFlexibleBool(v)
if err != nil || !got {
t.Fatalf("%q => %v %v", v, got, err)
}
}
for _, v := range []string{"false", "0", "no", "n"} {
got, err := parseFlexibleBool(v)
if err != nil || got {
t.Fatalf("%q => %v %v", v, got, err)
}
}
if _, err := parseFlexibleBool("wat"); err == nil {
t.Fatal("expected invalid bool")
}
}
func TestServerUserWhere(t *testing.T) {
where, args := serverUserWhere("42")
if where != "id = ?" || args[0] != int64(42) {
t.Fatalf("id where=%q args=%#v", where, args)
}
where, args = serverUserWhere("Alice@Example.com")
if where != "email = ?" || args[0] != "alice@example.com" {
t.Fatalf("email where=%q args=%#v", where, args)
}
where, args = serverUserWhere("Alice")
if where != "username = ?" || args[0] != "alice" {
t.Fatalf("username where=%q args=%#v", where, args)
}
}
func TestServerUsersListViewAddEditRemove(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
now := time.Date(2026, 1, 2, 3, 4, 0, 0, time.UTC)
mock.ExpectQuery("SELECT id, email, username, is_admin, created_at FROM users ORDER BY username").WillReturnRows(sqlmock.NewRows([]string{"id", "email", "username", "is_admin", "created_at"}).AddRow(int64(1), "alice@example.com", "alice", true, now))
out := captureStdout(t, func() {
if err := serverUsersList(db); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, "alice") || !strings.Contains(out, "ADMIN") {
t.Fatalf("unexpected list output: %s", out)
}
mock.ExpectQuery("SELECT id, email, username, is_admin, created_at FROM users WHERE username = ").WithArgs("alice").WillReturnRows(sqlmock.NewRows([]string{"id", "email", "username", "is_admin", "created_at"}).AddRow(int64(1), "alice@example.com", "alice", true, now))
out = captureStdout(t, func() {
if err := serverUsersView(db, "Alice"); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, "Username: alice") || !strings.Contains(out, "Admin: true") {
t.Fatalf("unexpected view output: %s", out)
}
mock.ExpectExec("INSERT INTO users").WithArgs("bob@example.com", "bob", sqlmock.AnyArg(), false).WillReturnResult(sqlmock.NewResult(2, 1))
out = captureStdout(t, func() {
if err := serverUsersAdd(db, "Bob@Example.com", "Bob", "password123", false); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, "Created user bob") {
t.Fatalf("unexpected add output: %s", out)
}
admin := false
email := "new@example.com"
edit := serverUserEdit{Email: &email, Admin: &admin}
mock.ExpectExec("UPDATE users SET email = \\?, is_admin = \\? WHERE username = ").WithArgs("new@example.com", false, "alice").WillReturnResult(sqlmock.NewResult(0, 1))
out = captureStdout(t, func() {
if err := serverUsersEditUser(db, "alice", edit); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, "Updated user alice") {
t.Fatalf("unexpected edit output: %s", out)
}
mock.ExpectExec("DELETE FROM users WHERE username = ").WithArgs("alice").WillReturnResult(sqlmock.NewResult(0, 1))
out = captureStdout(t, func() {
if err := serverUsersRemove(db, "alice"); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, "Removed user alice") {
t.Fatalf("unexpected remove output: %s", out)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestServerUsersEditValidationAndNotFound(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
badEmail := "bad"
if err := serverUsersEditUser(db, "alice", serverUserEdit{Email: &badEmail}); err == nil || !strings.Contains(err.Error(), "invalid email") {
t.Fatalf("expected invalid email, got %v", err)
}
shortPassword := "short"
if err := serverUsersEditUser(db, "alice", serverUserEdit{Password: &shortPassword}); err == nil || !strings.Contains(err.Error(), "password") {
t.Fatalf("expected password error, got %v", err)
}
admin := true
mock.ExpectExec("UPDATE users SET is_admin = ").WithArgs(true, "missing").WillReturnResult(sqlmock.NewResult(0, 0))
if err := serverUsersEditUser(db, "missing", serverUserEdit{Admin: &admin}); err != sql.ErrNoRows {
t.Fatalf("expected sql.ErrNoRows, got %v", err)
}
mock.ExpectExec("DELETE FROM users WHERE username = ").WithArgs("missing").WillReturnResult(sqlmock.NewResult(0, 0))
if err := serverUsersRemove(db, "missing"); err != sql.ErrNoRows {
t.Fatalf("expected sql.ErrNoRows remove, got %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestCliServerUsersRequiresDSN(t *testing.T) {
if err := cliServerUsers([]string{"list"}); err == nil || !strings.Contains(err.Error(), "database URL") {
t.Fatalf("expected required dsn, got %v", err)
}
if err := runServer([]string{"users", "list"}); err == nil || !strings.Contains(err.Error(), "database URL") {
t.Fatalf("expected runServer users dsn error, got %v", err)
}
if err := runServer([]string{"--config", filepath.Join(t.TempDir(), "missing.json")}); err == nil || !strings.Contains(err.Error(), "mysql_dsn") {
t.Fatalf("expected normal server dsn error, got %v", err)
}
}