183 lines
4.7 KiB
Go
183 lines
4.7 KiB
Go
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]
|
||
|
|
}
|