[object Object]

← back to Cli Printing Press

test: add coverage for research, dogfood, comparative, textfilter, readme_augment

02d022fe2f31e37ad6b9963db9e903a9f9e78e7d · 2026-03-25 00:00:42 -0700 · Matt Van Horn

30 new test cases across 5 files:
- research_test.go: novelty scoring, deduplication, recommendations, round-trip
- dogfood_test.go: tier1 commands, credential detection, score computation
- comparative_test.go: alternative scoring, gap analysis, no-research fallback
- textfilter_test.go: AI slop detection, normal text passthrough, formatting
- readme_augment_test.go: marker replacement, no-evidence passthrough, append mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files touched

Diff

commit 02d022fe2f31e37ad6b9963db9e903a9f9e78e7d
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date:   Wed Mar 25 00:00:42 2026 -0700

    test: add coverage for research, dogfood, comparative, textfilter, readme_augment
    
    30 new test cases across 5 files:
    - research_test.go: novelty scoring, deduplication, recommendations, round-trip
    - dogfood_test.go: tier1 commands, credential detection, score computation
    - comparative_test.go: alternative scoring, gap analysis, no-research fallback
    - textfilter_test.go: AI slop detection, normal text passthrough, formatting
    - readme_augment_test.go: marker replacement, no-evidence passthrough, append mode
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 internal/generator/readme_augment_test.go |  70 ++++++++++++++++++
 internal/generator/textfilter_test.go     |  63 ++++++++++++++++
 internal/pipeline/comparative_test.go     |  57 +++++++++++++++
 internal/pipeline/dogfood_test.go         | 116 ++++++++++++++++++++++++++++++
 internal/pipeline/research_test.go        |  97 +++++++++++++++++++++++++
 5 files changed, 403 insertions(+)

