gitcrawl/internal/cli/coverage_extra_test.go
2026-05-08 06:20:35 +01:00

269 lines
11 KiB
Go

package cli
import (
"bytes"
"context"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/openclaw/gitcrawl/internal/config"
"github.com/openclaw/gitcrawl/internal/store"
)
func TestCLIAppCommandCoveragePaths(t *testing.T) {
ctx := context.Background()
configPath := seedGHShimRepo(t, ctx)
cfg, err := config.Load(configPath)
if err != nil {
t.Fatalf("load config: %v", err)
}
st, err := store.Open(ctx, cfg.DBPath)
if err != nil {
t.Fatalf("open store: %v", err)
}
repo, err := st.RepositoryByFullName(ctx, "openclaw/openclaw")
if err != nil {
t.Fatalf("repo: %v", err)
}
threads, err := st.ListThreadsFiltered(ctx, store.ThreadListOptions{RepoID: repo.ID, IncludeClosed: true, Numbers: []int{10, 12}})
if err != nil {
t.Fatalf("threads: %v", err)
}
if len(threads) != 2 {
t.Fatalf("seed threads = %+v", threads)
}
result, err := st.SaveDurableClusters(ctx, repo.ID, []store.DurableClusterInput{{
StableKey: "cli:10,12",
StableSlug: "cli-10-12",
RepresentativeThreadID: threads[0].ID,
Title: "CLI command cluster",
Members: []store.DurableClusterMemberInput{
{ThreadID: threads[0].ID, Role: "canonical"},
{ThreadID: threads[1].ID, Role: "member"},
},
}})
if err != nil {
t.Fatalf("save cluster: %v", err)
}
if _, err := st.RecordRun(ctx, store.RunRecord{RepoID: repo.ID, Kind: "sync", Scope: "open", Status: "success", StartedAt: "2026-05-08T01:00:00Z", FinishedAt: "2026-05-08T01:00:01Z", StatsJSON: "{}"}); err != nil {
t.Fatalf("record run: %v", err)
}
clusterID, err := st.ClusterIDForThreadNumber(ctx, repo.ID, 10, true)
if err != nil {
t.Fatalf("cluster id: %v", err)
}
if result.RunID == 0 {
t.Fatal("cluster run id should be non-zero")
}
if err := st.Close(); err != nil {
t.Fatalf("close store: %v", err)
}
commands := [][]string{
{"--config", configPath, "--json", "configure", "--summary-model", "gpt-test", "--embed-model", "embed-test", "--embedding-basis", "title_original"},
{"--config", configPath, "--json", "metadata"},
{"--config", configPath, "--json", "status"},
{"--config", configPath, "--json", "threads", "openclaw/openclaw", "--numbers", "https://github.com/openclaw/openclaw/issues/10,https://github.com/openclaw/openclaw/pull/12", "--include-closed", "--limit", "2"},
{"--config", configPath, "--json", "runs", "openclaw/openclaw", "--kind", "sync", "--limit", "1"},
{"--config", configPath, "--json", "clusters", "openclaw/openclaw", "--include-closed", "--sort", "oldest", "--min-size", "1", "--limit", "5"},
{"--config", configPath, "--json", "durable-clusters", "openclaw/openclaw", "--include-closed", "--sort", "size", "--min-size", "1", "--limit", "5"},
{"--config", configPath, "--json", "cluster-detail", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10), "--member-limit", "2", "--body-chars", "10", "--include-closed"},
{"--config", configPath, "--json", "close-thread", "openclaw/openclaw", "--number", "https://github.com/openclaw/openclaw/issues/10", "--reason", "covered"},
{"--config", configPath, "--json", "reopen-thread", "openclaw/openclaw", "--number", "10"},
{"--config", configPath, "--json", "close-cluster", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10), "--reason", "covered"},
{"--config", configPath, "--json", "reopen-cluster", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10)},
{"--config", configPath, "--json", "exclude-cluster-member", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10), "--number", "12", "--reason", "covered"},
{"--config", configPath, "--json", "include-cluster-member", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10), "--number", "12", "--reason", "covered"},
{"--config", configPath, "--json", "set-cluster-canonical", "openclaw/openclaw", "--id", strconv.FormatInt(clusterID, 10), "--number", "12", "--reason", "covered"},
}
for _, args := range commands {
app := New()
var stdout, stderr bytes.Buffer
app.Stdout = &stdout
app.Stderr = &stderr
if err := app.Run(ctx, args); err != nil {
t.Fatalf("%v failed: %v\nstdout=%s\nstderr=%s", args, err, stdout.String(), stderr.String())
}
if stdout.Len() == 0 {
t.Fatalf("%v produced no output", args)
}
}
if clusterID <= 0 {
t.Fatalf("cluster id = %d", clusterID)
}
}
func TestCLIAppHumanAndLogOutputE2E(t *testing.T) {
ctx := context.Background()
configPath := seedGHShimRepo(t, ctx)
textCommands := [][]string{
{"--config", configPath, "version"},
{"--config", configPath, "metadata"},
{"--config", configPath, "status"},
{"--config", configPath, "doctor"},
{"--config", configPath, "help", "portable"},
{"--config", configPath, "help", "tui"},
}
for _, args := range textCommands {
app := New()
var stdout bytes.Buffer
app.Stdout = &stdout
if err := app.Run(ctx, args); err != nil {
t.Fatalf("%v failed: %v", args, err)
}
if strings.TrimSpace(stdout.String()) == "" {
t.Fatalf("%v produced no text output", args)
}
}
logCommands := [][]string{
{"--config", configPath, "--format", "log", "configure", "--summary-model", "gpt-log"},
{"--config", configPath, "--format", "log", "doctor"},
}
for _, args := range logCommands {
app := New()
var stdout bytes.Buffer
app.Stdout = &stdout
if err := app.Run(ctx, args); err != nil {
t.Fatalf("%v failed: %v", args, err)
}
if !strings.Contains(stdout.String(), "=") {
t.Fatalf("%v log output = %q", args, stdout.String())
}
}
jsonVersion := New()
var jsonOut bytes.Buffer
jsonVersion.Stdout = &jsonOut
if err := jsonVersion.Run(ctx, []string{"--config", configPath, "--format", "json", "version"}); err != nil {
t.Fatalf("json version: %v", err)
}
if !strings.Contains(jsonOut.String(), `"version"`) {
t.Fatalf("json version output = %q", jsonOut.String())
}
}
func TestCLIAppVectorFallbackCoveragePaths(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
configPath := filepath.Join(dir, "config.toml")
dbPath := filepath.Join(dir, "gitcrawl.db")
app := New()
if err := app.Run(ctx, []string{"--config", configPath, "init", "--db", dbPath}); err != nil {
t.Fatalf("init: %v", err)
}
repoID, firstID, secondID := seedCommandFlowStore(t, dbPath)
st, err := store.Open(ctx, dbPath)
if err != nil {
t.Fatalf("open store: %v", err)
}
now := time.Now().UTC().Format(time.RFC3339Nano)
for _, vector := range []store.ThreadVector{
{ThreadID: firstID, Basis: "other_basis", Model: "other-model", Dimensions: 2, ContentHash: "v1", Vector: []float64{1, 0}, CreatedAt: now, UpdatedAt: now},
{ThreadID: secondID, Basis: "other_basis", Model: "other-model", Dimensions: 2, ContentHash: "v2", Vector: []float64{0.95, 0.05}, CreatedAt: now, UpdatedAt: now},
} {
if err := st.UpsertThreadVector(ctx, vector); err != nil {
t.Fatalf("upsert vector: %v", err)
}
}
if err := st.Close(); err != nil {
t.Fatalf("close store: %v", err)
}
configure := New()
if err := configure.Run(ctx, []string{"--config", configPath, "configure", "--embed-model", "missing-model", "--embedding-basis", "missing-basis"}); err != nil {
t.Fatalf("configure: %v", err)
}
for _, args := range [][]string{
{"--config", configPath, "--json", "neighbors", "openclaw/openclaw", "--number", "101", "--limit", "1", "--threshold", "0.99"},
{"--config", configPath, "--json", "cluster", "openclaw/openclaw", "--threshold", "0.5", "--min-size", "2", "--limit", "2"},
{"--config", configPath, "--json", "refresh", "openclaw/openclaw", "--no-sync", "--no-embed", "--threshold", "0.5", "--min-size", "2"},
{"--config", configPath, "--json", "search", "openclaw/openclaw", "--query", "gateway", "--mode", ""},
} {
run := New()
var stdout bytes.Buffer
run.Stdout = &stdout
if err := run.Run(ctx, args); err != nil {
t.Fatalf("%v failed: %v\n%s", args, err, stdout.String())
}
}
if repoID == 0 {
t.Fatal("seed repo id should be non-zero")
}
}
func TestCLIAppUsageBranches(t *testing.T) {
ctx := context.Background()
configPath := filepath.Join(t.TempDir(), "config.toml")
cases := [][]string{
{"--format", "yaml", "status"},
{"serve"},
{"unknown"},
{"configure", "--bad"},
{"metadata", "extra"},
{"status", "extra"},
{"portable"},
{"portable", "unknown"},
{"portable", "prune", "extra"},
{"portable", "prune", "--body-chars", "bad"},
{"threads"},
{"threads", "bad-repo"},
{"threads", "openclaw/openclaw", "--numbers", "bad"},
{"threads", "openclaw/openclaw", "--limit", "bad"},
{"runs"},
{"runs", "openclaw/openclaw", "--limit", "bad"},
{"cluster-detail", "openclaw/openclaw", "--id", "bad"},
{"close-thread", "openclaw/openclaw"},
{"reopen-thread", "openclaw/openclaw", "--number", "bad"},
{"close-cluster", "openclaw/openclaw"},
{"reopen-cluster", "openclaw/openclaw", "--id", "bad"},
{"exclude-cluster-member", "openclaw/openclaw", "--id", "1"},
{"include-cluster-member", "openclaw/openclaw", "--id", "bad", "--number", "1"},
{"set-cluster-canonical", "openclaw/openclaw", "--id", "1", "--number", "bad"},
{"sync", "openclaw/openclaw", "--with", "bad"},
{"refresh"},
{"refresh", "openclaw/openclaw", "--no-sync", "--no-embed", "--no-cluster"},
{"refresh", "bad-repo"},
{"refresh", "openclaw/openclaw", "--limit", "bad"},
{"refresh", "openclaw/openclaw", "--threshold", "bad"},
{"refresh", "openclaw/openclaw", "--threshold", "2"},
{"refresh", "openclaw/openclaw", "--min-size", "bad"},
{"refresh", "openclaw/openclaw", "--k", "bad"},
{"search"},
{"search", "openclaw/openclaw"},
{"search", "bad-repo", "--query", "x"},
{"search", "openclaw/openclaw", "--query", "x", "--limit", "bad"},
{"search", "openclaw/openclaw", "--query", "x", "--mode", "bad"},
{"neighbors"},
{"neighbors", "bad-repo"},
{"neighbors", "openclaw/openclaw"},
{"neighbors", "openclaw/openclaw", "--number", "bad"},
{"neighbors", "openclaw/openclaw", "--number", "1", "--limit", "bad"},
{"neighbors", "openclaw/openclaw", "--number", "1", "--threshold", "bad"},
{"cluster"},
{"cluster", "bad-repo"},
{"cluster", "openclaw/openclaw", "--threshold", "bad"},
{"cluster", "openclaw/openclaw", "--threshold", "2"},
{"cluster", "openclaw/openclaw", "--min-size", "bad"},
{"cluster", "openclaw/openclaw", "--max-cluster-size", "bad"},
{"cluster", "openclaw/openclaw", "--limit", "bad"},
{"embed"},
{"embed", "bad-repo"},
{"embed", "openclaw/openclaw", "--number", "bad"},
{"embed", "openclaw/openclaw", "--limit", "bad"},
{"tui", "one", "two"},
{"tui", "--sort", "bad"},
}
for _, args := range cases {
app := New()
app.Stdout = &bytes.Buffer{}
app.Stderr = &bytes.Buffer{}
full := append([]string{"--config", configPath}, args...)
if err := app.Run(ctx, full); err == nil {
t.Fatalf("%v succeeded, want error", args)
}
}
}