GH #210: bd init --no-db was creating config.yaml but leaving no-db commented out, forcing users to pass --no-db on every command. Changes: - Modified createConfigYaml() to accept noDbMode parameter - When true, writes 'no-db: true' instead of '# no-db: false' - Added TestInitNoDbMode() to verify end-to-end workflow The config reading logic was already in place (main.go:122), just needed to write the correct value during init. Fixes bd-5cny Amp-Thread-ID: https://ampcode.com/threads/T-2c569435-6291-40e8-b39b-c33fd317d853 Co-authored-by: Amp <amp@ampcode.com>
480 lines
13 KiB
Go
480 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInitCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
prefix string
|
|
quiet bool
|
|
wantOutputText string
|
|
wantNoOutput bool
|
|
}{
|
|
{
|
|
name: "init with default prefix",
|
|
prefix: "",
|
|
quiet: false,
|
|
wantOutputText: "bd initialized successfully",
|
|
},
|
|
{
|
|
name: "init with custom prefix",
|
|
prefix: "myproject",
|
|
quiet: false,
|
|
wantOutputText: "myproject-1, myproject-2",
|
|
},
|
|
{
|
|
name: "init with quiet flag",
|
|
prefix: "test",
|
|
quiet: true,
|
|
wantNoOutput: true,
|
|
},
|
|
{
|
|
name: "init with prefix ending in hyphen",
|
|
prefix: "test-",
|
|
quiet: false,
|
|
wantOutputText: "test-1, test-2",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Reset global state
|
|
origDBPath := dbPath
|
|
defer func() { dbPath = origDBPath }()
|
|
dbPath = ""
|
|
|
|
// Reset Cobra command state
|
|
rootCmd.SetArgs([]string{})
|
|
initCmd.Flags().Set("prefix", "")
|
|
initCmd.Flags().Set("quiet", "false")
|
|
|
|
tmpDir := t.TempDir()
|
|
originalWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get working directory: %v", err)
|
|
}
|
|
defer os.Chdir(originalWd)
|
|
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
|
}
|
|
|
|
// Capture output
|
|
var buf bytes.Buffer
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
defer func() {
|
|
os.Stdout = oldStdout
|
|
}()
|
|
|
|
// Build command arguments
|
|
args := []string{"init"}
|
|
if tt.prefix != "" {
|
|
args = append(args, "--prefix", tt.prefix)
|
|
}
|
|
if tt.quiet {
|
|
args = append(args, "--quiet")
|
|
}
|
|
|
|
rootCmd.SetArgs(args)
|
|
|
|
// Run command
|
|
err = rootCmd.Execute()
|
|
|
|
// Restore stdout and read output
|
|
w.Close()
|
|
buf.ReadFrom(r)
|
|
os.Stdout = oldStdout
|
|
output := buf.String()
|
|
|
|
if err != nil {
|
|
t.Fatalf("init command failed: %v", err)
|
|
}
|
|
|
|
// Check output
|
|
if tt.wantNoOutput {
|
|
if output != "" {
|
|
t.Errorf("Expected no output with --quiet, got: %s", output)
|
|
}
|
|
} else if tt.wantOutputText != "" {
|
|
if !strings.Contains(output, tt.wantOutputText) {
|
|
t.Errorf("Expected output to contain %q, got: %s", tt.wantOutputText, output)
|
|
}
|
|
}
|
|
|
|
// Verify .beads directory was created
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if _, err := os.Stat(beadsDir); os.IsNotExist(err) {
|
|
t.Error(".beads directory was not created")
|
|
}
|
|
|
|
// Verify .gitignore was created with proper content
|
|
gitignorePath := filepath.Join(beadsDir, ".gitignore")
|
|
gitignoreContent, err := os.ReadFile(gitignorePath)
|
|
if err != nil {
|
|
t.Errorf(".gitignore file was not created: %v", err)
|
|
} else {
|
|
// Check for essential patterns
|
|
gitignoreStr := string(gitignoreContent)
|
|
expectedPatterns := []string{
|
|
"*.db",
|
|
"*.db-journal",
|
|
"*.db-wal",
|
|
"*.db-shm",
|
|
"daemon.log",
|
|
"daemon.pid",
|
|
"bd.sock",
|
|
"!*.jsonl",
|
|
}
|
|
for _, pattern := range expectedPatterns {
|
|
if !strings.Contains(gitignoreStr, pattern) {
|
|
t.Errorf(".gitignore missing expected pattern: %s", pattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify database was created (always beads.db now)
|
|
dbPath := filepath.Join(beadsDir, "beads.db")
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
t.Errorf("Database file was not created at %s", dbPath)
|
|
}
|
|
|
|
// Verify database has correct prefix
|
|
// Note: This database was already created by init command, just open it
|
|
store, err := openExistingTestDB(t, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx := context.Background()
|
|
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get issue prefix from database: %v", err)
|
|
}
|
|
|
|
expectedPrefix := tt.prefix
|
|
if expectedPrefix == "" {
|
|
expectedPrefix = filepath.Base(tmpDir)
|
|
} else {
|
|
expectedPrefix = strings.TrimRight(expectedPrefix, "-")
|
|
}
|
|
|
|
if prefix != expectedPrefix {
|
|
t.Errorf("Expected prefix %q, got %q", expectedPrefix, prefix)
|
|
}
|
|
|
|
// Verify version metadata was set
|
|
version, err := store.GetMetadata(ctx, "bd_version")
|
|
if err != nil {
|
|
t.Errorf("Failed to get bd_version metadata: %v", err)
|
|
}
|
|
if version == "" {
|
|
t.Error("bd_version metadata was not set")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Note: Error case testing is omitted because the init command calls os.Exit()
|
|
// on errors, which makes it difficult to test in a unit test context.
|
|
|
|
func TestInitAlreadyInitialized(t *testing.T) {
|
|
// Reset global state
|
|
origDBPath := dbPath
|
|
defer func() { dbPath = origDBPath }()
|
|
dbPath = ""
|
|
|
|
tmpDir := t.TempDir()
|
|
originalWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get working directory: %v", err)
|
|
}
|
|
defer os.Chdir(originalWd)
|
|
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
|
}
|
|
|
|
// Initialize once
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("First init failed: %v", err)
|
|
}
|
|
|
|
// Initialize again with same prefix - should succeed (overwrites)
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Second init failed: %v", err)
|
|
}
|
|
|
|
// Verify database still works (always beads.db now)
|
|
dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
|
|
store, err := openExistingTestDB(t, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx := context.Background()
|
|
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get prefix after re-init: %v", err)
|
|
}
|
|
|
|
if prefix != "test" {
|
|
t.Errorf("Expected prefix 'test', got %q", prefix)
|
|
}
|
|
}
|
|
|
|
func TestInitWithCustomDBPath(t *testing.T) {
|
|
// Save original state
|
|
origDBPath := dbPath
|
|
defer func() { dbPath = origDBPath }()
|
|
|
|
tmpDir := t.TempDir()
|
|
customDBDir := filepath.Join(tmpDir, "custom", "location")
|
|
|
|
// Change to a different directory to ensure --db flag is actually used
|
|
workDir := filepath.Join(tmpDir, "workdir")
|
|
if err := os.MkdirAll(workDir, 0750); err != nil {
|
|
t.Fatalf("Failed to create work directory: %v", err)
|
|
}
|
|
|
|
originalWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get working directory: %v", err)
|
|
}
|
|
defer os.Chdir(originalWd)
|
|
|
|
if err := os.Chdir(workDir); err != nil {
|
|
t.Fatalf("Failed to change to work directory: %v", err)
|
|
}
|
|
|
|
customDBPath := filepath.Join(customDBDir, "test.db")
|
|
|
|
// Test with BEADS_DB environment variable (replacing --db flag test)
|
|
t.Run("init with BEADS_DB pointing to custom path", func(t *testing.T) {
|
|
dbPath = "" // Reset global
|
|
os.Setenv("BEADS_DB", customDBPath)
|
|
defer os.Unsetenv("BEADS_DB")
|
|
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "custom", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Init with BEADS_DB failed: %v", err)
|
|
}
|
|
|
|
// Verify database was created at custom location
|
|
if _, err := os.Stat(customDBPath); os.IsNotExist(err) {
|
|
t.Errorf("Database was not created at custom path %s", customDBPath)
|
|
}
|
|
|
|
// Verify database works
|
|
store, err := openExistingTestDB(t, customDBPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx := context.Background()
|
|
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get prefix: %v", err)
|
|
}
|
|
|
|
if prefix != "custom" {
|
|
t.Errorf("Expected prefix 'custom', got %q", prefix)
|
|
}
|
|
|
|
// Verify .beads/ directory was NOT created in work directory
|
|
if _, err := os.Stat(filepath.Join(workDir, ".beads")); err == nil {
|
|
t.Error(".beads/ directory should not be created when using BEADS_DB env var")
|
|
}
|
|
})
|
|
|
|
// Test with BEADS_DB env var
|
|
t.Run("init with BEADS_DB env var", func(t *testing.T) {
|
|
dbPath = "" // Reset global
|
|
envDBPath := filepath.Join(tmpDir, "env", "location", "env.db")
|
|
os.Setenv("BEADS_DB", envDBPath)
|
|
defer os.Unsetenv("BEADS_DB")
|
|
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "envtest", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Init with BEADS_DB failed: %v", err)
|
|
}
|
|
|
|
// Verify database was created at env location
|
|
if _, err := os.Stat(envDBPath); os.IsNotExist(err) {
|
|
t.Errorf("Database was not created at BEADS_DB path %s", envDBPath)
|
|
}
|
|
|
|
// Verify database works
|
|
store, err := openExistingTestDB(t, envDBPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx := context.Background()
|
|
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get prefix: %v", err)
|
|
}
|
|
|
|
if prefix != "envtest" {
|
|
t.Errorf("Expected prefix 'envtest', got %q", prefix)
|
|
}
|
|
})
|
|
|
|
// Test that BEADS_DB path containing ".beads" doesn't create CWD/.beads
|
|
t.Run("init with BEADS_DB path containing .beads", func(t *testing.T) {
|
|
dbPath = "" // Reset global
|
|
// Path contains ".beads" but is outside work directory
|
|
customPath := filepath.Join(tmpDir, "storage", ".beads-backup", "test.db")
|
|
os.Setenv("BEADS_DB", customPath)
|
|
defer os.Unsetenv("BEADS_DB")
|
|
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "beadstest", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Init with custom .beads path failed: %v", err)
|
|
}
|
|
|
|
// Verify database was created at custom location
|
|
if _, err := os.Stat(customPath); os.IsNotExist(err) {
|
|
t.Errorf("Database was not created at custom path %s", customPath)
|
|
}
|
|
|
|
// Verify .beads/ directory was NOT created in work directory
|
|
if _, err := os.Stat(filepath.Join(workDir, ".beads")); err == nil {
|
|
t.Error(".beads/ directory should not be created in CWD when BEADS_DB path contains .beads")
|
|
}
|
|
})
|
|
|
|
// Test with multiple BEADS_DB variations
|
|
t.Run("BEADS_DB with subdirectories", func(t *testing.T) {
|
|
dbPath = "" // Reset global
|
|
envPath := filepath.Join(tmpDir, "env", "subdirs", "test.db")
|
|
|
|
os.Setenv("BEADS_DB", envPath)
|
|
defer os.Unsetenv("BEADS_DB")
|
|
|
|
rootCmd.SetArgs([]string{"init", "--prefix", "envtest2", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Init with BEADS_DB subdirs failed: %v", err)
|
|
}
|
|
|
|
// Verify database was created at env location
|
|
if _, err := os.Stat(envPath); os.IsNotExist(err) {
|
|
t.Errorf("Database was not created at BEADS_DB path %s", envPath)
|
|
}
|
|
|
|
// Verify .beads/ directory was NOT created in work directory
|
|
if _, err := os.Stat(filepath.Join(workDir, ".beads")); err == nil {
|
|
t.Error(".beads/ directory should not be created in CWD when BEADS_DB is set")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInitNoDbMode(t *testing.T) {
|
|
// Reset global state
|
|
origDBPath := dbPath
|
|
origNoDb := noDb
|
|
defer func() {
|
|
dbPath = origDBPath
|
|
noDb = origNoDb
|
|
}()
|
|
dbPath = ""
|
|
noDb = false
|
|
|
|
tmpDir := t.TempDir()
|
|
originalWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get working directory: %v", err)
|
|
}
|
|
defer os.Chdir(originalWd)
|
|
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
|
}
|
|
|
|
// Initialize with --no-db flag
|
|
rootCmd.SetArgs([]string{"init", "--no-db", "--no-daemon", "--prefix", "test", "--quiet"})
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("Init with --no-db failed: %v", err)
|
|
}
|
|
|
|
// Verify issues.jsonl was created
|
|
jsonlPath := filepath.Join(tmpDir, ".beads", "issues.jsonl")
|
|
if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
|
|
t.Error("issues.jsonl was not created in --no-db mode")
|
|
}
|
|
|
|
// Verify config.yaml was created with no-db: true
|
|
configPath := filepath.Join(tmpDir, ".beads", "config.yaml")
|
|
configContent, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read config.yaml: %v", err)
|
|
}
|
|
|
|
configStr := string(configContent)
|
|
if !strings.Contains(configStr, "no-db: true") {
|
|
t.Error("config.yaml should contain 'no-db: true' in --no-db mode")
|
|
}
|
|
|
|
// Verify subsequent command works without --no-db flag
|
|
rootCmd.SetArgs([]string{"create", "test issue", "--json"})
|
|
|
|
// Capture output to verify it worked
|
|
var buf bytes.Buffer
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
err = rootCmd.Execute()
|
|
|
|
// Restore stdout and read output
|
|
w.Close()
|
|
buf.ReadFrom(r)
|
|
os.Stdout = oldStdout
|
|
|
|
if err != nil {
|
|
t.Fatalf("create command failed in no-db mode: %v", err)
|
|
}
|
|
|
|
// Verify issue was written to JSONL
|
|
jsonlContent, err := os.ReadFile(jsonlPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read issues.jsonl: %v", err)
|
|
}
|
|
|
|
if len(jsonlContent) == 0 {
|
|
t.Error("issues.jsonl should not be empty after creating issue")
|
|
}
|
|
|
|
if !strings.Contains(string(jsonlContent), "test issue") {
|
|
t.Error("issues.jsonl should contain the created issue")
|
|
}
|
|
|
|
// Verify no SQLite database was created
|
|
dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
|
|
if _, err := os.Stat(dbPath); err == nil {
|
|
t.Error("SQLite database should not be created in --no-db mode")
|
|
}
|
|
}
|