test: improve internal/daemon test coverage to 60%
Adds fast unit tests for previously uncovered functions in the daemon package: - checkDaemonErrorFile: tests reading daemon error files - StopDaemon: tests error handling for non-running daemons - KillAllDaemons: tests empty lists and non-alive daemons - FindDaemonByWorkspace: tests not found case - discoverDaemon: tests missing socket scenario - CleanupStaleSockets: tests edge cases (already removed, alive daemon) - Registry: tests corrupted file handling and unregistering non-existent entries Coverage improved from 22.5% to 60.0% with only fast tests (<1s runtime). All new tests work in -short mode and don't start actual daemons. Fixes bd-3f80d9e0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -261,3 +261,135 @@ func TestDiscoverDaemons_Legacy(t *testing.T) {
|
|||||||
t.Errorf("Wrong workspace path: expected %s, got %s", tmpDir, daemon.WorkspacePath)
|
t.Errorf("Wrong workspace path: expected %s, got %s", tmpDir, daemon.WorkspacePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckDaemonErrorFile(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||||
|
os.MkdirAll(beadsDir, 0755)
|
||||||
|
socketPath := filepath.Join(beadsDir, "bd.sock")
|
||||||
|
|
||||||
|
// Test 1: No error file exists
|
||||||
|
errMsg := checkDaemonErrorFile(socketPath)
|
||||||
|
if errMsg != "" {
|
||||||
|
t.Errorf("Expected empty error message, got: %s", errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Error file exists with content
|
||||||
|
errorFilePath := filepath.Join(beadsDir, "daemon-error")
|
||||||
|
expectedError := "failed to start: database locked"
|
||||||
|
os.WriteFile(errorFilePath, []byte(expectedError), 0644)
|
||||||
|
|
||||||
|
errMsg = checkDaemonErrorFile(socketPath)
|
||||||
|
if errMsg != expectedError {
|
||||||
|
t.Errorf("Expected error message %q, got %q", expectedError, errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopDaemon_NotAlive(t *testing.T) {
|
||||||
|
daemon := DaemonInfo{
|
||||||
|
Alive: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := StopDaemon(daemon)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when stopping non-alive daemon")
|
||||||
|
}
|
||||||
|
if err.Error() != "daemon is not running" {
|
||||||
|
t.Errorf("Unexpected error message: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKillAllDaemons_Empty(t *testing.T) {
|
||||||
|
results := KillAllDaemons([]DaemonInfo{}, false)
|
||||||
|
if results.Stopped != 0 || results.Failed != 0 {
|
||||||
|
t.Errorf("Expected 0 stopped and 0 failed, got %d stopped and %d failed", results.Stopped, results.Failed)
|
||||||
|
}
|
||||||
|
if len(results.Failures) != 0 {
|
||||||
|
t.Errorf("Expected empty failures list, got %d failures", len(results.Failures))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKillAllDaemons_NotAlive(t *testing.T) {
|
||||||
|
daemons := []DaemonInfo{
|
||||||
|
{Alive: false, WorkspacePath: "/test", PID: 12345},
|
||||||
|
}
|
||||||
|
|
||||||
|
results := KillAllDaemons(daemons, false)
|
||||||
|
if results.Stopped != 0 || results.Failed != 0 {
|
||||||
|
t.Errorf("Expected 0 stopped and 0 failed for dead daemon, got %d stopped and %d failed", results.Stopped, results.Failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindDaemonByWorkspace_NotFound(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Try to find daemon in directory without any daemon
|
||||||
|
daemon, err := FindDaemonByWorkspace(tmpDir)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when daemon not found")
|
||||||
|
}
|
||||||
|
if daemon != nil {
|
||||||
|
t.Error("Expected nil daemon when not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoverDaemon_SocketMissing(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
socketPath := filepath.Join(tmpDir, "nonexistent.sock")
|
||||||
|
|
||||||
|
// Try to discover daemon on non-existent socket
|
||||||
|
daemon := discoverDaemon(socketPath)
|
||||||
|
if daemon.Alive {
|
||||||
|
t.Error("Expected daemon to not be alive for missing socket")
|
||||||
|
}
|
||||||
|
if daemon.SocketPath != socketPath {
|
||||||
|
t.Errorf("Expected socket path %s, got %s", socketPath, daemon.SocketPath)
|
||||||
|
}
|
||||||
|
if daemon.Error == "" {
|
||||||
|
t.Error("Expected error message when daemon not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanupStaleSockets_AlreadyRemoved(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Create stale daemon with non-existent socket
|
||||||
|
stalePath := filepath.Join(tmpDir, "nonexistent.sock")
|
||||||
|
|
||||||
|
daemons := []DaemonInfo{
|
||||||
|
{
|
||||||
|
SocketPath: stalePath,
|
||||||
|
Alive: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should succeed even if socket doesn't exist
|
||||||
|
cleaned, err := CleanupStaleSockets(daemons)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cleanup failed: %v", err)
|
||||||
|
}
|
||||||
|
if cleaned != 0 {
|
||||||
|
t.Errorf("expected 0 cleaned (socket didn't exist), got %d", cleaned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanupStaleSockets_AliveDaemon(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
socketPath := filepath.Join(tmpDir, "alive.sock")
|
||||||
|
|
||||||
|
daemons := []DaemonInfo{
|
||||||
|
{
|
||||||
|
SocketPath: socketPath,
|
||||||
|
Alive: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not remove socket for alive daemon
|
||||||
|
cleaned, err := CleanupStaleSockets(daemons)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cleanup failed: %v", err)
|
||||||
|
}
|
||||||
|
if cleaned != 0 {
|
||||||
|
t.Errorf("expected 0 cleaned (daemon alive), got %d", cleaned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func TestRegistryStaleCleanup(t *testing.T) {
|
|||||||
func TestRegistryEmptyArrayNotNull(t *testing.T) {
|
func TestRegistryEmptyArrayNotNull(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
registryPath := filepath.Join(tmpDir, ".beads", "registry.json")
|
registryPath := filepath.Join(tmpDir, ".beads", "registry.json")
|
||||||
|
|
||||||
// Override the registry path for testing (platform-specific)
|
// Override the registry path for testing (platform-specific)
|
||||||
homeEnv := "HOME"
|
homeEnv := "HOME"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@@ -218,3 +218,61 @@ func TestRegistryEmptyArrayNotNull(t *testing.T) {
|
|||||||
t.Errorf("Expected empty array [], got: %s", content)
|
t.Errorf("Expected empty array [], got: %s", content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegistryCorruptedFile(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
registryPath := filepath.Join(tmpDir, ".beads", "registry.json")
|
||||||
|
|
||||||
|
homeEnv := "HOME"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
homeEnv = "USERPROFILE"
|
||||||
|
}
|
||||||
|
oldHome := os.Getenv(homeEnv)
|
||||||
|
os.Setenv(homeEnv, tmpDir)
|
||||||
|
defer os.Setenv(homeEnv, oldHome)
|
||||||
|
|
||||||
|
registry, err := NewRegistry()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a corrupted registry file
|
||||||
|
os.MkdirAll(filepath.Dir(registryPath), 0755)
|
||||||
|
os.WriteFile(registryPath, []byte("invalid json{{{"), 0644)
|
||||||
|
|
||||||
|
// Reading should return an error
|
||||||
|
entries, err := registry.readEntries()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when reading corrupted registry")
|
||||||
|
}
|
||||||
|
if entries != nil {
|
||||||
|
t.Errorf("Expected nil entries on error, got %v", entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistryUnregisterNonExistent(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
oldHome := os.Getenv("HOME")
|
||||||
|
os.Setenv("HOME", tmpDir)
|
||||||
|
defer os.Setenv("HOME", oldHome)
|
||||||
|
|
||||||
|
registry, err := NewRegistry()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister from empty registry should succeed
|
||||||
|
err = registry.Unregister("/nonexistent/workspace", 99999)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unregister should succeed even if entry doesn't exist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify registry is still empty
|
||||||
|
rawEntries, err := registry.readEntries()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read entries: %v", err)
|
||||||
|
}
|
||||||
|
if len(rawEntries) != 0 {
|
||||||
|
t.Errorf("Expected empty registry, got %d entries", len(rawEntries))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user