package daemon
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestSchedulerStateManager_SaveTask(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Test saving a task
task := NewScheduledTask(
"test-task-1", "test-session", "test message", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
err := sm.SaveTask(task)
if err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Verify the state file was created
statePath := filepath.Join(tmpDir, SchedulerStateDir, SchedulerStateFile)
if _, err := os.Stat(statePath); os.IsNotExist(err) {
t.Error("State file should exist after save")
}
}
func TestSchedulerStateManager_SaveTaskEmptyPath(t *testing.T) {
sm := NewSchedulerStateManager()
task := NewScheduledTask(
"test-task", "", "test", "",
time.Time{}, time.Time{}, TaskStatusPending,
)
err := sm.SaveTask(task)
if err == nil {
t.Error("Expected error for empty project path")
}
}
func TestSchedulerStateManager_LoadTasks(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Load from non-existent path should return empty
tasks, err := sm.LoadTasks(tmpDir)
if err != nil {
t.Fatalf("LoadTasks failed: %v", err)
}
if len(tasks) != 0 {
t.Errorf("Expected 0 tasks, got %d", len(tasks))
}
// Save a task first
task := NewScheduledTask(
"load-test-1", "session-1", "test load", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Now load should return the task
tasks, err = sm.LoadTasks(tmpDir)
if err != nil {
t.Fatalf("LoadTasks failed: %v", err)
}
if len(tasks) != 1 {
t.Errorf("Expected 1 task, got %d", len(tasks))
}
if tasks[0].ID != "load-test-1" {
t.Errorf("Expected task ID load-test-1, got %s", tasks[0].ID)
}
}
func TestSchedulerStateManager_RemoveTask(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Save a task first
task := NewScheduledTask(
"remove-test-1", "session-1", "to be removed", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Remove the task
err := sm.RemoveTask("remove-test-1", tmpDir)
if err != nil {
t.Fatalf("RemoveTask failed: %v", err)
}
// Verify task was removed
tasks, err := sm.LoadTasks(tmpDir)
if err != nil {
t.Fatalf("LoadTasks failed: %v", err)
}
if len(tasks) != 0 {
t.Errorf("Expected 0 tasks after remove, got %d", len(tasks))
}
}
func TestSchedulerStateManager_RemoveTaskEmptyPath(t *testing.T) {
sm := NewSchedulerStateManager()
err := sm.RemoveTask("task-id", "")
if err == nil {
t.Error("Expected error for empty project path")
}
}
func TestSchedulerStateManager_RemoveTaskNonExistent(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Remove from non-existent state should succeed (no-op)
err := sm.RemoveTask("nonexistent-task", tmpDir)
if err != nil {
t.Fatalf("RemoveTask from non-existent path should not error: %v", err)
}
}
func TestSchedulerStateManager_ScanForProjects(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Create a state directory with a state file
stateDir := filepath.Join(tmpDir, SchedulerStateDir)
if err := os.MkdirAll(stateDir, 0755); err != nil {
t.Fatalf("Failed to create state dir: %v", err)
}
// Create an empty state file
statePath := filepath.Join(stateDir, SchedulerStateFile)
if err := os.WriteFile(statePath, []byte(`{"version":1,"tasks":[]}`), 0644); err != nil {
t.Fatalf("Failed to create state file: %v", err)
}
// Scan should discover this project
sm.ScanForProjects([]string{tmpDir})
// List projects should return the discovered project
projects := sm.ListProjectsWithTasks()
if len(projects) != 1 {
t.Errorf("Expected 1 project, got %d", len(projects))
}
if len(projects) > 0 && projects[0] != tmpDir {
t.Errorf("Expected project %s, got %s", tmpDir, projects[0])
}
}
func TestSchedulerStateManager_RegisterProject(t *testing.T) {
sm := NewSchedulerStateManager()
// Register a project
sm.RegisterProject("/test/project")
// Should be in the list
projects := sm.ListProjectsWithTasks()
found := false
for _, p := range projects {
if p == "/test/project" {
found = true
break
}
}
if !found {
t.Error("Expected registered project to be in list")
}
}
func TestSchedulerStateManager_ListProjectsWithTasks(t *testing.T) {
sm := NewSchedulerStateManager()
// Initially empty
projects := sm.ListProjectsWithTasks()
if len(projects) != 0 {
t.Errorf("Expected 0 projects, got %d", len(projects))
}
// Register some projects
sm.RegisterProject("/project1")
sm.RegisterProject("/project2")
projects = sm.ListProjectsWithTasks()
if len(projects) != 2 {
t.Errorf("Expected 2 projects, got %d", len(projects))
}
}
func TestSchedulerStateManager_ClearProject(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Save a task first
task := NewScheduledTask(
"clear-test-1", "session-1", "to be cleared", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Clear the project
err := sm.ClearProject(tmpDir)
if err != nil {
t.Fatalf("ClearProject failed: %v", err)
}
// State file should be gone
statePath := filepath.Join(tmpDir, SchedulerStateDir, SchedulerStateFile)
if _, err := os.Stat(statePath); !os.IsNotExist(err) {
t.Error("State file should not exist after clear")
}
// Project should be removed from list
projects := sm.ListProjectsWithTasks()
for _, p := range projects {
if p == tmpDir {
t.Error("Project should not be in list after clear")
}
}
}
func TestSchedulerStateManager_LoadAllTasks(t *testing.T) {
tmpDir1 := t.TempDir()
tmpDir2 := t.TempDir()
sm := NewSchedulerStateManager()
// Save tasks in two projects
task1 := NewScheduledTask(
"all-test-1", "session-1", "task 1", tmpDir1,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task1); err != nil {
t.Fatalf("SaveTask 1 failed: %v", err)
}
task2 := NewScheduledTask(
"all-test-2", "session-2", "task 2", tmpDir2,
time.Now().Add(2*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task2); err != nil {
t.Fatalf("SaveTask 2 failed: %v", err)
}
// Load all tasks
allTasks := sm.LoadAllTasks()
if len(allTasks) != 2 {
t.Errorf("Expected 2 tasks, got %d", len(allTasks))
}
}
func TestSchedulerStateManager_UpdateExistingTask(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Save a task
task := NewScheduledTask(
"update-test-1", "session-1", "original message", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Update the task
task.Message = "updated message"
task.SetStatus(TaskStatusDelivered)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask (update) failed: %v", err)
}
// Load and verify update
tasks, err := sm.LoadTasks(tmpDir)
if err != nil {
t.Fatalf("LoadTasks failed: %v", err)
}
if len(tasks) != 1 {
t.Errorf("Expected 1 task (should update, not add), got %d", len(tasks))
}
if tasks[0].Message != "updated message" {
t.Errorf("Expected updated message, got %s", tasks[0].Message)
}
if tasks[0].Status() != TaskStatusDelivered {
t.Errorf("Expected status delivered, got %s", tasks[0].Status())
}
}
func TestSchedulerStateManager_ClearNonExistentProject(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Clear a project that doesn't have a state file
err := sm.ClearProject(tmpDir)
if err != nil {
t.Fatalf("ClearProject should succeed for non-existent state: %v", err)
}
}
func TestSchedulerStateManager_RemoveLastTask(t *testing.T) {
tmpDir := t.TempDir()
sm := NewSchedulerStateManager()
// Save one task
task := NewScheduledTask(
"last-task", "session-1", "only task", tmpDir,
time.Now().Add(1*time.Hour), time.Now(), TaskStatusPending,
)
if err := sm.SaveTask(task); err != nil {
t.Fatalf("SaveTask failed: %v", err)
}
// Verify state file exists
statePath := filepath.Join(tmpDir, SchedulerStateDir, SchedulerStateFile)
if _, err := os.Stat(statePath); os.IsNotExist(err) {
t.Fatal("State file should exist after save")
}
// Remove the only task
if err := sm.RemoveTask("last-task", tmpDir); err != nil {
t.Fatalf("RemoveTask failed: %v", err)
}
// State file should be removed since no tasks remain
if _, err := os.Stat(statePath); !os.IsNotExist(err) {
t.Error("State file should be removed when last task is deleted")
}
}