Files
review-bot/review/formatter_test.go
T
Rodin 27a9be38bc fix: address PR #63 review findings
1. Refactor err2 to use scoped loadErr variable (MINOR - sonnet-review-bot)
   The else-if branches are mutually exclusive, so the error variable
   should be scoped inside the block, not declared outside with err2.

2. Sanitize DisplayName before embedding in Markdown (MINOR - security-review-bot)
   Remote persona metadata is untrusted. Added sanitizeMarkdownText() to
   escape Markdown special characters and strip control characters.
   Applied to both the header title and the footer attribution.

3. Document YAML DoS mitigations (MINOR - security-review-bot)
   Added comprehensive comment in remote_persona.go explaining existing
   defenses: file size limit, file count cap, depth limit, node count cap,
   and alias cycle detection. These collectively mitigate billion-laughs
   and stack exhaustion attacks.
2026-05-10 20:54:20 -07:00

285 lines
7.5 KiB
Go

package review
import (
"strings"
"testing"
)
func TestFormatMarkdown_EmptyFindings(t *testing.T) {
result := &ReviewResult{
Verdict: "APPROVE",
Summary: "All good, no issues.",
Findings: []Finding{},
Recommendation: "Merge this PR.",
}
got := FormatMarkdown(result, "Sonnet")
if !strings.Contains(got, "## Summary") {
t.Error("expected Summary header")
}
if !strings.Contains(got, "All good, no issues.") {
t.Error("expected summary text")
}
if strings.Contains(got, "## Findings") {
t.Error("should not contain Findings header when empty")
}
if !strings.Contains(got, "**APPROVE**") {
t.Error("expected verdict in recommendation")
}
if !strings.Contains(got, "Review by Sonnet") {
t.Error("expected reviewer name")
}
}
func TestFormatMarkdown_MultipleFindings(t *testing.T) {
result := &ReviewResult{
Verdict: "REQUEST_CHANGES",
Summary: "Several issues found.",
Findings: []Finding{
{Severity: "MAJOR", File: "main.go", Line: 42, Finding: "Nil pointer dereference"},
{Severity: "MINOR", File: "util.go", Line: 7, Finding: "Unused variable"},
{Severity: "NIT", File: "doc.go", Line: 1, Finding: "Typo in comment"},
},
Recommendation: "Fix the nil pointer issue before merging.",
}
got := FormatMarkdown(result, "GPT")
if !strings.Contains(got, "## Findings") {
t.Error("expected Findings header")
}
if !strings.Contains(got, "| 1 | [MAJOR] | `main.go` | 42 | Nil pointer dereference |") {
t.Error("expected first finding row")
}
if !strings.Contains(got, "| 2 | [MINOR] | `util.go` | 7 | Unused variable |") {
t.Error("expected second finding row")
}
if !strings.Contains(got, "| 3 | [NIT] | `doc.go` | 1 | Typo in comment |") {
t.Error("expected third finding row")
}
if !strings.Contains(got, "**REQUEST_CHANGES**") {
t.Error("expected verdict in recommendation")
}
}
func TestFormatMarkdown_NoReviewerName(t *testing.T) {
result := &ReviewResult{
Verdict: "APPROVE",
Summary: "Fine.",
Findings: []Finding{},
Recommendation: "Go ahead.",
}
got := FormatMarkdown(result, "")
if strings.Contains(got, "Review by") {
t.Error("should not contain reviewer line when name is empty")
}
}
func TestFormatMarkdown_SpecialChars(t *testing.T) {
result := &ReviewResult{
Verdict: "REQUEST_CHANGES",
Summary: "Issues with `fmt.Sprintf` usage.",
Findings: []Finding{
{Severity: "MAJOR", File: "render.go", Line: 15, Finding: "Use `%v` instead of `%s` for interface{}"},
},
Recommendation: "Fix the format verb.",
}
got := FormatMarkdown(result, "Test")
// Should contain the backtick content without breaking the table
if !strings.Contains(got, "`render.go`") {
t.Error("expected file in backticks")
}
if !strings.Contains(got, "Use `%v` instead of `%s` for interface{}") {
t.Error("expected finding text with backticks preserved")
}
}
func TestGiteaEvent(t *testing.T) {
tests := []struct {
verdict string
expected string
}{
{"APPROVE", "APPROVED"},
{"REQUEST_CHANGES", "REQUEST_CHANGES"},
{"UNKNOWN", "COMMENT"},
{"", "COMMENT"},
}
for _, tc := range tests {
got := GiteaEvent(tc.verdict)
if got != tc.expected {
t.Errorf("GiteaEvent(%q) = %q, want %q", tc.verdict, got, tc.expected)
}
}
}
func TestFormatMarkdown_Sentinel(t *testing.T) {
result := &ReviewResult{
Verdict: "APPROVE",
Summary: "All good.",
Recommendation: "Merge it.",
}
output := FormatMarkdown(result, "security")
if !strings.Contains(output, "<!-- review-bot:security -->") {
t.Error("expected sentinel comment in output")
}
// Empty reviewer name should NOT have sentinel
output2 := FormatMarkdown(result, "")
if strings.Contains(output2, "<!-- review-bot") {
t.Error("should not contain sentinel when reviewer name is empty")
}
}
func TestFormatMarkdown_RoleTitle(t *testing.T) {
result := &ReviewResult{
Verdict: "APPROVE",
Summary: "All good.",
Recommendation: "Merge it.",
}
// With reviewer name: should have title header
output := FormatMarkdown(result, "security")
if !strings.Contains(output, "# Security Review\n") {
t.Error("expected '# Security Review' header when reviewer name is set")
}
output2 := FormatMarkdown(result, "gpt")
if !strings.Contains(output2, "# Gpt Review\n") {
t.Error("expected '# Gpt Review' header")
}
// Without reviewer name: no title header
output3 := FormatMarkdown(result, "")
if strings.Contains(output3, "# ") && strings.Contains(output3, " Review\n") {
t.Error("should not contain role title header when reviewer name is empty")
}
}
func TestFormatMarkdownWithDisplay(t *testing.T) {
result := &ReviewResult{
Verdict: "APPROVE",
Summary: "Test summary",
Findings: nil,
Recommendation: "Test recommendation",
}
t.Run("with display name", func(t *testing.T) {
body := FormatMarkdownWithDisplay(result, "Security Specialist", "security")
// Header should use display name
if !strings.Contains(body, "# Security Specialist Review") {
t.Error("header should use display name")
}
// Sentinel should use sentinel name
if !strings.Contains(body, "<!-- review-bot:security -->") {
t.Error("sentinel should use sentinel name")
}
// Footer "Review by" should use display name
if !strings.Contains(body, "*Review by Security Specialist*") {
t.Error("footer should use display name")
}
})
t.Run("without display name", func(t *testing.T) {
body := FormatMarkdownWithDisplay(result, "", "reviewer")
// Should fall back to sentinel name for header
if !strings.Contains(body, "# Reviewer Review") {
t.Error("header should fall back to sentinel name")
}
if !strings.Contains(body, "<!-- review-bot:reviewer -->") {
t.Error("sentinel should use sentinel name")
}
})
t.Run("empty both names", func(t *testing.T) {
body := FormatMarkdownWithDisplay(result, "", "")
// Should not have header
if strings.Contains(body, "# ") && strings.Contains(body, " Review") {
t.Error("should not have header when both names empty")
}
// Should not have sentinel
if strings.Contains(body, "<!-- review-bot:") {
t.Error("should not have sentinel when sentinel name empty")
}
})
}
func TestSanitizeMarkdownText(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "plain text unchanged",
input: "Security Specialist",
want: "Security Specialist",
},
{
name: "escapes asterisks",
input: "**bold** attack",
want: `\*\*bold\*\* attack`,
},
{
name: "escapes brackets for links",
input: "[click me](http://evil.com)",
want: `\[click me\]\(http://evil.com\)`,
},
{
name: "escapes backticks",
input: "`code` injection",
want: "\\`code\\` injection",
},
{
name: "escapes angle brackets",
input: "<script>alert(1)</script>",
want: `\<script\>alert\(1\)\</script\>`,
},
{
name: "escapes hash for headers",
input: "# Fake Header",
want: `\# Fake Header`,
},
{
name: "escapes pipe for tables",
input: "col1 | col2",
want: `col1 \| col2`,
},
{
name: "removes control characters",
input: "hello\x00world\x1f",
want: "helloworld",
},
{
name: "preserves tabs and newlines",
input: "line1\n\tindented",
want: "line1\n\tindented",
},
{
name: "escapes tilde for strikethrough",
input: "~~strikethrough~~",
want: `\~\~strikethrough\~\~`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := sanitizeMarkdownText(tt.input)
if got != tt.want {
t.Errorf("sanitizeMarkdownText(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}