diff --git a/internal/generator/readme_augment_test.go b/internal/generator/readme_augment_test.go
new file mode 100644
index 00000000..e7c89b65
--- /dev/null
+++ b/internal/generator/readme_augment_test.go
@@ -0,0 +1,70 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAugmentREADME_WithMarkers(t *testing.T) {
+	dir := t.TempDir()
+	evidenceDir := filepath.Join(dir, "evidence")
+	require.NoError(t, os.MkdirAll(evidenceDir, 0o755))
+
+	readmePath := filepath.Join(dir, "README.md")
+	readmeContent := "# My CLI\n\n<!-- HELP_OUTPUT -->\n\nSome other text.\n"
+	require.NoError(t, os.WriteFile(readmePath, []byte(readmeContent), 0o644))
+
+	helpOutput := "Usage: mycli [command]\n\nAvailable commands:\n  list    List items\n  get     Get an item\n"
+	require.NoError(t, os.WriteFile(filepath.Join(evidenceDir, "tier1-help.txt"), []byte(helpOutput), 0o644))
+
+	err := AugmentREADME(readmePath, evidenceDir)
+	require.NoError(t, err)
+
+	result, err := os.ReadFile(readmePath)
+	require.NoError(t, err)
+	assert.Contains(t, string(result), "Usage: mycli [command]")
+	assert.NotContains(t, string(result), "<!-- HELP_OUTPUT -->")
+}
+
+func TestAugmentREADME_NoEvidence(t *testing.T) {
+	dir := t.TempDir()
+	evidenceDir := filepath.Join(dir, "evidence-missing")
+	// Do not create the evidence dir
+
+	readmePath := filepath.Join(dir, "README.md")
+	original := "# My CLI\n\nNothing to see here.\n"
+	require.NoError(t, os.WriteFile(readmePath, []byte(original), 0o644))
+
+	err := AugmentREADME(readmePath, evidenceDir)
+	require.NoError(t, err)
+
+	result, err := os.ReadFile(readmePath)
+	require.NoError(t, err)
+	assert.Equal(t, original, string(result))
+}
+
+func TestAugmentREADME_AppendMode(t *testing.T) {
+	dir := t.TempDir()
+	evidenceDir := filepath.Join(dir, "evidence")
+	require.NoError(t, os.MkdirAll(evidenceDir, 0o755))
+
+	readmePath := filepath.Join(dir, "README.md")
+	original := "# My CLI\n\nA simple tool.\n"
+	require.NoError(t, os.WriteFile(readmePath, []byte(original), 0o644))
+
+	helpOutput := "Usage: mycli [command]\n"
+	require.NoError(t, os.WriteFile(filepath.Join(evidenceDir, "tier1-help.txt"), []byte(helpOutput), 0o644))
+
+	err := AugmentREADME(readmePath, evidenceDir)
+	require.NoError(t, err)
+
+	result, err := os.ReadFile(readmePath)
+	require.NoError(t, err)
+	assert.Contains(t, string(result), "## Real Usage Examples")
+	assert.Contains(t, string(result), "Usage: mycli [command]")
+	assert.Contains(t, string(result), "# My CLI")
+}
diff --git a/internal/generator/textfilter_test.go b/internal/generator/textfilter_test.go
new file mode 100644
index 00000000..c26c5095
--- /dev/null
+++ b/internal/generator/textfilter_test.go
@@ -0,0 +1,63 @@
+package generator
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCheckText_CatchesAISlop(t *testing.T) {
+	t.Run("comprehensive solution triggers warning", func(t *testing.T) {
+		warnings := CheckText("This is a comprehensive solution for your needs.")
+		assert.NotEmpty(t, warnings)
+		found := false
+		for _, w := range warnings {
+			if w.Match == "comprehensive" {
+				found = true
+			}
+		}
+		assert.True(t, found)
+	})
+
+	t.Run("robust framework triggers warning", func(t *testing.T) {
+		warnings := CheckText("A robust framework for building CLIs.")
+		assert.NotEmpty(t, warnings)
+		found := false
+		for _, w := range warnings {
+			if w.Match == "robust" {
+				found = true
+			}
+		}
+		assert.True(t, found)
+	})
+}
+
+func TestCheckText_NormalText(t *testing.T) {
+	t.Run("the API returns JSON has zero warnings", func(t *testing.T) {
+		warnings := CheckText("the API returns JSON")
+		assert.Empty(t, warnings)
+	})
+
+	t.Run("run the command has zero warnings", func(t *testing.T) {
+		warnings := CheckText("run the command")
+		assert.Empty(t, warnings)
+	})
+}
+
+func TestFormatWarnings_Empty(t *testing.T) {
+	result := FormatWarnings(nil)
+	assert.Equal(t, "", result)
+}
+
+func TestFormatWarnings_WithMatches(t *testing.T) {
+	warnings := []AITextWarning{
+		{Pattern: "p1", Match: "comprehensive", Line: 1, Context: "a comprehensive guide"},
+		{Pattern: "p2", Match: "robust", Line: 3, Context: "robust solution"},
+		{Pattern: "p3", Match: "seamless", Line: 5, Context: "seamless integration"},
+	}
+	result := FormatWarnings(warnings)
+	assert.Contains(t, result, "3 warning(s)")
+	assert.Contains(t, result, "comprehensive")
+	assert.Contains(t, result, "robust")
+	assert.Contains(t, result, "seamless")
+}
diff --git a/internal/pipeline/comparative_test.go b/internal/pipeline/comparative_test.go
new file mode 100644
index 00000000..16bd0307
--- /dev/null
+++ b/internal/pipeline/comparative_test.go
@@ -0,0 +1,57 @@
+package pipeline
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestScoreAlternative(t *testing.T) {
+	t.Run("binary install gets 20", func(t *testing.T) {
+		alt := Alternative{Name: "go-cli", InstallMethod: "binary"}
+		scored := scoreAlternative(alt, 10)
+		assert.Equal(t, 20, scored.InstallFriction)
+	})
+
+	t.Run("npm install gets 10", func(t *testing.T) {
+		alt := Alternative{Name: "node-cli", InstallMethod: "npm"}
+		scored := scoreAlternative(alt, 10)
+		assert.Equal(t, 10, scored.InstallFriction)
+	})
+
+	t.Run("unknown install gets 5", func(t *testing.T) {
+		alt := Alternative{Name: "weird-cli", InstallMethod: "something-else"}
+		scored := scoreAlternative(alt, 10)
+		assert.Equal(t, 5, scored.InstallFriction)
+	})
+}
+
+func TestCompareGapsAndAdvantages(t *testing.T) {
+	result := &ComparativeResult{
+		OurScore: 95,
+		Alternatives: []AltScore{
+			{Name: "some-tool", Breadth: 10, Total: 40},
+		},
+	}
+	gaps, advantages := compareGapsAndAdvantages(result)
+	assert.NotEmpty(t, advantages)
+
+	foundGoBinary := false
+	for _, a := range advantages {
+		if a == "Go binary - zero runtime dependencies, instant startup" {
+			foundGoBinary = true
+		}
+	}
+	assert.True(t, foundGoBinary, "should always include Go binary advantage")
+	assert.NotEmpty(t, gaps)
+}
+
+func TestRunComparative(t *testing.T) {
+	dir := t.TempDir()
+	// No research.json exists - should still produce a result without panicking
+	result, err := RunComparative(dir, 10)
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+	assert.Equal(t, 95, result.OurScore)
+	assert.Equal(t, "ship", result.Recommendation)
+}
diff --git a/internal/pipeline/dogfood_test.go b/internal/pipeline/dogfood_test.go
new file mode 100644
index 00000000..2abc66f3
--- /dev/null
+++ b/internal/pipeline/dogfood_test.go
@@ -0,0 +1,116 @@
+package pipeline
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBuildTier1Commands(t *testing.T) {
+	t.Run("zero resources returns 3 commands", func(t *testing.T) {
+		cmds := buildTier1Commands("/bin/fake", nil)
+		assert.Len(t, cmds, 3)
+	})
+
+	t.Run("two resources returns 5 commands", func(t *testing.T) {
+		cmds := buildTier1Commands("/bin/fake", []string{"users", "projects"})
+		assert.Len(t, cmds, 5)
+	})
+}
+
+func TestHasCredentials(t *testing.T) {
+	t.Run("empty slice returns false", func(t *testing.T) {
+		assert.False(t, hasCredentials(nil))
+		assert.False(t, hasCredentials([]string{}))
+	})
+
+	t.Run("unset vars return false", func(t *testing.T) {
+		assert.False(t, hasCredentials([]string{"TOTALLY_UNSET_VAR_12345"}))
+	})
+
+	t.Run("set var returns true", func(t *testing.T) {
+		t.Setenv("TEST_CRED_ABC", "secret123")
+		assert.True(t, hasCredentials([]string{"TEST_CRED_ABC"}))
+	})
+}
+
+func TestComputeDogfoodScore(t *testing.T) {
+	t.Run("all pass returns 40+", func(t *testing.T) {
+		r := &DogfoodResults{
+			Tier:           1,
+			TotalCommands:  5,
+			PassedCommands: 5,
+		}
+		score := computeDogfoodScore(r)
+		assert.GreaterOrEqual(t, score, 40)
+	})
+
+	t.Run("half pass returns ~20", func(t *testing.T) {
+		r := &DogfoodResults{
+			Tier:           1,
+			TotalCommands:  10,
+			PassedCommands: 5,
+		}
+		score := computeDogfoodScore(r)
+		assert.Equal(t, 20, score)
+	})
+
+	t.Run("zero pass returns 0", func(t *testing.T) {
+		r := &DogfoodResults{
+			Tier:           1,
+			TotalCommands:  5,
+			PassedCommands: 0,
+		}
+		score := computeDogfoodScore(r)
+		assert.Equal(t, 0, score)
+	})
+
+	t.Run("zero total returns 0", func(t *testing.T) {
+		r := &DogfoodResults{
+			Tier:          1,
+			TotalCommands: 0,
+		}
+		score := computeDogfoodScore(r)
+		assert.Equal(t, 0, score)
+	})
+
+	t.Run("tier bonus adds points", func(t *testing.T) {
+		r := &DogfoodResults{
+			Tier:           2,
+			TotalCommands:  5,
+			PassedCommands: 5,
+		}
+		score := computeDogfoodScore(r)
+		assert.Equal(t, 45, score) // 40 base + 5 tier2 bonus
+	})
+}
+
+func TestWriteAndLoadDogfoodResults(t *testing.T) {
+	dir := t.TempDir()
+	original := &DogfoodResults{
+		Tier:           2,
+		TotalCommands:  4,
+		PassedCommands: 3,
+		FailedCommands: 1,
+		Score:          35,
+		Commands: []CommandResult{
+			{Tier: 1, Command: "--help", ExitCode: 0, Pass: true},
+			{Tier: 1, Command: "version", ExitCode: 0, Pass: true},
+			{Tier: 1, Command: "doctor", ExitCode: 0, Pass: true},
+			{Tier: 2, Command: "users list --json", ExitCode: 1, Pass: false},
+		},
+	}
+
+	err := writeDogfoodResults(original, dir)
+	require.NoError(t, err)
+
+	loaded, err := LoadDogfoodResults(dir)
+	require.NoError(t, err)
+	assert.Equal(t, 2, loaded.Tier)
+	assert.Equal(t, 4, loaded.TotalCommands)
+	assert.Equal(t, 3, loaded.PassedCommands)
+	assert.Equal(t, 1, loaded.FailedCommands)
+	assert.Equal(t, 35, loaded.Score)
+	assert.Len(t, loaded.Commands, 4)
+}
diff --git a/internal/pipeline/research_test.go b/internal/pipeline/research_test.go
new file mode 100644
index 00000000..d459a2b2
--- /dev/null
+++ b/internal/pipeline/research_test.go
@@ -0,0 +1,97 @@
+package pipeline
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestScoreNovelty(t *testing.T) {
+	t.Run("no alternatives returns 10", func(t *testing.T) {
+		score := scoreNovelty(nil)
+		assert.Equal(t, 10, score)
+	})
+
+	t.Run("one alt with 10000 stars returns 2", func(t *testing.T) {
+		alts := []Alternative{{Name: "popular-cli", Stars: 10000}}
+		score := scoreNovelty(alts)
+		assert.Equal(t, 2, score)
+	})
+
+	t.Run("one alt with 50 stars returns 7", func(t *testing.T) {
+		alts := []Alternative{{Name: "small-cli", Stars: 50}}
+		score := scoreNovelty(alts)
+		assert.Equal(t, 7, score)
+	})
+}
+
+func TestDeduplicateAlts(t *testing.T) {
+	alts := []Alternative{
+		{Name: "cli-a", URL: "https://github.com/org/cli-a"},
+		{Name: "cli-b", URL: "https://github.com/org/cli-b"},
+		{Name: "cli-a-dup", URL: "https://github.com/org/cli-a"},
+	}
+	result := deduplicateAlts(alts)
+	assert.Len(t, result, 2)
+	assert.Equal(t, "cli-a", result[0].Name)
+	assert.Equal(t, "cli-b", result[1].Name)
+}
+
+func TestRecommend(t *testing.T) {
+	tests := []struct {
+		score    int
+		expected string
+	}{
+		{1, "skip"},
+		{2, "skip"},
+		{3, "skip"},
+		{4, "proceed-with-gaps"},
+		{5, "proceed-with-gaps"},
+		{6, "proceed-with-gaps"},
+		{7, "proceed"},
+		{8, "proceed"},
+		{9, "proceed"},
+		{10, "proceed"},
+	}
+	for _, tt := range tests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, tt.expected, recommend(tt.score))
+		})
+	}
+}
+
+func TestAnalyzeAlternatives(t *testing.T) {
+	alts := []Alternative{
+		{Name: "tool-a", Language: "python", HasJSON: false},
+		{Name: "tool-b", Language: "typescript", HasJSON: true},
+	}
+	gaps, patterns := analyzeAlternatives(alts)
+	assert.NotEmpty(t, gaps)
+	assert.NotEmpty(t, patterns)
+}
+
+func TestWriteAndLoadResearch(t *testing.T) {
+	dir := t.TempDir()
+	result := &ResearchResult{
+		APIName:        "test-api",
+		NoveltyScore:   8,
+		Recommendation: "proceed",
+		Alternatives: []Alternative{
+			{Name: "alt-1", URL: "https://example.com/alt-1"},
+		},
+		Gaps:     []string{"no --json"},
+		Patterns: []string{"standard CRUD"},
+	}
+
+	err := writeResearchJSON(result, dir)
+	require.NoError(t, err)
+
+	loaded, err := LoadResearch(dir)
+	require.NoError(t, err)
+	assert.Equal(t, "test-api", loaded.APIName)
+	assert.Equal(t, 8, loaded.NoveltyScore)
+	assert.Equal(t, "proceed", loaded.Recommendation)
+	assert.Len(t, loaded.Alternatives, 1)
+	assert.Equal(t, "alt-1", loaded.Alternatives[0].Name)
+}

← 2370b45b fix(templates): guard readme.md.tmpl against empty Auth.EnvV  ·  back to Cli Printing Press  ·  feat(catalog): generate Plaid CLI from official OpenAPI spec 66bc4ea8 →