b1f5dd4b5f
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 39s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m21s
When hasSharedToken() detects two roles sharing the same Gitea user, the bot now skips ALL update logic (PATCH, supersede) and always POSTs a fresh review. This prevents clobbering a sibling's review body or state when misconfigured. Tests now assert return values (true/false) rather than just verifying no panic. Added additional test case for three-roles-same-user scenario. Addresses review feedback: update logic and review state must not interact with sibling reviews under the same user.
271 lines
7.2 KiB
Go
271 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gitea.weiker.me/rodin/review-bot/gitea"
|
|
)
|
|
|
|
func TestValidateReviewerName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}{
|
|
{"valid simple", "sonnet", false},
|
|
{"valid with dash", "code-review", false},
|
|
{"valid with underscore", "my_bot", false},
|
|
{"valid alphanumeric", "bot123", false},
|
|
{"valid uppercase", "MyBot", false},
|
|
{"empty is valid", "", false},
|
|
{"invalid html close", "foo-->", true},
|
|
{"invalid space", "my bot", true},
|
|
{"invalid dot", "my.bot", true},
|
|
{"invalid slash", "my/bot", true},
|
|
{"invalid angle", "bot<script>", true},
|
|
{"invalid colon", "bot:name", true},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := validateReviewerName(tc.input)
|
|
if tc.wantErr && err == nil {
|
|
t.Errorf("expected error for %q, got nil", tc.input)
|
|
}
|
|
if !tc.wantErr && err != nil {
|
|
t.Errorf("expected no error for %q, got %v", tc.input, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeReview(id int64, login, state string, stale bool, body string) gitea.Review {
|
|
r := gitea.Review{
|
|
ID: id,
|
|
Body: body,
|
|
State: state,
|
|
Stale: stale,
|
|
}
|
|
r.User.Login = login
|
|
return r
|
|
}
|
|
|
|
func TestReviewUnchanged(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
existing []gitea.Review
|
|
newBody string
|
|
newEvent string
|
|
sentinel string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "no existing review",
|
|
existing: nil,
|
|
newBody: "new review",
|
|
newEvent: "APPROVED",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "identical body and state",
|
|
existing: []gitea.Review{
|
|
makeReview(100, "bot", "APPROVED", false, "same body\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
newBody: "same body\n<!-- review-bot:sonnet -->",
|
|
newEvent: "APPROVED",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "same body but different state",
|
|
existing: []gitea.Review{
|
|
makeReview(100, "bot", "APPROVED", false, "body\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
newBody: "body\n<!-- review-bot:sonnet -->",
|
|
newEvent: "REQUEST_CHANGES",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "different body same state",
|
|
existing: []gitea.Review{
|
|
makeReview(100, "bot", "APPROVED", false, "old body\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
newBody: "new body\n<!-- review-bot:sonnet -->",
|
|
newEvent: "APPROVED",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "stale review with same body (should still post)",
|
|
existing: []gitea.Review{
|
|
makeReview(100, "bot", "APPROVED", true, "same\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
newBody: "same\n<!-- review-bot:sonnet -->",
|
|
newEvent: "APPROVED",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "different sentinel (not our review)",
|
|
existing: []gitea.Review{
|
|
makeReview(100, "bot", "APPROVED", false, "body\n<!-- review-bot:gpt -->"),
|
|
},
|
|
newBody: "body\n<!-- review-bot:sonnet -->",
|
|
newEvent: "APPROVED",
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := reviewUnchanged(tc.existing, tc.newBody, tc.newEvent, tc.sentinel)
|
|
if got != tc.want {
|
|
t.Errorf("reviewUnchanged() = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindOwnReview(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
reviews []gitea.Review
|
|
sentinel string
|
|
wantID int64
|
|
wantNil bool
|
|
}{
|
|
{
|
|
name: "no reviews",
|
|
reviews: nil,
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
wantNil: true,
|
|
},
|
|
{
|
|
name: "found by sentinel",
|
|
reviews: []gitea.Review{
|
|
makeReview(42, "bot", "APPROVED", false, "review body\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
wantID: 42,
|
|
},
|
|
{
|
|
name: "wrong sentinel",
|
|
reviews: []gitea.Review{
|
|
makeReview(42, "bot", "APPROVED", false, "body\n<!-- review-bot:gpt -->"),
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
wantNil: true,
|
|
},
|
|
{
|
|
name: "multiple reviews, returns first match",
|
|
reviews: []gitea.Review{
|
|
makeReview(10, "bot", "APPROVED", false, "old\n<!-- review-bot:gpt -->"),
|
|
makeReview(20, "bot", "APPROVED", false, "new\n<!-- review-bot:sonnet -->"),
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
wantID: 20,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := findOwnReview(tc.reviews, tc.sentinel)
|
|
if tc.wantNil {
|
|
if got != nil {
|
|
t.Errorf("findOwnReview() = %v, want nil", got)
|
|
}
|
|
} else {
|
|
if got == nil {
|
|
t.Fatal("findOwnReview() = nil, want non-nil")
|
|
}
|
|
if got.ID != tc.wantID {
|
|
t.Errorf("findOwnReview().ID = %d, want %d", got.ID, tc.wantID)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasSharedToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
reviews []gitea.Review
|
|
sentinel string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "no reviews",
|
|
reviews: nil,
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no own review yet - cannot detect",
|
|
reviews: []gitea.Review{
|
|
{ID: 1, User: struct{ Login string `json:"login"` }{Login: "other"}, Body: "<!-- review-bot:gpt --> body"},
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "separate users - no shared token",
|
|
reviews: []gitea.Review{
|
|
{ID: 1, User: struct{ Login string `json:"login"` }{Login: "sonnet-review-bot"}, Body: "<!-- review-bot:sonnet --> body"},
|
|
{ID: 2, User: struct{ Login string `json:"login"` }{Login: "security-review-bot"}, Body: "<!-- review-bot:security --> body"},
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "shared token detected - same user different sentinels",
|
|
reviews: []gitea.Review{
|
|
{ID: 1, User: struct{ Login string `json:"login"` }{Login: "sonnet-review-bot"}, Body: "<!-- review-bot:sonnet --> body"},
|
|
{ID: 2, User: struct{ Login string `json:"login"` }{Login: "sonnet-review-bot"}, Body: "<!-- review-bot:security --> body"},
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "three roles same user",
|
|
reviews: []gitea.Review{
|
|
{ID: 1, User: struct{ Login string `json:"login"` }{Login: "bot"}, Body: "<!-- review-bot:sonnet --> body"},
|
|
{ID: 2, User: struct{ Login string `json:"login"` }{Login: "bot"}, Body: "<!-- review-bot:security --> body"},
|
|
{ID: 3, User: struct{ Login string `json:"login"` }{Login: "bot"}, Body: "<!-- review-bot:gpt --> body"},
|
|
},
|
|
sentinel: "<!-- review-bot:sonnet -->",
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := hasSharedToken(tc.reviews, tc.sentinel)
|
|
if got != tc.want {
|
|
t.Errorf("hasSharedToken() = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractSentinelName(t *testing.T) {
|
|
tests := []struct {
|
|
body string
|
|
want string
|
|
}{
|
|
{"<!-- review-bot:sonnet --> rest", "sonnet"},
|
|
{"<!-- review-bot:security --> rest", "security"},
|
|
{"no sentinel here", "unknown"},
|
|
{"<!-- review-bot:gpt-review --> rest", "gpt-review"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := extractSentinelName(tc.body)
|
|
if got != tc.want {
|
|
t.Errorf("extractSentinelName(%q) = %q, want %q", tc.body, got, tc.want)
|
|
}
|
|
}
|
|
}
|