mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-07-09 17:28:58 +00:00
Add comprehensive unit tests covering config dispatch, emit run result, git helpers, provider commands, provider TUI pure functions and View rendering, shared utilities, flags parsing, scan command flags, and small file entry points (version, viewer, llm, rules commands).
249 lines
7 KiB
Go
249 lines
7 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/open-code-review/open-code-review/internal/llm"
|
|
)
|
|
|
|
func TestExcludeToolDef(t *testing.T) {
|
|
defs := []llm.ToolDef{
|
|
{Type: "function", Function: llm.FunctionDef{Name: "task_done"}},
|
|
{Type: "function", Function: llm.FunctionDef{Name: "file_read"}},
|
|
{Type: "function", Function: llm.FunctionDef{Name: "file_read_diff"}},
|
|
{Type: "function", Function: llm.FunctionDef{Name: "code_comment"}},
|
|
}
|
|
got := excludeToolDef(defs, "file_read_diff")
|
|
if len(got) != 3 {
|
|
t.Fatalf("expected 3 defs, got %d", len(got))
|
|
}
|
|
for _, d := range got {
|
|
if d.Function.Name == "file_read_diff" {
|
|
t.Errorf("file_read_diff should have been removed")
|
|
}
|
|
}
|
|
// Input slice must not be mutated.
|
|
if len(defs) != 4 {
|
|
t.Errorf("input slice was mutated: len=%d, want 4", len(defs))
|
|
}
|
|
}
|
|
|
|
func TestExcludeToolDef_AbsentName(t *testing.T) {
|
|
defs := []llm.ToolDef{
|
|
{Type: "function", Function: llm.FunctionDef{Name: "task_done"}},
|
|
}
|
|
got := excludeToolDef(defs, "does_not_exist")
|
|
if !reflect.DeepEqual(got, defs) {
|
|
t.Errorf("removing absent name should return identical content")
|
|
}
|
|
}
|
|
|
|
func TestSplitPaths(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want []string
|
|
}{
|
|
{"empty", "", nil},
|
|
{"single", "internal/agent", []string{"internal/agent"}},
|
|
{"multiple", "a.go,b.go,c.go", []string{"a.go", "b.go", "c.go"}},
|
|
{"trims whitespace", " a.go , b.go ", []string{"a.go", "b.go"}},
|
|
{"drops empty segments", "a.go,,b.go,", []string{"a.go", "b.go"}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := splitPaths(tt.in)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("splitPaths(%q) = %v, want %v", tt.in, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_BareCommandScansWholeRepo(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{}) // no flags
|
|
if err != nil {
|
|
t.Fatalf("bare `ocr scan` should not error, got: %v", err)
|
|
}
|
|
if opts.paths != "" {
|
|
t.Errorf("default paths should be empty (= whole repo), got %q", opts.paths)
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_RejectsInvalidAudience(t *testing.T) {
|
|
_, err := parseScanFlags([]string{"--audience", "robot"})
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid --audience")
|
|
}
|
|
if !strings.Contains(err.Error(), "invalid --audience") {
|
|
t.Errorf("error message = %q; want invalid --audience", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_RejectsNegativeMaxTools(t *testing.T) {
|
|
_, err := parseScanFlags([]string{"--max-tools", "-1"})
|
|
if err == nil {
|
|
t.Fatal("expected error for negative --max-tools")
|
|
}
|
|
if !strings.Contains(err.Error(), "--max-tools") {
|
|
t.Errorf("error message = %q; want it to mention --max-tools", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_RejectsNegativeMaxGitProcs(t *testing.T) {
|
|
_, err := parseScanFlags([]string{"--max-git-procs", "-3"})
|
|
if err == nil {
|
|
t.Fatal("expected error for negative --max-git-procs")
|
|
}
|
|
if !strings.Contains(err.Error(), "--max-git-procs") {
|
|
t.Errorf("error message = %q; want it to mention --max-git-procs", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_DefaultsValid(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{}) // bare command
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.paths != "" {
|
|
t.Errorf("opts = %+v; want paths empty (whole repo)", opts)
|
|
}
|
|
if opts.audience != "human" {
|
|
t.Errorf("default audience = %q, want \"human\"", opts.audience)
|
|
}
|
|
if opts.outputFormat != "text" {
|
|
t.Errorf("default outputFormat = %q, want \"text\"", opts.outputFormat)
|
|
}
|
|
if opts.concurrency != 8 {
|
|
t.Errorf("default concurrency = %d, want 8", opts.concurrency)
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_PathNarrowsScope(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{"--path", "internal/agent,internal/diff"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got := splitPaths(opts.paths); !reflect.DeepEqual(got, []string{"internal/agent", "internal/diff"}) {
|
|
t.Errorf("splitPaths(opts.paths) = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_HelpFlag(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{"-h"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !opts.showHelp {
|
|
t.Error("opts.showHelp should be true when -h is supplied")
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_RejectsNegativeMaxTokensBudget(t *testing.T) {
|
|
_, err := parseScanFlags([]string{"--max-tokens-budget", "-100"})
|
|
if err == nil {
|
|
t.Fatal("expected error for negative --max-tokens-budget")
|
|
}
|
|
if !strings.Contains(err.Error(), "--max-tokens-budget") {
|
|
t.Errorf("error message = %q; want it to mention --max-tokens-budget", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_BooleanFlags(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{"--no-plan", "--no-dedup", "--no-summary", "--preview"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !opts.noPlan {
|
|
t.Error("noPlan should be true")
|
|
}
|
|
if !opts.noDedup {
|
|
t.Error("noDedup should be true")
|
|
}
|
|
if !opts.noSummary {
|
|
t.Error("noSummary should be true")
|
|
}
|
|
if !opts.preview {
|
|
t.Error("preview should be true")
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_ModelOverride(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{"--model", "claude-opus-4-6"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.model != "claude-opus-4-6" {
|
|
t.Errorf("model = %q, want claude-opus-4-6", opts.model)
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_AllStringFlags(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{
|
|
"--tools", "/tmp/tools.json",
|
|
"--rule", "/tmp/rule.json",
|
|
"--repo", "/tmp/repo",
|
|
"--exclude", "*.md,*.txt",
|
|
"--batch", "by-language",
|
|
"--background", "test context",
|
|
"--audience", "agent",
|
|
"-f", "json",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.toolConfigPath != "/tmp/tools.json" {
|
|
t.Errorf("toolConfigPath = %q", opts.toolConfigPath)
|
|
}
|
|
if opts.rulePath != "/tmp/rule.json" {
|
|
t.Errorf("rulePath = %q", opts.rulePath)
|
|
}
|
|
if opts.repoDir != "/tmp/repo" {
|
|
t.Errorf("repoDir = %q", opts.repoDir)
|
|
}
|
|
if opts.excludes != "*.md,*.txt" {
|
|
t.Errorf("excludes = %q", opts.excludes)
|
|
}
|
|
if opts.batch != "by-language" {
|
|
t.Errorf("batch = %q", opts.batch)
|
|
}
|
|
if opts.background != "test context" {
|
|
t.Errorf("background = %q", opts.background)
|
|
}
|
|
if opts.audience != "agent" {
|
|
t.Errorf("audience = %q", opts.audience)
|
|
}
|
|
if opts.outputFormat != "json" {
|
|
t.Errorf("outputFormat = %q", opts.outputFormat)
|
|
}
|
|
}
|
|
|
|
func TestParseScanFlags_IntFlags(t *testing.T) {
|
|
opts, err := parseScanFlags([]string{
|
|
"--concurrency", "16",
|
|
"--timeout", "20",
|
|
"--max-tools", "50",
|
|
"--max-git-procs", "32",
|
|
"--max-tokens-budget", "100000",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.concurrency != 16 {
|
|
t.Errorf("concurrency = %d", opts.concurrency)
|
|
}
|
|
if opts.perFileTimeout != 20 {
|
|
t.Errorf("perFileTimeout = %d", opts.perFileTimeout)
|
|
}
|
|
if opts.maxTools != 50 {
|
|
t.Errorf("maxTools = %d", opts.maxTools)
|
|
}
|
|
if opts.maxGitProcs != 32 {
|
|
t.Errorf("maxGitProcs = %d", opts.maxGitProcs)
|
|
}
|
|
if opts.maxTokensBudget != 100000 {
|
|
t.Errorf("maxTokensBudget = %d", opts.maxTokensBudget)
|
|
}
|
|
}
|