package db import ( "farga/db/types" "farga/lib" "testing" "time" ) func TestSSHKeys(t *testing.T) { db := testDB(t) user := newTestUser(t, "poyo") if err := db.NewUser(user); err != nil { t.Fatalf("NewUser: %v", err) } key, err := db.NewPubKey("laptop", lib.SSHPubKey{ Key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIORiMzyBuNhagHOFNMsKeElwwE8Z9kWqg9M49Bn9KOyd", Fingerprint: "SHA256:abc123", Algorithm: "ssh-ed25519", }, user.ID) if err != nil { t.Fatalf("NewPubKey: %v", err) } if key.UserID != user.ID { t.Errorf("key UserID = %v, want %v", key.UserID, user.ID) } // duplicate fingerprints are rejected if _, err := db.NewPubKey("phone", lib.SSHPubKey{ Key: "ssh-ed25519 something", Fingerprint: "SHA256:abc123", Algorithm: "ssh-ed25519", }, user.ID); err == nil { t.Error("NewPubKey(duplicate fingerprint) = nil error, want error") } // same fingerprint, different user, is still a duplicate other := newTestUser(t, "someone-else") if err := db.NewUser(other); err != nil { t.Fatal(err) } if _, err := db.NewPubKey("phone", lib.SSHPubKey{ Key: "ssh-ed25519 something", Fingerprint: "SHA256:abc123", Algorithm: "ssh-ed25519", }, other.ID); err == nil { t.Error("NewPubKey(duplicate fingerprint, other user) = nil error, want error") } // a second user with their own key resolves to them, not the first key owner if _, err := db.NewPubKey("server", lib.SSHPubKey{ Key: "ssh-ed25519 another", Fingerprint: "SHA256:xyz789", Algorithm: "ssh-ed25519", }, other.ID); err != nil { t.Fatalf("NewPubKey(second key): %v", err) } otherByKey, err := db.UserByFingerprint("SHA256:xyz789") if err != nil { t.Fatalf("UserByFingerprint(second key): %v", err) } if otherByKey.ID != other.ID { t.Errorf("UserByFingerprint(second key) = %v, want %v", otherByKey.ID, other.ID) } // reverse lookup by fingerprint found, err := db.UserByFingerprint("SHA256:abc123") if err != nil { t.Fatalf("UserByFingerprint: %v", err) } if found.ID != user.ID { t.Errorf("UserByFingerprint returned %v, want %v", found.ID, user.ID) } if _, err := db.UserByFingerprint("SHA256:nope"); err == nil { t.Error("UserByFingerprint(unknown) = nil error, want error") } if err := db.DeletePubKey(key.ID, user.ID); err != nil { t.Fatalf("DeletePubKey: %v", err) } if _, err := db.UserByFingerprint("SHA256:abc123"); err == nil { t.Error("UserByFingerprint after delete = nil error, want error") } } func TestIssues(t *testing.T) { db := testDB(t) user := newTestUser(t, "poyo") if err := db.NewUser(user); err != nil { t.Fatalf("NewUser: %v", err) } // NewRepo writes a git repo to disk, so create the row directly. repo := types.Repo{UserID: &user.ID, Name: "farga"} if err := db.Create(&repo).Error; err != nil { t.Fatalf("create repo: %v", err) } issue1 := &types.Issue{RepoID: &repo.ID, UserID: &user.ID, Title: "one", Description: "first issue"} issue2 := &types.Issue{RepoID: &repo.ID, UserID: &user.ID, Title: "two", Description: "second issue"} if err := db.NewIssue(issue1); err != nil { t.Fatalf("NewIssue #1: %v", err) } if err := db.NewIssue(issue2); err != nil { t.Fatalf("NewIssue #2: %v", err) } if issue1.IssueID != 1 || issue2.IssueID != 2 { t.Errorf("IssueIDs = %d, %d, want 1, 2", issue1.IssueID, issue2.IssueID) } got, err := db.Issue(&repo, 2) if err != nil { t.Fatalf("Issue: %v", err) } if got.Title != "two" || got.Description != "second issue" { t.Errorf("Issue #2 = %+v, want two/second issue", got) } if got.Status != "open" { t.Errorf("Issue #2 status = %q, want open", got.Status) } if _, err := db.Issue(&repo, 99); err == nil { t.Error("Issue(unknown number) = nil error, want error") } // closing if err := db.CloseIssue(&got); err != nil { t.Fatalf("CloseIssue: %v", err) } closed, err := db.Issue(&repo, 2) if err != nil { t.Fatalf("Issue after close: %v", err) } if closed.Status != "closed" { t.Errorf("Issue #2 status after close = %q, want closed", closed.Status) } // comments if err := db.NewIssueComment(&types.IssueComment{IssueID: &issue1.ID, UserID: &user.ID, Comment: "first!"}); err != nil { t.Fatalf("NewIssueComment #1: %v", err) } if err := db.NewIssueComment(&types.IssueComment{IssueID: &issue1.ID, UserID: &user.ID, Comment: "second!"}); err != nil { t.Fatalf("NewIssueComment #2: %v", err) } comments, err := db.IssueComments(issue1.ID) if err != nil { t.Fatalf("IssueComments: %v", err) } if len(comments) != 2 { t.Fatalf("IssueComments = %d comments, want 2", len(comments)) } gotComments := map[string]bool{} for _, c := range comments { gotComments[c.Comment] = true } if !gotComments["first!"] || !gotComments["second!"] { t.Errorf("comments = %v, want [first! second!]", comments) } empty, err := db.IssueComments(issue2.ID) if err != nil { t.Fatalf("IssueComments(#2): %v", err) } if len(empty) != 0 { t.Errorf("IssueComments(#2) = %d comments, want 0", len(empty)) } } func TestOAuthStates(t *testing.T) { db := testDB(t) user := newTestUser(t, "poyo") if err := db.NewUser(user); err != nil { t.Fatalf("NewUser: %v", err) } state, err := db.CreateOAuthState(user.ID) if err != nil { t.Fatalf("CreateOAuthState: %v", err) } if state.State == "" || state.Verifier == "" { t.Errorf("state = %+v, want non-empty State and Verifier", state) } if !state.ExpiresAt.After(time.Now()) { t.Error("ExpiresAt is not in the future") } got, err := db.OAuthState(state.State) if err != nil { t.Fatalf("OAuthState: %v", err) } if got.ID != state.ID { t.Errorf("OAuthState = %v, want %v", got.ID, state.ID) } if _, err := db.OAuthState("bogus"); err == nil { t.Error("OAuthState(unknown) = nil error, want error") } // expired states get cleaned up stale := &types.OAuthState{UserID: user.ID, State: "stale-state", Verifier: "v", ExpiresAt: time.Now().Add(-time.Minute)} if err := db.Create(stale).Error; err != nil { t.Fatal(err) } db.DeleteExpiredOAuthStates() if _, err := db.OAuthState("stale-state"); err == nil { t.Error("expired OAuthState was not Deleted") } if err := db.DeleteOAuthState(state.ID); err != nil { t.Fatalf("DeleteOAuthState: %v", err) } if _, err := db.OAuthState(state.State); err == nil { t.Error("OAuthState after DeleteOAuthState = nil error, want error") } } func TestRepoQueries(t *testing.T) { db := testDB(t) user := newTestUser(t, "poyo") if err := db.NewUser(user); err != nil { t.Fatalf("NewUser: %v", err) } repo := types.Repo{UserID: &user.ID, Name: "farga"} if err := db.Create(&repo).Error; err != nil { t.Fatalf("create repo: %v", err) } byName, err := db.RepoByName(&user.ID, "farga") if err != nil { t.Fatalf("RepoByName: %v", err) } if byName.ID != repo.ID { t.Errorf("RepoByName = %v, want %v", byName.ID, repo.ID) } if _, err := db.RepoByName(&user.ID, "nope"); err == nil { t.Error("RepoByName(unknown) = nil error, want error") } byHandle, err := db.RepoHandle("poyo", "farga") if err != nil { t.Fatalf("RepoHandle: %v", err) } if byHandle.ID != repo.ID { t.Errorf("RepoHandle = %v, want %v", byHandle.ID, repo.ID) } if _, err := db.RepoHandle("poyo", "nope"); err == nil { t.Error("RepoHandle(unknown) = nil error, want error") } repos, err := db.UserRepos(&user.ID) if err != nil { t.Fatalf("UserRepos: %v", err) } if len(repos) != 1 || repos[0].ID != repo.ID { t.Fatalf("UserRepos = %+v, want the one repo", repos) } if repos[0].User == nil || repos[0].User.Handle != "poyo" { t.Errorf("UserRepos did not preload User: %+v", repos[0]) } if err := db.SetRepoHackatimeName(repo.ID, "hackatime-name"); err != nil { t.Fatalf("SetRepoHackatimeName: %v", err) } got, _ := db.RepoByName(&user.ID, "farga") if got.HackatimeName != "hackatime-name" { t.Errorf("HackatimeName = %q, want hackatime-name", got.HackatimeName) } if err := db.SetRepoHackatimeName(repo.ID, ""); err != nil { t.Fatalf("SetRepoHackatimeName(\"\"): %v", err) } got, _ = db.RepoByName(&user.ID, "farga") if got.HackatimeName != "" { t.Errorf("HackatimeName = %q, want cleared", got.HackatimeName) } if err := db.SetRepoDefaultBranch(repo, "main"); err != nil { t.Fatalf("SetRepoDefaultBranch: %v", err) } got, _ = db.RepoByName(&user.ID, "farga") if got.BaseBranch != "main" { t.Errorf("BaseBranch = %q, want main", got.BaseBranch) } if err := db.SetRepoVisibility(repo.ID, false); err != nil { t.Fatalf("SetRepoVisibility: %v", err) } got, _ = db.RepoByName(&user.ID, "farga") if !got.IsPrivate { t.Error("repo is not private after SetRepoVisibility(false)") } if err := db.DeleteRepo(repo.ID); err != nil { t.Fatalf("DeleteRepo: %v", err) } if _, err := db.RepoByName(&user.ID, "farga"); err == nil { t.Error("repo still found after DeleteRepo") } }