test: add tests for bd create --dry-run flag (bd-nib2)
Adds runBDInProcessAllowError helper and dry-run test coverage. Interrupted work - committing to preserve progress. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -672,19 +672,19 @@ func TestCLI_Reopen(t *testing.T) {
|
||||
// Note: Not using t.Parallel() because inProcessMutex serializes execution anyway
|
||||
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)
|
||||
@@ -693,4 +693,283 @@ func TestCLI_Reopen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// runBDInProcessAllowError is like runBDInProcess but doesn't fail on error
|
||||
// Returns stdout, stderr, and any error from command execution
|
||||
func runBDInProcessAllowError(t *testing.T, dir string, args ...string) (string, string, error) {
|
||||
t.Helper()
|
||||
|
||||
inProcessMutex.Lock()
|
||||
defer inProcessMutex.Unlock()
|
||||
|
||||
if len(args) > 0 && args[0] != "init" {
|
||||
args = append([]string{"--no-daemon"}, args...)
|
||||
}
|
||||
|
||||
oldStdout := os.Stdout
|
||||
oldStderr := os.Stderr
|
||||
oldDir, _ := os.Getwd()
|
||||
oldArgs := os.Args
|
||||
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("Failed to chdir to %s: %v", dir, err)
|
||||
}
|
||||
|
||||
rOut, wOut, _ := os.Pipe()
|
||||
rErr, wErr, _ := os.Pipe()
|
||||
os.Stdout = wOut
|
||||
os.Stderr = wErr
|
||||
|
||||
rootCmd.SetArgs(args)
|
||||
os.Args = append([]string{"bd"}, args...)
|
||||
|
||||
os.Setenv("BEADS_NO_DAEMON", "1")
|
||||
defer os.Unsetenv("BEADS_NO_DAEMON")
|
||||
|
||||
cmdErr := rootCmd.Execute()
|
||||
|
||||
if store != nil {
|
||||
store.Close()
|
||||
store = nil
|
||||
}
|
||||
if daemonClient != nil {
|
||||
daemonClient.Close()
|
||||
daemonClient = nil
|
||||
}
|
||||
|
||||
dbPath = ""
|
||||
actor = ""
|
||||
jsonOutput = false
|
||||
noDaemon = false
|
||||
noAutoFlush = false
|
||||
noAutoImport = false
|
||||
sandboxMode = false
|
||||
noDb = false
|
||||
autoFlushEnabled = true
|
||||
storeActive = false
|
||||
flushFailureCount = 0
|
||||
lastFlushError = nil
|
||||
if flushManager != nil {
|
||||
_ = flushManager.Shutdown()
|
||||
flushManager = nil
|
||||
}
|
||||
rootCtx = nil
|
||||
rootCancel = nil
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
wOut.Close()
|
||||
wErr.Close()
|
||||
os.Stdout = oldStdout
|
||||
os.Stderr = oldStderr
|
||||
os.Chdir(oldDir)
|
||||
os.Args = oldArgs
|
||||
rootCmd.SetArgs(nil)
|
||||
|
||||
var outBuf, errBuf bytes.Buffer
|
||||
outBuf.ReadFrom(rOut)
|
||||
errBuf.ReadFrom(rErr)
|
||||
|
||||
return outBuf.String(), errBuf.String(), cmdErr
|
||||
}
|
||||
|
||||
// TestCLI_CreateDryRun tests the --dry-run flag for bd create command (bd-nib2)
|
||||
func TestCLI_CreateDryRun(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow CLI test in short mode")
|
||||
}
|
||||
|
||||
t.Run("BasicDryRunPreview", func(t *testing.T) {
|
||||
// Note: Not using t.Parallel() because inProcessMutex serializes execution anyway
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run
|
||||
out := runBDInProcess(t, tmpDir, "create", "Test dry run issue", "-p", "1", "--dry-run")
|
||||
|
||||
// Verify output contains dry-run indicator
|
||||
if !strings.Contains(out, "[DRY RUN]") {
|
||||
t.Errorf("Expected '[DRY RUN]' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Would create issue") {
|
||||
t.Errorf("Expected 'Would create issue' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Test dry run issue") {
|
||||
t.Errorf("Expected title in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "(will be generated)") {
|
||||
t.Errorf("Expected '(will be generated)' for ID, got: %s", out)
|
||||
}
|
||||
|
||||
// Verify no issue was actually created
|
||||
listOut := runBDInProcess(t, tmpDir, "list", "--json")
|
||||
var issues []map[string]interface{}
|
||||
json.Unmarshal([]byte(listOut), &issues)
|
||||
if len(issues) != 0 {
|
||||
t.Errorf("Expected 0 issues after dry-run, got %d", len(issues))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithJSONOutput", func(t *testing.T) {
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run --json
|
||||
out := runBDInProcess(t, tmpDir, "create", "JSON dry run test", "-p", "2", "-t", "bug", "--dry-run", "--json")
|
||||
|
||||
// Find JSON in output (may have warnings before it)
|
||||
jsonStart := strings.Index(out, "{")
|
||||
if jsonStart < 0 {
|
||||
t.Fatalf("No JSON found in output: %s", out)
|
||||
}
|
||||
jsonOut := out[jsonStart:]
|
||||
|
||||
var issue map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonOut), &issue); err != nil {
|
||||
t.Fatalf("Failed to parse JSON: %v\nOutput: %s", err, jsonOut)
|
||||
}
|
||||
|
||||
// Verify JSON has empty ID (not a placeholder string)
|
||||
id, ok := issue["id"]
|
||||
if !ok {
|
||||
t.Error("Expected 'id' field in JSON output")
|
||||
}
|
||||
if id != "" {
|
||||
t.Errorf("Expected empty ID in dry-run JSON, got: %v", id)
|
||||
}
|
||||
|
||||
// Verify other fields are populated
|
||||
if issue["title"] != "JSON dry run test" {
|
||||
t.Errorf("Expected title 'JSON dry run test', got: %v", issue["title"])
|
||||
}
|
||||
if issue["issue_type"] != "bug" {
|
||||
t.Errorf("Expected issue_type 'bug', got: %v", issue["issue_type"])
|
||||
}
|
||||
if issue["priority"].(float64) != 2 {
|
||||
t.Errorf("Expected priority 2, got: %v", issue["priority"])
|
||||
}
|
||||
|
||||
// Verify no issue was actually created
|
||||
listOut := runBDInProcess(t, tmpDir, "list", "--json")
|
||||
var issues []map[string]interface{}
|
||||
json.Unmarshal([]byte(listOut), &issues)
|
||||
if len(issues) != 0 {
|
||||
t.Errorf("Expected 0 issues after dry-run, got %d", len(issues))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithLabelsAndDeps", func(t *testing.T) {
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run including labels and deps
|
||||
out := runBDInProcess(t, tmpDir, "create", "Issue with extras", "-p", "1",
|
||||
"--labels", "urgent,backend",
|
||||
"--deps", "blocks:test-123",
|
||||
"--dry-run")
|
||||
|
||||
// Verify labels are shown in preview
|
||||
if !strings.Contains(out, "Labels:") {
|
||||
t.Errorf("Expected 'Labels:' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "urgent") {
|
||||
t.Errorf("Expected 'urgent' label in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "backend") {
|
||||
t.Errorf("Expected 'backend' label in output, got: %s", out)
|
||||
}
|
||||
|
||||
// Verify dependencies are shown
|
||||
if !strings.Contains(out, "Dependencies:") {
|
||||
t.Errorf("Expected 'Dependencies:' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "blocks:test-123") {
|
||||
t.Errorf("Expected 'blocks:test-123' dependency in output, got: %s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithRigPrefix", func(t *testing.T) {
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run and --prefix (simulates cross-rig creation)
|
||||
// Note: This won't actually route to another rig since we don't have one,
|
||||
// but it should show the target rig in the preview
|
||||
out := runBDInProcess(t, tmpDir, "create", "Cross-rig issue", "-p", "1",
|
||||
"--prefix", "other-rig",
|
||||
"--dry-run")
|
||||
|
||||
// Verify target rig is shown in preview
|
||||
if !strings.Contains(out, "Target rig:") {
|
||||
t.Errorf("Expected 'Target rig:' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "other-rig") {
|
||||
t.Errorf("Expected 'other-rig' in output, got: %s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithFileReturnsError", func(t *testing.T) {
|
||||
// This test must use exec.Command because FatalError calls os.Exit(1)
|
||||
// which would kill the test process if run in-process
|
||||
tmpDir := createTempDirWithCleanup(t)
|
||||
|
||||
// Initialize the database first
|
||||
initCmd := exec.Command(testBD, "init", "--prefix", "test", "--quiet")
|
||||
initCmd.Dir = tmpDir
|
||||
initCmd.Env = append(os.Environ(), "BEADS_NO_DAEMON=1")
|
||||
if out, err := initCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("init failed: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Create a dummy markdown file
|
||||
mdFile := filepath.Join(tmpDir, "issues.md")
|
||||
os.WriteFile(mdFile, []byte("# Test Issue\n\nDescription here"), 0644)
|
||||
|
||||
// Run create with --dry-run and --file (should error)
|
||||
cmd := exec.Command(testBD, "--no-daemon", "create", "--file", mdFile, "--dry-run")
|
||||
cmd.Dir = tmpDir
|
||||
cmd.Env = append(os.Environ(), "BEADS_NO_DAEMON=1")
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error when using --dry-run with --file, but got none")
|
||||
}
|
||||
|
||||
// Verify error message is informative
|
||||
if !strings.Contains(string(out), "--dry-run is not supported with --file") {
|
||||
t.Errorf("Expected error about --dry-run with --file, got: %s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithEventType", func(t *testing.T) {
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run and event-specific fields
|
||||
out := runBDInProcess(t, tmpDir, "create", "Event issue", "-p", "1",
|
||||
"--type", "event",
|
||||
"--event-category", "agent.started",
|
||||
"--dry-run")
|
||||
|
||||
// Verify event category is shown in preview
|
||||
if !strings.Contains(out, "Event category:") {
|
||||
t.Errorf("Expected 'Event category:' in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "agent.started") {
|
||||
t.Errorf("Expected 'agent.started' in output, got: %s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DryRunWithExplicitID", func(t *testing.T) {
|
||||
tmpDir := setupCLITestDB(t)
|
||||
|
||||
// Run create with --dry-run and explicit ID
|
||||
out := runBDInProcess(t, tmpDir, "create", "Explicit ID issue", "-p", "1",
|
||||
"--id", "test-explicit123",
|
||||
"--dry-run")
|
||||
|
||||
// Verify explicit ID is shown (not "(will be generated)")
|
||||
if strings.Contains(out, "(will be generated)") {
|
||||
t.Errorf("Expected explicit ID in output, but got '(will be generated)': %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "test-explicit123") {
|
||||
t.Errorf("Expected 'test-explicit123' in output, got: %s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user