Optimize CLI tests with in-process testing (bd-ky74)
- Convert exec.Command() tests to in-process rootCmd.Execute() - Achieve ~10x speedup (3.8s vs 40s for 17 tests) - Add mutex serialization for thread safety - Implement manual temp dir cleanup with retries - Reset global state between tests to prevent contamination - Keep TestCLI_EndToEnd for binary validation
This commit is contained in:
@@ -1,34 +1,177 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Fast CLI tests converted from scripttest suite
|
||||
// These run with --no-daemon flag to avoid daemon startup overhead
|
||||
// These use in-process testing (calling rootCmd.Execute directly) for speed
|
||||
// A few tests still use exec.Command for end-to-end validation
|
||||
//
|
||||
// Performance improvement (bd-ky74):
|
||||
// - Before: exec.Command() tests took 2-4 seconds each (~40s total)
|
||||
// - After: in-process tests take <1 second each, ~10x faster
|
||||
// - End-to-end test (TestCLI_EndToEnd) still validates binary with exec.Command
|
||||
|
||||
var (
|
||||
inProcessMutex sync.Mutex // Protects concurrent access to rootCmd and global state
|
||||
)
|
||||
|
||||
// setupCLITestDB creates a fresh initialized bd database for CLI tests
|
||||
func setupCLITestDB(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
runBD(t, tmpDir, "init", "--prefix", "test", "--quiet")
|
||||
tmpDir := createTempDirWithCleanup(t)
|
||||
runBDInProcess(t, tmpDir, "init", "--prefix", "test", "--quiet")
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
// createTempDirWithCleanup creates a temp directory with non-fatal cleanup
|
||||
// This prevents test failures from SQLite file lock cleanup issues
|
||||
func createTempDirWithCleanup(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "bd-cli-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
// Retry cleanup with delays to handle SQLite file locks
|
||||
// Don't fail the test if cleanup fails - just log it
|
||||
for i := 0; i < 5; i++ {
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err == nil {
|
||||
return // Success
|
||||
}
|
||||
if i < 4 {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
// Final attempt failed - log but don't fail test
|
||||
t.Logf("Warning: Failed to clean up temp dir %s (SQLite file locks)", tmpDir)
|
||||
})
|
||||
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
// runBDInProcess runs bd commands in-process by calling rootCmd.Execute
|
||||
// This is ~10-20x faster than exec.Command because it avoids process spawn overhead
|
||||
func runBDInProcess(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
// Serialize all in-process test execution to avoid race conditions
|
||||
// rootCmd, cobra state, and viper are not thread-safe
|
||||
inProcessMutex.Lock()
|
||||
defer inProcessMutex.Unlock()
|
||||
|
||||
// Add --no-daemon to all commands except init
|
||||
if len(args) > 0 && args[0] != "init" {
|
||||
args = append([]string{"--no-daemon"}, args...)
|
||||
}
|
||||
|
||||
// Save original state
|
||||
oldStdout := os.Stdout
|
||||
oldStderr := os.Stderr
|
||||
oldDir, _ := os.Getwd()
|
||||
oldArgs := os.Args
|
||||
|
||||
// Change to test directory
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("Failed to chdir to %s: %v", dir, err)
|
||||
}
|
||||
|
||||
// Capture stdout/stderr
|
||||
rOut, wOut, _ := os.Pipe()
|
||||
rErr, wErr, _ := os.Pipe()
|
||||
os.Stdout = wOut
|
||||
os.Stderr = wErr
|
||||
|
||||
// Set args for rootCmd
|
||||
rootCmd.SetArgs(args)
|
||||
os.Args = append([]string{"bd"}, args...)
|
||||
|
||||
// Set environment
|
||||
os.Setenv("BEADS_NO_DAEMON", "1")
|
||||
defer os.Unsetenv("BEADS_NO_DAEMON")
|
||||
|
||||
// Execute command
|
||||
err := rootCmd.Execute()
|
||||
|
||||
// Close and clean up all global state to prevent contamination between tests
|
||||
if store != nil {
|
||||
store.Close()
|
||||
store = nil
|
||||
}
|
||||
if daemonClient != nil {
|
||||
daemonClient.Close()
|
||||
daemonClient = nil
|
||||
}
|
||||
|
||||
// Reset all global flags and state
|
||||
dbPath = ""
|
||||
actor = ""
|
||||
jsonOutput = false
|
||||
noDaemon = false
|
||||
noAutoFlush = false
|
||||
noAutoImport = false
|
||||
sandboxMode = false
|
||||
noDb = false
|
||||
autoFlushEnabled = true
|
||||
isDirty = false
|
||||
needsFullExport = false
|
||||
storeActive = false
|
||||
flushFailureCount = 0
|
||||
lastFlushError = nil
|
||||
if flushTimer != nil {
|
||||
flushTimer.Stop()
|
||||
flushTimer = nil
|
||||
}
|
||||
|
||||
// Give SQLite time to release file locks before cleanup
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Close writers and restore
|
||||
wOut.Close()
|
||||
wErr.Close()
|
||||
os.Stdout = oldStdout
|
||||
os.Stderr = oldStderr
|
||||
os.Chdir(oldDir)
|
||||
os.Args = oldArgs
|
||||
rootCmd.SetArgs(nil)
|
||||
|
||||
// Read output (keep stdout and stderr separate)
|
||||
var outBuf, errBuf bytes.Buffer
|
||||
outBuf.ReadFrom(rOut)
|
||||
errBuf.ReadFrom(rErr)
|
||||
|
||||
stdout := outBuf.String()
|
||||
stderr := errBuf.String()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("bd %v failed: %v\nStdout: %s\nStderr: %s", args, err, stdout, stderr)
|
||||
}
|
||||
|
||||
// Return only stdout (stderr contains warnings that break JSON parsing)
|
||||
return stdout
|
||||
}
|
||||
|
||||
func TestCLI_Ready(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
runBD(t, tmpDir, "create", "Ready issue", "-p", "1")
|
||||
out := runBD(t, tmpDir, "ready")
|
||||
runBDInProcess(t, tmpDir, "create", "Ready issue", "-p", "1")
|
||||
out := runBDInProcess(t, tmpDir, "ready")
|
||||
if !strings.Contains(out, "Ready issue") {
|
||||
t.Errorf("Expected 'Ready issue' in output, got: %s", out)
|
||||
}
|
||||
@@ -40,7 +183,7 @@ func TestCLI_Create(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Test issue", "-p", "1", "--json")
|
||||
out := runBDInProcess(t, tmpDir, "create", "Test issue", "-p", "1", "--json")
|
||||
|
||||
// Extract JSON from output (may contain warnings before JSON)
|
||||
jsonStart := strings.Index(out, "{")
|
||||
@@ -64,10 +207,10 @@ func TestCLI_List(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
runBD(t, tmpDir, "create", "First", "-p", "1")
|
||||
runBD(t, tmpDir, "create", "Second", "-p", "2")
|
||||
runBDInProcess(t, tmpDir, "create", "First", "-p", "1")
|
||||
runBDInProcess(t, tmpDir, "create", "Second", "-p", "2")
|
||||
|
||||
out := runBD(t, tmpDir, "list", "--json")
|
||||
out := runBDInProcess(t, tmpDir, "list", "--json")
|
||||
var issues []map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(out), &issues); err != nil {
|
||||
t.Fatalf("Failed to parse JSON: %v", err)
|
||||
@@ -83,15 +226,15 @@ func TestCLI_Update(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Issue to update", "-p", "1", "--json")
|
||||
out := runBDInProcess(t, tmpDir, "create", "Issue to update", "-p", "1", "--json")
|
||||
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
runBD(t, tmpDir, "update", id, "--status", "in_progress")
|
||||
runBDInProcess(t, tmpDir, "update", id, "--status", "in_progress")
|
||||
|
||||
out = runBD(t, tmpDir, "show", id, "--json")
|
||||
out = runBDInProcess(t, tmpDir, "show", id, "--json")
|
||||
var updated []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &updated)
|
||||
if updated[0]["status"] != "in_progress" {
|
||||
@@ -105,15 +248,15 @@ func TestCLI_Close(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Issue to close", "-p", "1", "--json")
|
||||
out := runBDInProcess(t, tmpDir, "create", "Issue to close", "-p", "1", "--json")
|
||||
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
runBD(t, tmpDir, "close", id, "--reason", "Done")
|
||||
runBDInProcess(t, tmpDir, "close", id, "--reason", "Done")
|
||||
|
||||
out = runBD(t, tmpDir, "show", id, "--json")
|
||||
out = runBDInProcess(t, tmpDir, "show", id, "--json")
|
||||
var closed []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &closed)
|
||||
if closed[0]["status"] != "closed" {
|
||||
@@ -128,8 +271,8 @@ func TestCLI_DepAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
out1 := runBD(t, tmpDir, "create", "First", "-p", "1", "--json")
|
||||
out2 := runBD(t, tmpDir, "create", "Second", "-p", "1", "--json")
|
||||
out1 := runBDInProcess(t, tmpDir, "create", "First", "-p", "1", "--json")
|
||||
out2 := runBDInProcess(t, tmpDir, "create", "Second", "-p", "1", "--json")
|
||||
|
||||
var issue1, issue2 map[string]interface{}
|
||||
json.Unmarshal([]byte(out1), &issue1)
|
||||
@@ -138,7 +281,7 @@ func TestCLI_DepAdd(t *testing.T) {
|
||||
id1 := issue1["id"].(string)
|
||||
id2 := issue2["id"].(string)
|
||||
|
||||
out := runBD(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBDInProcess(t, tmpDir, "dep", "add", id2, id1)
|
||||
if !strings.Contains(out, "Added dependency") {
|
||||
t.Errorf("Expected 'Added dependency', got: %s", out)
|
||||
}
|
||||
@@ -151,8 +294,8 @@ func TestCLI_DepRemove(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
out1 := runBD(t, tmpDir, "create", "First", "-p", "1", "--json")
|
||||
out2 := runBD(t, tmpDir, "create", "Second", "-p", "1", "--json")
|
||||
out1 := runBDInProcess(t, tmpDir, "create", "First", "-p", "1", "--json")
|
||||
out2 := runBDInProcess(t, tmpDir, "create", "Second", "-p", "1", "--json")
|
||||
|
||||
var issue1, issue2 map[string]interface{}
|
||||
json.Unmarshal([]byte(out1), &issue1)
|
||||
@@ -161,8 +304,8 @@ func TestCLI_DepRemove(t *testing.T) {
|
||||
id1 := issue1["id"].(string)
|
||||
id2 := issue2["id"].(string)
|
||||
|
||||
runBD(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBD(t, tmpDir, "dep", "remove", id2, id1)
|
||||
runBDInProcess(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBDInProcess(t, tmpDir, "dep", "remove", id2, id1)
|
||||
if !strings.Contains(out, "Removed dependency") {
|
||||
t.Errorf("Expected 'Removed dependency', got: %s", out)
|
||||
}
|
||||
@@ -175,8 +318,8 @@ func TestCLI_DepTree(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
out1 := runBD(t, tmpDir, "create", "Parent", "-p", "1", "--json")
|
||||
out2 := runBD(t, tmpDir, "create", "Child", "-p", "1", "--json")
|
||||
out1 := runBDInProcess(t, tmpDir, "create", "Parent", "-p", "1", "--json")
|
||||
out2 := runBDInProcess(t, tmpDir, "create", "Child", "-p", "1", "--json")
|
||||
|
||||
var issue1, issue2 map[string]interface{}
|
||||
json.Unmarshal([]byte(out1), &issue1)
|
||||
@@ -185,8 +328,8 @@ func TestCLI_DepTree(t *testing.T) {
|
||||
id1 := issue1["id"].(string)
|
||||
id2 := issue2["id"].(string)
|
||||
|
||||
runBD(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBD(t, tmpDir, "dep", "tree", id1)
|
||||
runBDInProcess(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBDInProcess(t, tmpDir, "dep", "tree", id1)
|
||||
if !strings.Contains(out, "Parent") {
|
||||
t.Errorf("Expected 'Parent' in tree, got: %s", out)
|
||||
}
|
||||
@@ -199,8 +342,8 @@ func TestCLI_Blocked(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
out1 := runBD(t, tmpDir, "create", "Blocker", "-p", "1", "--json")
|
||||
out2 := runBD(t, tmpDir, "create", "Blocked", "-p", "1", "--json")
|
||||
out1 := runBDInProcess(t, tmpDir, "create", "Blocker", "-p", "1", "--json")
|
||||
out2 := runBDInProcess(t, tmpDir, "create", "Blocked", "-p", "1", "--json")
|
||||
|
||||
var issue1, issue2 map[string]interface{}
|
||||
json.Unmarshal([]byte(out1), &issue1)
|
||||
@@ -209,8 +352,8 @@ func TestCLI_Blocked(t *testing.T) {
|
||||
id1 := issue1["id"].(string)
|
||||
id2 := issue2["id"].(string)
|
||||
|
||||
runBD(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBD(t, tmpDir, "blocked")
|
||||
runBDInProcess(t, tmpDir, "dep", "add", id2, id1)
|
||||
out := runBDInProcess(t, tmpDir, "blocked")
|
||||
if !strings.Contains(out, "Blocked") {
|
||||
t.Errorf("Expected 'Blocked' in output, got: %s", out)
|
||||
}
|
||||
@@ -222,10 +365,10 @@ func TestCLI_Stats(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
runBD(t, tmpDir, "create", "Issue 1", "-p", "1")
|
||||
runBD(t, tmpDir, "create", "Issue 2", "-p", "1")
|
||||
runBDInProcess(t, tmpDir, "create", "Issue 1", "-p", "1")
|
||||
runBDInProcess(t, tmpDir, "create", "Issue 2", "-p", "1")
|
||||
|
||||
out := runBD(t, tmpDir, "stats")
|
||||
out := runBDInProcess(t, tmpDir, "stats")
|
||||
if !strings.Contains(out, "Total") || !strings.Contains(out, "2") {
|
||||
t.Errorf("Expected stats to show 2 issues, got: %s", out)
|
||||
}
|
||||
@@ -237,13 +380,13 @@ func TestCLI_Show(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Show test", "-p", "1", "--json")
|
||||
out := runBDInProcess(t, tmpDir, "create", "Show test", "-p", "1", "--json")
|
||||
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
out = runBD(t, tmpDir, "show", id)
|
||||
out = runBDInProcess(t, tmpDir, "show", id)
|
||||
if !strings.Contains(out, "Show test") {
|
||||
t.Errorf("Expected 'Show test' in output, got: %s", out)
|
||||
}
|
||||
@@ -255,10 +398,10 @@ func TestCLI_Export(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
runBD(t, tmpDir, "create", "Export test", "-p", "1")
|
||||
runBDInProcess(t, tmpDir, "create", "Export test", "-p", "1")
|
||||
|
||||
exportFile := filepath.Join(tmpDir, "export.jsonl")
|
||||
runBD(t, tmpDir, "export", "-o", exportFile)
|
||||
runBDInProcess(t, tmpDir, "export", "-o", exportFile)
|
||||
|
||||
if _, err := os.Stat(exportFile); os.IsNotExist(err) {
|
||||
t.Errorf("Export file not created: %s", exportFile)
|
||||
@@ -271,17 +414,17 @@ func TestCLI_Import(t *testing.T) {
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
runBD(t, tmpDir, "create", "Import test", "-p", "1")
|
||||
runBDInProcess(t, tmpDir, "create", "Import test", "-p", "1")
|
||||
|
||||
exportFile := filepath.Join(tmpDir, "export.jsonl")
|
||||
runBD(t, tmpDir, "export", "-o", exportFile)
|
||||
runBDInProcess(t, tmpDir, "export", "-o", exportFile)
|
||||
|
||||
// Create new db and import
|
||||
tmpDir2 := t.TempDir()
|
||||
runBD(t, tmpDir2, "init", "--prefix", "test", "--quiet")
|
||||
runBD(t, tmpDir2, "import", "-i", exportFile)
|
||||
tmpDir2 := createTempDirWithCleanup(t)
|
||||
runBDInProcess(t, tmpDir2, "init", "--prefix", "test", "--quiet")
|
||||
runBDInProcess(t, tmpDir2, "import", "-i", exportFile)
|
||||
|
||||
out := runBD(t, tmpDir2, "list", "--json")
|
||||
out := runBDInProcess(t, tmpDir2, "list", "--json")
|
||||
var issues []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &issues)
|
||||
if len(issues) != 1 {
|
||||
@@ -319,95 +462,9 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_Labels(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Label test", "-p", "1", "--json")
|
||||
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
// Add label
|
||||
runBD(t, tmpDir, "label", "add", id, "urgent")
|
||||
|
||||
// List labels
|
||||
out = runBD(t, tmpDir, "label", "list", id)
|
||||
if !strings.Contains(out, "urgent") {
|
||||
t.Errorf("Expected 'urgent' label, got: %s", out)
|
||||
}
|
||||
|
||||
// Remove label
|
||||
runBD(t, tmpDir, "label", "remove", id, "urgent")
|
||||
out = runBD(t, tmpDir, "label", "list", id)
|
||||
if strings.Contains(out, "urgent") {
|
||||
t.Errorf("Label should be removed, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_PriorityFormats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Test numeric priority
|
||||
out := runBD(t, tmpDir, "create", "Test P0", "-p", "0", "--json")
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
if issue["priority"].(float64) != 0 {
|
||||
t.Errorf("Expected priority 0, got: %v", issue["priority"])
|
||||
}
|
||||
|
||||
// Test P-format priority
|
||||
out = runBD(t, tmpDir, "create", "Test P3", "-p", "P3", "--json")
|
||||
jsonStart = strings.Index(out, "{")
|
||||
jsonOut = out[jsonStart:]
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
if issue["priority"].(float64) != 3 {
|
||||
t.Errorf("Expected priority 3, got: %v", issue["priority"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_Reopen(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBD(t, tmpDir, "create", "Reopen test", "-p", "1", "--json")
|
||||
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
// Close it
|
||||
runBD(t, tmpDir, "close", id)
|
||||
|
||||
// Reopen it
|
||||
runBD(t, tmpDir, "reopen", id)
|
||||
|
||||
out = runBD(t, tmpDir, "show", id, "--json")
|
||||
var reopened []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &reopened)
|
||||
if reopened[0]["status"] != "open" {
|
||||
t.Errorf("Expected status 'open', got: %v", reopened[0]["status"])
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to run bd command in tmpDir with --no-daemon
|
||||
func runBD(t *testing.T, dir string, args ...string) string {
|
||||
// runBDExec runs bd via exec.Command for end-to-end testing
|
||||
// This is kept for a few tests to ensure the actual binary works correctly
|
||||
func runBDExec(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
// Add --no-daemon to all commands except init
|
||||
@@ -425,3 +482,131 @@ func runBD(t *testing.T, dir string, args ...string) string {
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// TestCLI_EndToEnd performs end-to-end testing using the actual binary
|
||||
// This ensures the compiled binary works correctly when executed normally
|
||||
func TestCLI_EndToEnd(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
tmpDir := createTempDirWithCleanup(t)
|
||||
|
||||
// Test full workflow with exec.Command to validate binary
|
||||
runBDExec(t, tmpDir, "init", "--prefix", "test", "--quiet")
|
||||
|
||||
out := runBDExec(t, tmpDir, "create", "E2E test", "-p", "1", "--json")
|
||||
var issue map[string]interface{}
|
||||
jsonStart := strings.Index(out, "{")
|
||||
json.Unmarshal([]byte(out[jsonStart:]), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
runBDExec(t, tmpDir, "update", id, "--status", "in_progress")
|
||||
runBDExec(t, tmpDir, "close", id, "--reason", "Done")
|
||||
|
||||
out = runBDExec(t, tmpDir, "show", id, "--json")
|
||||
var closed []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &closed)
|
||||
|
||||
if closed[0]["status"] != "closed" {
|
||||
t.Errorf("Expected status 'closed', got: %v", closed[0]["status"])
|
||||
}
|
||||
|
||||
// Test export
|
||||
exportFile := filepath.Join(tmpDir, "export.jsonl")
|
||||
runBDExec(t, tmpDir, "export", "-o", exportFile)
|
||||
|
||||
if _, err := os.Stat(exportFile); os.IsNotExist(err) {
|
||||
t.Errorf("Export file not created: %s", exportFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_Labels(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBDInProcess(t, tmpDir, "create", "Label test", "-p", "1", "--json")
|
||||
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
// Add label
|
||||
runBDInProcess(t, tmpDir, "label", "add", id, "urgent")
|
||||
|
||||
// List labels
|
||||
out = runBDInProcess(t, tmpDir, "label", "list", id)
|
||||
if !strings.Contains(out, "urgent") {
|
||||
t.Errorf("Expected 'urgent' label, got: %s", out)
|
||||
}
|
||||
|
||||
// Remove label
|
||||
runBDInProcess(t, tmpDir, "label", "remove", id, "urgent")
|
||||
out = runBDInProcess(t, tmpDir, "label", "list", id)
|
||||
if strings.Contains(out, "urgent") {
|
||||
t.Errorf("Label should be removed, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_PriorityFormats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Test numeric priority
|
||||
out := runBDInProcess(t, tmpDir, "create", "Test P0", "-p", "0", "--json")
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
if issue["priority"].(float64) != 0 {
|
||||
t.Errorf("Expected priority 0, got: %v", issue["priority"])
|
||||
}
|
||||
|
||||
// Test P-format priority
|
||||
out = runBDInProcess(t, tmpDir, "create", "Test P3", "-p", "P3", "--json")
|
||||
jsonStart = strings.Index(out, "{")
|
||||
jsonOut = out[jsonStart:]
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
if issue["priority"].(float64) != 3 {
|
||||
t.Errorf("Expected priority 3, got: %v", issue["priority"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_Reopen(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
tmpDir := setupCLITestDB(t)
|
||||
out := runBDInProcess(t, tmpDir, "create", "Reopen test", "-p", "1", "--json")
|
||||
|
||||
jsonStart := strings.Index(out, "{")
|
||||
jsonOut := out[jsonStart:]
|
||||
var issue map[string]interface{}
|
||||
json.Unmarshal([]byte(jsonOut), &issue)
|
||||
id := issue["id"].(string)
|
||||
|
||||
// Close it
|
||||
runBDInProcess(t, tmpDir, "close", id)
|
||||
|
||||
// Reopen it
|
||||
runBDInProcess(t, tmpDir, "reopen", id)
|
||||
|
||||
out = runBDInProcess(t, tmpDir, "show", id, "--json")
|
||||
var reopened []map[string]interface{}
|
||||
json.Unmarshal([]byte(out), &reopened)
|
||||
if reopened[0]["status"] != "open" {
|
||||
t.Errorf("Expected status 'open', got: %v", reopened[0]["status"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user