diff --git a/dbi/scholarship.go b/dbi/scholarship.go new file mode 100644 index 0000000..09d8ea3 --- /dev/null +++ b/dbi/scholarship.go @@ -0,0 +1,90 @@ +package dbi + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/google/uuid" +) + +type Scholarship struct { + UID string `json:"uid"` + Citation string `json:"citation"` + Hyperlink string `json:"hyperlink"` + Status string `json:"status"` + Created string `json:"created"` + Modified string `json:"modified"` + Username string `json:"username"` +} + +func CreateScholarship(conn *sql.Conn, s *Scholarship) error { + if s.UID == "" { + s.UID = strings.ReplaceAll(uuid.New().String(), "-", "") + } + now := time.Now().Format("2006-01-02 15:04:05") + s.Created = now + s.Modified = now + _, err := conn.ExecContext(context.Background(), + `INSERT INTO scholarship (UID, Citation, Hyperlink, Status, Created, Modified, Username) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + s.UID, s.Citation, s.Hyperlink, s.Status, s.Created, s.Modified, s.Username, + ) + return err +} + +func GetScholarship(conn *sql.Conn, uid string) (Scholarship, error) { + row := conn.QueryRowContext(context.Background(), + `SELECT UID, Citation, Hyperlink, Status, Created, Modified, Username + FROM scholarship WHERE UID = ?`, + uid, + ) + var s Scholarship + err := row.Scan(&s.UID, &s.Citation, &s.Hyperlink, &s.Status, &s.Created, &s.Modified, &s.Username) + if err == sql.ErrNoRows { + return Scholarship{}, fmt.Errorf("scholarship %q not found", uid) + } + return s, err +} + +func UpdateScholarship(conn *sql.Conn, s *Scholarship) error { + s.Modified = time.Now().Format("2006-01-02 15:04:05") + _, err := conn.ExecContext(context.Background(), + `UPDATE scholarship SET Citation = ?, Hyperlink = ?, Status = ?, Modified = ?, Username = ? + WHERE UID = ?`, + s.Citation, s.Hyperlink, s.Status, s.Modified, s.Username, s.UID, + ) + return err +} + +func DeleteScholarship(conn *sql.Conn, uid string) error { + _, err := conn.ExecContext(context.Background(), + `DELETE FROM scholarship WHERE UID = ?`, + uid, + ) + return err +} + +func GetScholarshipsForUsername(conn *sql.Conn, username string) ([]Scholarship, error) { + rows, err := conn.QueryContext(context.Background(), + `SELECT UID, Citation, Hyperlink, Status, Created, Modified, Username + FROM scholarship WHERE Username = ?`, + username, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + var results []Scholarship + for rows.Next() { + var s Scholarship + if err := rows.Scan(&s.UID, &s.Citation, &s.Hyperlink, &s.Status, &s.Created, &s.Modified, &s.Username); err != nil { + return nil, err + } + results = append(results, s) + } + return results, rows.Err() +} diff --git a/dbi/scholarship_test.go b/dbi/scholarship_test.go new file mode 100644 index 0000000..acfd6f3 --- /dev/null +++ b/dbi/scholarship_test.go @@ -0,0 +1,210 @@ +package dbi + +import ( + "testing" +) + +func genScholarship(username string) Scholarship { + return Scholarship{ + Citation: randString(40), + Hyperlink: "https://" + randString(12) + ".example.com", + Status: "reported", + Username: username, + } +} + +func TestCreateScholarship(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("CreateScholarship: %v", err) + } + + if s.UID == "" { + t.Error("expected UID to be set after create") + } + if s.Created == "" { + t.Error("expected Created to be set after create") + } + if s.Modified == "" { + t.Error("expected Modified to be set after create") + } + + got, err := GetScholarship(conn, s.UID) + if err != nil { + t.Fatalf("GetScholarship after create: %v", err) + } + if got != s { + t.Errorf("got %+v, want %+v", got, s) + } +} + +func TestCreateScholarshipPresetUID(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + s.UID = "preset" + randString(10) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("CreateScholarship: %v", err) + } + if s.UID != "preset"+s.UID[6:] { + t.Error("CreateScholarship should not overwrite a non-empty UID") + } + + got, err := GetScholarship(conn, s.UID) + if err != nil { + t.Fatalf("GetScholarship: %v", err) + } + if got.UID != s.UID { + t.Errorf("got UID %q, want %q", got.UID, s.UID) + } +} + +func TestCreateScholarshipDuplicateUID(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("first CreateScholarship: %v", err) + } + s2 := s + if err := CreateScholarship(conn, &s2); err == nil { + t.Error("expected error on duplicate UID, got nil") + } +} + +func TestGetScholarship(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("CreateScholarship: %v", err) + } + + got, err := GetScholarship(conn, s.UID) + if err != nil { + t.Fatalf("GetScholarship: %v", err) + } + if got != s { + t.Errorf("got %+v, want %+v", got, s) + } +} + +func TestGetScholarshipNotFound(t *testing.T) { + conn := testConn(t, testDB(t)) + + _, err := GetScholarship(conn, randString(32)) + if err == nil { + t.Error("expected error for missing scholarship, got nil") + } +} + +func TestUpdateScholarship(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("CreateScholarship: %v", err) + } + + originalUID := s.UID + originalCreated := s.Created + s.Citation = randString(40) + s.Hyperlink = "https://" + randString(12) + ".example.com" + s.Status = "posted" + + if err := UpdateScholarship(conn, &s); err != nil { + t.Fatalf("UpdateScholarship: %v", err) + } + + got, err := GetScholarship(conn, s.UID) + if err != nil { + t.Fatalf("GetScholarship after update: %v", err) + } + if got.UID != originalUID { + t.Errorf("UID changed: got %q, want %q", got.UID, originalUID) + } + if got.Created != originalCreated { + t.Errorf("Created changed: got %q, want %q", got.Created, originalCreated) + } + if got.Citation != s.Citation { + t.Errorf("Citation not updated: got %q, want %q", got.Citation, s.Citation) + } + if got.Status != "posted" { + t.Errorf("Status not updated: got %q", got.Status) + } + if got.Modified == "" { + t.Error("Modified should be set after update") + } +} + +func TestDeleteScholarship(t *testing.T) { + conn := testConn(t, testDB(t)) + s := genScholarship(randString(10)) + + if err := CreateScholarship(conn, &s); err != nil { + t.Fatalf("CreateScholarship: %v", err) + } + + if err := DeleteScholarship(conn, s.UID); err != nil { + t.Fatalf("DeleteScholarship: %v", err) + } + + _, err := GetScholarship(conn, s.UID) + if err == nil { + t.Error("expected error after delete, got nil") + } +} + +func TestGetScholarshipsForUsername(t *testing.T) { + conn := testConn(t, testDB(t)) + username := randString(10) + + items := make([]Scholarship, 3) + for i := range items { + items[i] = genScholarship(username) + if err := CreateScholarship(conn, &items[i]); err != nil { + t.Fatalf("CreateScholarship %d: %v", i, err) + } + } + // extra item belonging to a different user + other := genScholarship(randString(10)) + if err := CreateScholarship(conn, &other); err != nil { + t.Fatalf("CreateScholarship other: %v", err) + } + + got, err := GetScholarshipsForUsername(conn, username) + if err != nil { + t.Fatalf("GetScholarshipsForUsername: %v", err) + } + if len(got) != 3 { + t.Errorf("got %d scholarships, want 3", len(got)) + } + byUID := make(map[string]Scholarship) + for _, s := range got { + byUID[s.UID] = s + } + for _, want := range items { + s, ok := byUID[want.UID] + if !ok { + t.Errorf("scholarship %q missing from results", want.UID) + continue + } + if s != want { + t.Errorf("scholarship %q: got %+v, want %+v", want.UID, s, want) + } + } +} + +func TestGetScholarshipsForUsernameEmpty(t *testing.T) { + conn := testConn(t, testDB(t)) + + got, err := GetScholarshipsForUsername(conn, randString(10)) + if err != nil { + t.Fatalf("GetScholarshipsForUsername: %v", err) + } + if len(got) != 0 { + t.Errorf("expected empty slice, got %d items", len(got)) + } +}