package board import ( "strings" "testing" "github.com/stretchr/testify/assert" ) var validBoard = `# Task Board — Milestone T1: Production Hardening **Submilestone:** T1 (Error recovery, observability, metrics, reliability) | ID | Scope | Status | Branch | Verification | |----|-------|--------|--------|--------------| | T1.1 | Workflow error recovery | [x] | task/T1.1 | Verify recovery works | | T1.2 | Structured logging | [x] | task/T1.2 | Verify metrics visible | | T1.3 | Timeout tuning | [x] | task/T1.3 | Verify recommendations | | T1.4 | Board validation | [ ] | task/T1.4 | Verify healing works | ` func TestValidateValidBoard(t *testing.T) { bv := NewBoardValidator("") valid := bv.ValidateBoard(validBoard) assert.True(t, valid) assert.False(t, bv.HasErrors()) } func TestValidateInvalidStatus(t *testing.T) { board := strings.ReplaceAll(validBoard, "[x]", "[?]") bv := NewBoardValidator("") valid := bv.ValidateBoard(board) assert.False(t, valid) assert.True(t, bv.HasErrors()) } func TestValidateMissingHeader(t *testing.T) { boardNoHeader := `| ID | Scope | Status | Branch | Verification | |----|-------|--------|--------|--------------| | T1.1 | Task | [x] | branch | verify | ` bv := NewBoardValidator("") valid := bv.ValidateBoard(boardNoHeader) assert.False(t, valid) assert.True(t, bv.HasErrors()) } func TestParseTasks(t *testing.T) { bv := NewBoardValidator("") tasks, err := bv.ParseTasks(validBoard) assert.NoError(t, err) assert.Equal(t, 4, len(tasks)) assert.Equal(t, "T1.1", tasks[0].ID) assert.Equal(t, "[x]", tasks[0].Status) } func TestErrorSummary(t *testing.T) { board := strings.ReplaceAll(validBoard, "[x]", "[?]") bv := NewBoardValidator("") bv.ValidateBoard(board) summary := bv.ErrorSummary() assert.Contains(t, summary, "error") } func TestRepairBoard(t *testing.T) { boardNoHeader := `| T1.1 | Task | [ ] | branch | verify |` bv := NewBoardValidator("") repaired, err := bv.RepairBoard(boardNoHeader) assert.NoError(t, err) assert.Contains(t, repaired, "Task Board") } func TestIsValidTaskID(t *testing.T) { assert.True(t, isValidTaskID("T0")) assert.True(t, isValidTaskID("T1")) assert.True(t, isValidTaskID("T1.1")) assert.True(t, isValidTaskID("T1.8")) assert.False(t, isValidTaskID("Task1")) assert.False(t, isValidTaskID("T")) } func TestDetectDivergence(t *testing.T) { bv := NewBoardValidator("") actualStates := map[string]bool{ "T1.1": true, // Completed in reality "T1.2": true, // Completed in reality "T1.3": true, // Completed in reality "T1.4": false, // Not completed in reality } // Valid board has T1.1, T1.2, T1.3 as [x] and T1.4 as [ ] divergences := bv.DetectDivergence(validBoard, actualStates) // Should be no divergences since they match assert.Equal(t, 0, len(divergences)) } func TestDetectDivergenceWithMismatch(t *testing.T) { bv := NewBoardValidator("") actualStates := map[string]bool{ "T1.1": false, // Should be true but is false "T1.2": true, "T1.3": true, "T1.4": true, // Should be false but is true } divergences := bv.DetectDivergence(validBoard, actualStates) // Should find 2 divergences assert.Greater(t, len(divergences), 0) } func TestHealDivergence(t *testing.T) { bv := NewBoardValidator("") actualStates := map[string]bool{ "T1.1": false, // Different from board "T1.2": true, "T1.3": true, "T1.4": true, // Different from board } healed, changes, err := bv.HealDivergence(validBoard, actualStates) assert.NoError(t, err) assert.Greater(t, len(changes), 0) // Verify healing worked bv2 := NewBoardValidator("") tasks, _ := bv2.ParseTasks(healed) for _, task := range tasks { expected, _ := actualStates[task.ID] if expected { assert.Equal(t, "[x]", task.Status) } else { assert.Equal(t, "[ ]", task.Status) } } } func TestParseTasksEmptyBoard(t *testing.T) { bv := NewBoardValidator("") tasks, err := bv.ParseTasks("") assert.Error(t, err) assert.Equal(t, 0, len(tasks)) } func TestValidateEmptyBoard(t *testing.T) { bv := NewBoardValidator("") valid := bv.ValidateBoard("") assert.False(t, valid) assert.True(t, bv.HasErrors()) } func TestWarningsSummary(t *testing.T) { bv := NewBoardValidator("") bv.validateTaskRow("| ABC | Description | [x] | branch | verify |", 1) summary := bv.WarningsSummary() assert.Contains(t, summary, "Invalid task ID") } func TestMultipleTasks(t *testing.T) { bv := NewBoardValidator("") tasks, err := bv.ParseTasks(validBoard) assert.NoError(t, err) for _, task := range tasks { assert.NotEmpty(t, task.ID) assert.NotEmpty(t, task.Status) } } func TestNormalizeStatus(t *testing.T) { board := strings.ReplaceAll(validBoard, "[x]", "[X]") bv := NewBoardValidator("") _, _ = bv.RepairBoard(board) // Should normalize [X] to [x] }