196 lines
7.4 KiB
Go
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)
|
|
}
|
|
}
|