package app import ( "database/sql" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/DATA-DOG/go-sqlmock" ) func prRows(pr PullRequest) *sqlmock.Rows { created := pr.CreatedAt if created.IsZero() { created = time.Now().UTC() } updated := pr.UpdatedAt if updated.IsZero() { updated = created } closed := sql.NullTime{} if pr.ClosedAt != nil { closed = sql.NullTime{Time: *pr.ClosedAt, Valid: true} } merged := sql.NullTime{} if pr.MergedAt != nil { merged = sql.NullTime{Time: *pr.MergedAt, Valid: true} } return sqlmock.NewRows([]string{"id", "number", "target_repository_id", "source_repository_id", "author_user_id", "author", "source_owner", "source_repo", "source_branch", "target_owner", "target_repo", "target_branch", "title", "description", "status", "created_at", "updated_at", "closed_at", "merged_at"}). AddRow(pr.ID, pr.Number, pr.TargetRepositoryID, pr.SourceRepositoryID, pr.AuthorUserID, pr.Author, pr.SourceOwner, pr.SourceRepo, pr.SourceBranch, pr.TargetOwner, pr.TargetRepo, pr.TargetBranch, pr.Title, pr.Description, pr.Status, created, updated, closed, merged) } func TestPRListViewCloseAndComments(t *testing.T) { s, mock, cleanup := newMockServer(t) defer cleanup() owner := User{ID: 1, Email: "alice@example.com", Username: "alice"} token := "tok" repo := Repository{ID: 10, OwnerUserID: owner.ID, Owner: "alice", Name: "demo", Visibility: "public", DefaultBranch: "main"} pr := PullRequest{ID: 30, Number: 2, TargetRepositoryID: repo.ID, SourceRepositoryID: repo.ID, AuthorUserID: owner.ID, Author: "alice", SourceOwner: "alice", SourceRepo: "demo", SourceBranch: "feature", TargetOwner: "alice", TargetRepo: "demo", TargetBranch: "main", Title: "Fix", Description: "desc", Status: "open"} expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID).WillReturnRows(prRows(pr)) rr := httptest.NewRecorder() s.handlePRList(rr, httptest.NewRequest(http.MethodGet, "/api/repos/alice/demo/pulls", nil), "alice", "demo") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "Fix") { t.Fatalf("list status=%d body=%s", rr.Code, rr.Body.String()) } expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID, pr.Number).WillReturnRows(prRows(pr)) rr = httptest.NewRecorder() s.handlePRView(rr, httptest.NewRequest(http.MethodGet, "/api/repos/alice/demo/pulls/2", nil), "alice", "demo", 2) if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "Fix") { t.Fatalf("view status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, owner) expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectExec("UPDATE pull_requests SET status = 'closed'").WithArgs(repo.ID, pr.Number).WillReturnResult(sqlmock.NewResult(0, 1)) closedPR := pr closedPR.Status = "closed" mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID, pr.Number).WillReturnRows(prRows(closedPR)) rr = httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/repos/alice/demo/pulls/2/close", nil) req.Header.Set("Authorization", "Bearer "+token) s.handlePRClose(rr, req, "alice", "demo", 2) if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "closed") { t.Fatalf("close status=%d body=%s", rr.Code, rr.Body.String()) } expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID, pr.Number).WillReturnRows(prRows(pr)) mock.ExpectQuery("SELECT c.id, u.username").WithArgs(pr.ID).WillReturnRows(sqlmock.NewRows([]string{"id", "username", "body", "created_at", "updated_at"}).AddRow(int64(1), "alice", "hello", time.Now().UTC(), time.Now().UTC())) rr = httptest.NewRecorder() s.handlePRCommentsList(rr, httptest.NewRequest(http.MethodGet, "/api/repos/alice/demo/pulls/2/comments", nil), "alice", "demo", 2) if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "hello") { t.Fatalf("comments list status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, owner) expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID, pr.Number).WillReturnRows(prRows(pr)) mock.ExpectExec("INSERT INTO pull_request_comments").WithArgs(pr.ID, owner.ID, "hello").WillReturnResult(sqlmock.NewResult(5, 1)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodPost, "/api/repos/alice/demo/pulls/2/comments", strings.NewReader(`{"body":" hello "}`)) req.Header.Set("Authorization", "Bearer "+token) s.handlePRCommentCreate(rr, req, "alice", "demo", 2) if rr.Code != http.StatusCreated || !strings.Contains(rr.Body.String(), "hello") { t.Fatalf("comment create status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, owner) expectLoadRepo(mock, "alice", "demo", repo) mock.ExpectQuery("FROM pull_requests pr").WithArgs(repo.ID, pr.Number).WillReturnRows(prRows(pr)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodPost, "/api/repos/alice/demo/pulls/2/comments", strings.NewReader(`{"body":" "}`)) req.Header.Set("Authorization", "Bearer "+token) s.handlePRCommentCreate(rr, req, "alice", "demo", 2) if rr.Code != http.StatusBadRequest { t.Fatalf("empty comment status=%d body=%s", rr.Code, rr.Body.String()) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }