From 86ebe6bb9028b3ece5d405d1dea59e5af8ca8a24 Mon Sep 17 00:00:00 2001 From: Joshua Herring Date: Wed, 27 May 2026 12:29:54 -0400 Subject: [PATCH] add user type --- dbi/user.go | 56 +++++++++++++++++ dbi/user_test.go | 152 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 dbi/user.go create mode 100644 dbi/user_test.go diff --git a/dbi/user.go b/dbi/user.go new file mode 100644 index 0000000..9201ed7 --- /dev/null +++ b/dbi/user.go @@ -0,0 +1,56 @@ +package dbi + +import ( + "context" + "database/sql" + "fmt" +) + +type User struct { + APIKey string `json:"api_key"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Password string `json:"password"` + Status string `json:"status"` + Username string `json:"username"` +} + +func CreateUser(conn *sql.Conn, user *User) error { + _, err := conn.ExecContext(context.Background(), + `INSERT INTO users (APIKey, FirstName, LastName, Password, Status, Username) + VALUES (?, ?, ?, ?, ?, ?)`, + user.APIKey, user.FirstName, user.LastName, user.Password, user.Status, user.Username, + ) + return err +} + +func GetUser(conn *sql.Conn, username string) (User, error) { + row := conn.QueryRowContext(context.Background(), + `SELECT APIKey, FirstName, LastName, Password, Status, Username + FROM users WHERE Username = ?`, + username, + ) + var u User + err := row.Scan(&u.APIKey, &u.FirstName, &u.LastName, &u.Password, &u.Status, &u.Username) + if err == sql.ErrNoRows { + return User{}, fmt.Errorf("user %q not found", username) + } + return u, err +} + +func UpdateUser(conn *sql.Conn, user *User) error { + _, err := conn.ExecContext(context.Background(), + `UPDATE users SET APIKey = ?, FirstName = ?, LastName = ?, Password = ?, Status = ? + WHERE Username = ?`, + user.APIKey, user.FirstName, user.LastName, user.Password, user.Status, user.Username, + ) + return err +} + +func DeleteUser(conn *sql.Conn, username string) error { + _, err := conn.ExecContext(context.Background(), + `DELETE FROM users WHERE Username = ?`, + username, + ) + return err +} diff --git a/dbi/user_test.go b/dbi/user_test.go new file mode 100644 index 0000000..612dbeb --- /dev/null +++ b/dbi/user_test.go @@ -0,0 +1,152 @@ +package dbi + +import ( + "context" + "database/sql" + "math/rand" + "testing" + + _ "modernc.org/sqlite" +) + +func testDB(t *testing.T) *sql.DB { + t.Helper() + db, err := sql.Open("sqlite", ":memory:") + if err != nil { + t.Fatalf("open in-memory db: %v", err) + } + if _, err := db.Exec(ActivityReportSchema); err != nil { + t.Fatalf("apply schema: %v", err) + } + t.Cleanup(func() { db.Close() }) + return db +} + +func testConn(t *testing.T, db *sql.DB) *sql.Conn { + t.Helper() + conn, err := db.Conn(context.Background()) + if err != nil { + t.Fatalf("get conn: %v", err) + } + t.Cleanup(func() { conn.Close() }) + return conn +} + +func randString(n int) string { + const letters = "abcdefghijklmnopqrstuvwxyz" + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +func genUser() User { + return User{ + APIKey: randString(16), + FirstName: randString(8), + LastName: randString(8), + Password: randString(20), + Status: "faculty", + Username: randString(10), + } +} + +func TestCreateUser(t *testing.T) { + conn := testConn(t, testDB(t)) + u := genUser() + + if err := CreateUser(conn, &u); err != nil { + t.Fatalf("CreateUser: %v", err) + } + + got, err := GetUser(conn, u.Username) + if err != nil { + t.Fatalf("GetUser after create: %v", err) + } + if got != u { + t.Errorf("got %+v, want %+v", got, u) + } +} + +func TestCreateUserDuplicateUsername(t *testing.T) { + conn := testConn(t, testDB(t)) + u := genUser() + + if err := CreateUser(conn, &u); err != nil { + t.Fatalf("first CreateUser: %v", err) + } + if err := CreateUser(conn, &u); err == nil { + t.Error("expected error on duplicate username, got nil") + } +} + +func TestGetUser(t *testing.T) { + conn := testConn(t, testDB(t)) + u := genUser() + + if err := CreateUser(conn, &u); err != nil { + t.Fatalf("CreateUser: %v", err) + } + + got, err := GetUser(conn, u.Username) + if err != nil { + t.Fatalf("GetUser: %v", err) + } + if got != u { + t.Errorf("got %+v, want %+v", got, u) + } +} + +func TestGetUserNotFound(t *testing.T) { + conn := testConn(t, testDB(t)) + + _, err := GetUser(conn, randString(10)) + if err == nil { + t.Error("expected error for missing user, got nil") + } +} + +func TestUpdateUser(t *testing.T) { + conn := testConn(t, testDB(t)) + u := genUser() + + if err := CreateUser(conn, &u); err != nil { + t.Fatalf("CreateUser: %v", err) + } + + u.FirstName = randString(8) + u.LastName = randString(8) + u.APIKey = randString(16) + u.Status = "admin" + + if err := UpdateUser(conn, &u); err != nil { + t.Fatalf("UpdateUser: %v", err) + } + + got, err := GetUser(conn, u.Username) + if err != nil { + t.Fatalf("GetUser after update: %v", err) + } + if got != u { + t.Errorf("got %+v, want %+v", got, u) + } +} + +func TestDeleteUser(t *testing.T) { + conn := testConn(t, testDB(t)) + u := genUser() + + if err := CreateUser(conn, &u); err != nil { + t.Fatalf("CreateUser: %v", err) + } + + if err := DeleteUser(conn, u.Username); err != nil { + t.Fatalf("DeleteUser: %v", err) + } + + _, err := GetUser(conn, u.Username) + if err == nil { + t.Error("expected error after delete, got nil") + } +}