From 0b503e5e8690439a4eca58de4e9b3244b14779d6 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Tue, 16 Jan 2024 10:28:09 +0000
Subject: [PATCH] [GITEA] DELETE
 /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}

* reuse deleteIssueComment by adding the commentType parameter
* ensure tests start with a PR with no random reviews from fixtures

Refs: https://codeberg.org/forgejo/forgejo/issues/2109
(cherry picked from commit 5b90ab77f67e4c0ac17d8b1101453d7790fa45d2)
(cherry picked from commit 28ecd6f5a67891788ad4d989311050df55deb008)
(cherry picked from commit 24870cf133153f0fdefb76df58fe074ae6aef7c0)
---
 routers/api/v1/api.go                     |  6 ++-
 routers/api/v1/repo/issue_comment.go      |  8 +--
 routers/api/v1/repo/pull_review.go        | 47 +++++++++++++++++
 templates/swagger/v1_json.tmpl            | 61 +++++++++++++++++++++++
 tests/integration/api_pull_review_test.go | 40 ++++++++++++++-
 5 files changed, 156 insertions(+), 6 deletions(-)

diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 6e800fdee8..2245f9093a 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1265,7 +1265,11 @@ func Routes() *web.Route {
 									m.Combo("").
 										Get(repo.GetPullReviewComments).
 										Post(reqToken(), bind(api.CreatePullReviewCommentOptions{}), repo.CreatePullReviewComment)
-									m.Get("/{comment}", commentAssignment("comment"), repo.GetPullReviewComment)
+									m.Group("/{comment}", func() {
+										m.Combo("").
+											Get(repo.GetPullReviewComment).
+											Delete(reqToken(), repo.DeletePullReviewComment)
+									}, commentAssignment("comment"))
 								})
 								m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
 								m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 91de21db40..7cd44762e3 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -624,7 +624,7 @@ func DeleteIssueComment(ctx *context.APIContext) {
 	//   "404":
 	//     "$ref": "#/responses/notFound"
 
-	deleteIssueComment(ctx)
+	deleteIssueComment(ctx, issues_model.CommentTypeComment)
 }
 
 // DeleteIssueCommentDeprecated delete a comment from an issue
@@ -663,16 +663,16 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
 	//   "404":
 	//     "$ref": "#/responses/notFound"
 
-	deleteIssueComment(ctx)
+	deleteIssueComment(ctx, issues_model.CommentTypeComment)
 }
 
-func deleteIssueComment(ctx *context.APIContext) {
+func deleteIssueComment(ctx *context.APIContext, commentType issues_model.CommentType) {
 	comment := ctx.Comment
 
 	if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
 		ctx.Status(http.StatusForbidden)
 		return
-	} else if comment.Type != issues_model.CommentTypeComment {
+	} else if comment.Type != commentType {
 		ctx.Status(http.StatusNoContent)
 		return
 	}
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index ad9e2c5981..ab8deab362 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -1014,6 +1014,53 @@ func UnDismissPullReview(ctx *context.APIContext) {
 	dismissReview(ctx, "", false, false)
 }
 
+// DeletePullReviewComment delete a pull review comment
+func DeletePullReviewComment(ctx *context.APIContext) {
+	// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment} repository repoDeletePullReviewComment
+	// ---
+	// summary: Delete a pull review comment
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo
+	//   type: string
+	//   required: true
+	// - name: index
+	//   in: path
+	//   description: index of the pull request
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: id
+	//   in: path
+	//   description: id of the review
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: comment
+	//   in: path
+	//   description: id of the comment
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// responses:
+	//   "204":
+	//     "$ref": "#/responses/empty"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	deleteIssueComment(ctx, issues_model.CommentTypeCode)
+}
+
 func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
 	if !ctx.Repo.IsAdmin() {
 		ctx.Error(http.StatusForbidden, "", "Must be repo admin")
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 1c5ef5ab29..453d8b0809 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -11666,6 +11666,67 @@
             "$ref": "#/responses/notFound"
           }
         }
+      },
+      "delete": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Delete a pull review comment",
+        "operationId": "repoDeletePullReviewComment",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "integer",
+            "format": "int64",
+            "description": "index of the pull request",
+            "name": "index",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "integer",
+            "format": "int64",
+            "description": "id of the review",
+            "name": "id",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "integer",
+            "format": "int64",
+            "description": "id of the comment",
+            "name": "comment",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "204": {
+            "$ref": "#/responses/empty"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
       }
     },
     "/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals": {
diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go
index db44e1ade5..c66c7d752d 100644
--- a/tests/integration/api_pull_review_test.go
+++ b/tests/integration/api_pull_review_test.go
@@ -21,7 +21,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestAPIPullReviewCreateComment(t *testing.T) {
+func TestAPIPullReviewCreateDeleteComment(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
 	pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
 	assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext))
@@ -47,6 +47,30 @@ func TestAPIPullReviewCreateComment(t *testing.T) {
 			var review api.PullReview
 			var reviewLine int64 = 1
 
+			// cleanup
+			{
+				session := loginUser(t, "user1")
+				token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+
+				req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/pulls/%d/reviews", repo.FullName(), pullIssue.Index).AddTokenAuth(token)
+				resp := MakeRequest(t, req, http.StatusOK)
+				var reviews []*api.PullReview
+				DecodeJSON(t, resp, &reviews)
+				for _, review := range reviews {
+					req := NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/pulls/%d/reviews/%d", repo.FullName(), pullIssue.Index, review.ID).
+						AddTokenAuth(token)
+					MakeRequest(t, req, http.StatusNoContent)
+				}
+			}
+
+			requireReviewCount := func(count int) {
+				req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/pulls/%d/reviews", repo.FullName(), pullIssue.Index).AddTokenAuth(token)
+				resp := MakeRequest(t, req, http.StatusOK)
+				var reviews []*api.PullReview
+				DecodeJSON(t, resp, &reviews)
+				require.EqualValues(t, count, len(reviews))
+			}
+
 			{
 				req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/pulls/%d/reviews", repo.FullName(), pullIssue.Index), &api.CreatePullReviewOptions{
 					Body:  "body1",
@@ -66,6 +90,7 @@ func TestAPIPullReviewCreateComment(t *testing.T) {
 				DecodeJSON(t, resp, &getReview)
 				require.EqualValues(t, getReview, review)
 			}
+			requireReviewCount(1)
 
 			newCommentBody := "first new line"
 			var reviewComment api.PullReviewComment
@@ -95,11 +120,24 @@ func TestAPIPullReviewCreateComment(t *testing.T) {
 				assert.EqualValues(t, reviewComment, comment)
 			}
 
+			{
+				req := NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/pulls/%d/reviews/%d/comments/%d", repo.FullName(), pullIssue.Index, review.ID, reviewComment.ID).
+					AddTokenAuth(token)
+				MakeRequest(t, req, http.StatusNoContent)
+			}
+
+			{
+				req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/pulls/%d/reviews/%d/comments/%d", repo.FullName(), pullIssue.Index, review.ID, reviewComment.ID).
+					AddTokenAuth(token)
+				MakeRequest(t, req, http.StatusNotFound)
+			}
+
 			{
 				req := NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/pulls/%d/reviews/%d", repo.FullName(), pullIssue.Index, review.ID).
 					AddTokenAuth(token)
 				MakeRequest(t, req, http.StatusNoContent)
 			}
+			requireReviewCount(0)
 		})
 	}
 }