Add CLI output tests, remove obsolete design docs (bd-37)
- Added cmd/bd/output_test.go with 7 new tests for CLI output functions - Improved coverage: outputJSON (0→60%), printCollisionReport (0→100%), printRemappingReport (0→100%) - Removed obsolete design documents that were implemented or superseded: - DAEMON_DESIGN.md (daemon implemented) - DAEMON_STRESS_TEST.md (stress testing complete) - DESIGN.md (general design, superseded by other docs) - DUPLICATES_REPORT.md (old analysis) - REVIEW_BD379.md (completed review) - Closed bd-37: All critical features now have test coverage Amp-Thread-ID: https://ampcode.com/threads/T-9ee0fcdd-3675-42ea-966b-64ee3d9248a3 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
323
cmd/bd/output_test.go
Normal file
323
cmd/bd/output_test.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
func TestOutputJSON(t *testing.T) {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Test data
|
||||
testData := map[string]interface{}{
|
||||
"id": "bd-1",
|
||||
"title": "Test Issue",
|
||||
"count": 42,
|
||||
}
|
||||
|
||||
// Call outputJSON
|
||||
outputJSON(testData)
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify it's valid JSON
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal([]byte(output), &result)
|
||||
if err != nil {
|
||||
t.Fatalf("outputJSON did not produce valid JSON: %v", err)
|
||||
}
|
||||
|
||||
// Verify content
|
||||
if result["id"] != "bd-1" {
|
||||
t.Errorf("Expected id 'bd-1', got '%v'", result["id"])
|
||||
}
|
||||
if result["title"] != "Test Issue" {
|
||||
t.Errorf("Expected title 'Test Issue', got '%v'", result["title"])
|
||||
}
|
||||
// Note: JSON numbers are float64
|
||||
if result["count"] != float64(42) {
|
||||
t.Errorf("Expected count 42, got %v", result["count"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputJSONArray(t *testing.T) {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Test data - array of issues
|
||||
testData := []map[string]string{
|
||||
{"id": "bd-1", "title": "First"},
|
||||
{"id": "bd-2", "title": "Second"},
|
||||
}
|
||||
|
||||
// Call outputJSON
|
||||
outputJSON(testData)
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify it's valid JSON array
|
||||
var result []map[string]string
|
||||
err := json.Unmarshal([]byte(output), &result)
|
||||
if err != nil {
|
||||
t.Fatalf("outputJSON did not produce valid JSON array: %v", err)
|
||||
}
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("Expected 2 items, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintCollisionReport(t *testing.T) {
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
|
||||
// Create collision data
|
||||
result := &sqlite.CollisionResult{
|
||||
ExactMatches: []string{"bd-1", "bd-2"},
|
||||
NewIssues: []string{"bd-3", "bd-4", "bd-5"},
|
||||
Collisions: []*sqlite.CollisionDetail{
|
||||
{
|
||||
ID: "bd-6",
|
||||
IncomingIssue: &types.Issue{
|
||||
ID: "bd-6",
|
||||
Title: "Test Issue 6",
|
||||
},
|
||||
ConflictingFields: []string{"title", "priority"},
|
||||
},
|
||||
{
|
||||
ID: "bd-7",
|
||||
IncomingIssue: &types.Issue{
|
||||
ID: "bd-7",
|
||||
Title: "Test Issue 7",
|
||||
},
|
||||
ConflictingFields: []string{"description"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Call printCollisionReport
|
||||
printCollisionReport(result)
|
||||
|
||||
// Restore stderr
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify output contains expected sections
|
||||
if !strings.Contains(output, "Collision Detection Report") {
|
||||
t.Errorf("Expected report header. Got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "Exact matches (idempotent): 2") {
|
||||
t.Errorf("Expected exact matches count. Got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "New issues: 3") {
|
||||
t.Errorf("Expected new issues count. Got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "COLLISIONS DETECTED: 2") {
|
||||
t.Errorf("Expected collisions count. Got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "bd-6") {
|
||||
t.Errorf("Expected first collision ID. Got: %s", output)
|
||||
}
|
||||
// The field names are printed directly, not in brackets
|
||||
if !strings.Contains(output, "title") || !strings.Contains(output, "priority") {
|
||||
t.Errorf("Expected conflicting fields for bd-6. Got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintCollisionReportNoCollisions(t *testing.T) {
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
|
||||
// Create data with no collisions
|
||||
result := &sqlite.CollisionResult{
|
||||
ExactMatches: []string{"bd-1", "bd-2", "bd-3"},
|
||||
NewIssues: []string{"bd-4"},
|
||||
Collisions: []*sqlite.CollisionDetail{},
|
||||
}
|
||||
|
||||
// Call printCollisionReport
|
||||
printCollisionReport(result)
|
||||
|
||||
// Restore stderr
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify output shows no collisions
|
||||
if !strings.Contains(output, "COLLISIONS DETECTED: 0") {
|
||||
t.Error("Expected 0 collisions")
|
||||
}
|
||||
if strings.Contains(output, "Colliding issues:") {
|
||||
t.Error("Should not show colliding issues section when there are none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintRemappingReport(t *testing.T) {
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
|
||||
// Create remapping data
|
||||
remapping := map[string]string{
|
||||
"bd-10": "bd-100",
|
||||
"bd-20": "bd-200",
|
||||
"bd-30": "bd-300",
|
||||
}
|
||||
collisions := []*sqlite.CollisionDetail{
|
||||
{ID: "bd-10", ReferenceScore: 5},
|
||||
{ID: "bd-20", ReferenceScore: 0},
|
||||
{ID: "bd-30", ReferenceScore: 12},
|
||||
}
|
||||
|
||||
// Call printRemappingReport
|
||||
printRemappingReport(remapping, collisions)
|
||||
|
||||
// Restore stderr
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify output contains expected information
|
||||
if !strings.Contains(output, "Remapping Report") {
|
||||
t.Errorf("Expected report title. Got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "bd-10 → bd-100") {
|
||||
t.Error("Expected first remapping")
|
||||
}
|
||||
if !strings.Contains(output, "refs: 5") {
|
||||
t.Error("Expected reference count for bd-10")
|
||||
}
|
||||
if !strings.Contains(output, "bd-20 → bd-200") {
|
||||
t.Error("Expected second remapping")
|
||||
}
|
||||
if !strings.Contains(output, "refs: 0") {
|
||||
t.Error("Expected 0 references for bd-20")
|
||||
}
|
||||
if !strings.Contains(output, "bd-30 → bd-300") {
|
||||
t.Error("Expected third remapping")
|
||||
}
|
||||
if !strings.Contains(output, "refs: 12") {
|
||||
t.Error("Expected reference count for bd-30")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintRemappingReportEmpty(t *testing.T) {
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
|
||||
// Empty remapping
|
||||
remapping := map[string]string{}
|
||||
collisions := []*sqlite.CollisionDetail{}
|
||||
|
||||
// Call printRemappingReport
|
||||
printRemappingReport(remapping, collisions)
|
||||
|
||||
// Restore stderr
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Should still have header
|
||||
if !strings.Contains(output, "Remapping Report") {
|
||||
t.Errorf("Expected report title even with no remappings. Got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintRemappingReportOrdering(t *testing.T) {
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
|
||||
// Create remapping with different reference scores
|
||||
// Ordering is by reference score (ascending)
|
||||
remapping := map[string]string{
|
||||
"bd-2": "bd-200",
|
||||
"bd-10": "bd-100",
|
||||
"bd-100": "bd-1000",
|
||||
}
|
||||
collisions := []*sqlite.CollisionDetail{
|
||||
{ID: "bd-2", ReferenceScore: 10}, // highest refs
|
||||
{ID: "bd-10", ReferenceScore: 5}, // medium refs
|
||||
{ID: "bd-100", ReferenceScore: 1}, // lowest refs
|
||||
}
|
||||
|
||||
// Call printRemappingReport
|
||||
printRemappingReport(remapping, collisions)
|
||||
|
||||
// Restore stderr
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
|
||||
// Read output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Find positions of each remapping in output
|
||||
pos2 := strings.Index(output, "bd-2 →")
|
||||
pos10 := strings.Index(output, "bd-10 →")
|
||||
pos100 := strings.Index(output, "bd-100 →")
|
||||
|
||||
// Verify ordering by reference score (ascending): bd-100 (1 ref) < bd-10 (5 refs) < bd-2 (10 refs)
|
||||
if pos2 == -1 || pos10 == -1 || pos100 == -1 {
|
||||
t.Fatalf("Missing remappings in output: %s", output)
|
||||
}
|
||||
if !(pos100 < pos10 && pos10 < pos2) {
|
||||
t.Errorf("Remappings not in reference score order. Got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: createIssuesFromMarkdown is tested via cmd/bd/markdown_test.go which has
|
||||
// comprehensive tests for the markdown parsing functionality. We don't duplicate
|
||||
// those tests here since they require full DB setup.
|
||||
Reference in New Issue
Block a user