diff --git a/cmd/review-bot/main.go b/cmd/review-bot/main.go index d5e4734..d98e3db 100644 --- a/cmd/review-bot/main.go +++ b/cmd/review-bot/main.go @@ -327,7 +327,7 @@ func main() { // In shared-token mode, skip superseding to avoid clobbering sibling reviews. sharedToken := hasSharedToken(existingReviews, sentinel) if !sharedToken { - existing := findOwnReview(existingReviews, sentinel) + existing := findOwnReviewStrict(existingReviews, sentinel, *reviewerName) if existing != nil { commentID, err := giteaClient.GetTimelineReviewCommentID(ctx, owner, repoName, prNumber, sentinel) if err != nil { @@ -522,9 +522,13 @@ func buildSupersededBody(originalBody, commitSHA, sentinel string) string { var sb strings.Builder sb.WriteString("~~Original review~~\n\n") sb.WriteString("**Superseded** \u2014 see current review for up-to-date findings.\n\n") - sb.WriteString("
Previous findings (commit ") - sb.WriteString(shortSHA) - sb.WriteString(")\n\n") + if shortSHA != "" { + sb.WriteString("
Previous findings (commit ") + sb.WriteString(shortSHA) + sb.WriteString(")\n\n") + } else { + sb.WriteString("
Previous findings\n\n") + } sb.WriteString(originalBody) sb.WriteString("\n\n
\n\n") sb.WriteString(sentinel) @@ -574,10 +578,40 @@ func extractSentinelName(body string) string { // findOwnReview locates a review matching the given sentinel in its body. func findOwnReview(reviews []gitea.Review, sentinel string) *gitea.Review { + var best *gitea.Review for i := range reviews { - if strings.Contains(reviews[i].Body, sentinel) { - return &reviews[i] + if !strings.Contains(reviews[i].Body, sentinel) { + continue + } + // Skip superseded reviews (they contain our sentinel in the collapsed body) + if strings.Contains(reviews[i].Body, "~~Original review~~") { + continue + } + // Take the highest ID (most recent) + if best == nil || reviews[i].ID > best.ID { + best = &reviews[i] } } - return nil + return best +} + +// findOwnReviewStrict is like findOwnReview but also verifies the review +// was posted by the expected user (defense-in-depth against sentinel injection). +func findOwnReviewStrict(reviews []gitea.Review, sentinel, expectedLogin string) *gitea.Review { + var best *gitea.Review + for i := range reviews { + if !strings.Contains(reviews[i].Body, sentinel) { + continue + } + if strings.Contains(reviews[i].Body, "~~Original review~~") { + continue + } + if expectedLogin != "" && reviews[i].User.Login != expectedLogin { + continue + } + if best == nil || reviews[i].ID > best.ID { + best = &reviews[i] + } + } + return best } diff --git a/cmd/review-bot/main_test.go b/cmd/review-bot/main_test.go index 9944185..d812ea2 100644 --- a/cmd/review-bot/main_test.go +++ b/cmd/review-bot/main_test.go @@ -140,6 +140,32 @@ func TestFindOwnReview(t *testing.T) { sentinel: "", wantID: 20, }, + { + name: "skips superseded review", + reviews: []gitea.Review{ + makeReview(10, "bot", "APPROVED", false, "~~Original review~~\n\n**Superseded**\n"), + makeReview(20, "bot", "APPROVED", false, "fresh review\n"), + }, + sentinel: "", + wantID: 20, + }, + { + name: "only superseded reviews exist", + reviews: []gitea.Review{ + makeReview(10, "bot", "APPROVED", false, "~~Original review~~\n\n"), + }, + sentinel: "", + wantNil: true, + }, + { + name: "picks highest ID among matches", + reviews: []gitea.Review{ + makeReview(50, "bot", "APPROVED", false, "v1\n"), + makeReview(30, "bot", "APPROVED", false, "v0\n"), + }, + sentinel: "", + wantID: 50, + }, } for _, tc := range tests {