package board import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestStateTracker(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) // Update a task state err := st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) assert.NoError(t, err) // Retrieve the state state := st.GetTaskState("T1.1") assert.NotNil(t, state) assert.Equal(t, "T1.1", state.TaskID) assert.Equal(t, "completed", state.Status) assert.NotZero(t, state.CompletedAt) } func TestGetAllStates(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st.UpdateTaskState("T1.2", "in_progress", "task/T1.2", nil) st.UpdateTaskState("T1.3", "pending", "task/T1.3", nil) states := st.GetAllStates() assert.Equal(t, 3, len(states)) } func TestGetCompletedTasks(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st.UpdateTaskState("T1.2", "completed", "task/T1.2", nil) st.UpdateTaskState("T1.3", "pending", "task/T1.3", nil) completed := st.GetCompletedTasks() assert.Equal(t, 2, len(completed)) assert.Contains(t, completed, "T1.1") assert.Contains(t, completed, "T1.2") } func TestGetFailedTasks(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) err := assert.AnError st.UpdateTaskState("T1.1", "failed", "task/T1.1", err) st.UpdateTaskState("T1.2", "completed", "task/T1.2", nil) failed := st.GetFailedTasks() assert.Equal(t, 1, len(failed)) assert.Equal(t, "T1.1", failed[0]) } func TestGetPendingTasks(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "pending", "task/T1.1", nil) st.UpdateTaskState("T1.2", "in_progress", "task/T1.2", nil) st.UpdateTaskState("T1.3", "completed", "task/T1.3", nil) pending := st.GetPendingTasks() assert.Equal(t, 2, len(pending)) } func TestAddMetric(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "in_progress", "task/T1.1", nil) err := st.AddMetric("T1.1", "duration_seconds", 42.5) assert.NoError(t, err) state := st.GetTaskState("T1.1") assert.NotNil(t, state.Metrics["duration_seconds"]) assert.Equal(t, 42.5, state.Metrics["duration_seconds"]) } func TestAddMetricNonexistent(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) err := st.AddMetric("nonexistent", "metric", 123) assert.Error(t, err) } func TestPersistence(t *testing.T) { tmpDir := t.TempDir() st1 := NewStateTracker(tmpDir) st1.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st1.UpdateTaskState("T1.2", "pending", "task/T1.2", nil) // Create new instance and load st2 := NewStateTracker(tmpDir) err := st2.Load() assert.NoError(t, err) states := st2.GetAllStates() assert.Equal(t, 2, len(states)) assert.Equal(t, "completed", states["T1.1"].Status) assert.Equal(t, "pending", states["T1.2"].Status) } func TestGetAsCompletionMap(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st.UpdateTaskState("T1.2", "pending", "task/T1.2", nil) st.UpdateTaskState("T1.3", "failed", "task/T1.3", assert.AnError) completion := st.GetAsCompletionMap() assert.Equal(t, true, completion["T1.1"]) assert.Equal(t, false, completion["T1.2"]) assert.Equal(t, false, completion["T1.3"]) } func TestGetLastUpdate(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) before := time.Now() st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) after := time.Now() lastUpdate := st.GetLastUpdate() assert.True(t, lastUpdate.After(before) || lastUpdate.Equal(before)) assert.True(t, lastUpdate.Before(after) || lastUpdate.Equal(after)) } func TestGetStats(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st.UpdateTaskState("T1.2", "completed", "task/T1.2", nil) st.UpdateTaskState("T1.3", "pending", "task/T1.3", nil) st.UpdateTaskState("T1.4", "failed", "task/T1.4", assert.AnError) stats := st.GetStats() assert.Equal(t, 4, stats["total"]) counts := stats["counts"].(map[string]int) assert.Equal(t, 2, counts["completed"]) assert.Equal(t, 1, counts["pending"]) assert.Equal(t, 1, counts["failed"]) } func TestReset(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) st.UpdateTaskState("T1.2", "pending", "task/T1.2", nil) assert.Equal(t, 2, len(st.GetAllStates())) err := st.Reset() assert.NoError(t, err) assert.Equal(t, 0, len(st.GetAllStates())) } func TestTaskStateFields(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) err := assert.AnError st.UpdateTaskState("T1.1", "failed", "task/T1.1", err) state := st.GetTaskState("T1.1") assert.Equal(t, "T1.1", state.TaskID) assert.Equal(t, "failed", state.Status) assert.Equal(t, "task/T1.1", state.Branch) assert.NotEmpty(t, state.Error) assert.NotZero(t, state.FailedAt) } func TestLoadNonexistentState(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) // Should not error when file doesn't exist err := st.Load() assert.NoError(t, err) assert.Equal(t, 0, len(st.GetAllStates())) } func TestMultipleStateUpdates(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) // Task progresses through states st.UpdateTaskState("T1.1", "pending", "task/T1.1", nil) state1 := st.GetTaskState("T1.1") time.Sleep(10 * time.Millisecond) st.UpdateTaskState("T1.1", "in_progress", "task/T1.1", nil) state2 := st.GetTaskState("T1.1") // Status should be updated assert.Equal(t, "pending", state1.Status) assert.Equal(t, "in_progress", state2.Status) } func TestStateFileLayout(t *testing.T) { tmpDir := t.TempDir() st := NewStateTracker(tmpDir) st.UpdateTaskState("T1.1", "completed", "task/T1.1", nil) // Verify state was tracked state := st.GetTaskState("T1.1") assert.NotNil(t, state) }