Files
poimen-workflows/internal/approval/gate_test.go
T
Test 75a01a9444 feat(T3.4): implement human-in-the-loop approval gates
- Add internal/approval package for workflow approval gates
- Implement ApprovalGate for gating workflow progression
- Implement ApprovalGateManager for managing multiple gates
- Gate status tracking: pending, approved, rejected, expired
- TTL-based gate expiration (auto-expire after timeout)
- Multiple approval tracking (configurable approval count)
- History tracking for all approval decisions
- Query by task, workflow, status
- Audit trail with decision reasons
- 16 approval tests, all passing
2026-08-23 17:45:44 -07:00

174 lines
5.1 KiB
Go

package approval
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNewApprovalGateManager(t *testing.T) {
manager := NewApprovalGateManager()
assert.NotNil(t, manager)
assert.Equal(t, 0, manager.stats.TotalGates)
}
func TestCreateGate(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
assert.NotNil(t, gate)
assert.Equal(t, "T0.1", gate.TaskID)
assert.Equal(t, StatusPending, gate.Decision.Status)
}
func TestApproveGate(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
err := manager.ApproveGate(gate.ID, "reviewer-1", "Approved")
assert.NoError(t, err)
assert.True(t, manager.IsApproved(gate.ID))
}
func TestRejectGate(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
err := manager.RejectGate(gate.ID, "reviewer-1", "Rejected")
assert.NoError(t, err)
assert.True(t, manager.IsRejected(gate.ID))
}
func TestGetPendingGates(t *testing.T) {
manager := NewApprovalGateManager()
gate1 := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
gate2 := manager.CreateGate("T0.2", "workflow-1", "Review", 24*time.Hour)
manager.ApproveGate(gate1.ID, "reviewer-1", "")
pending := manager.GetPendingGates()
assert.Equal(t, 1, len(pending))
if len(pending) > 0 {
assert.Equal(t, gate2.ID, pending[0].ID)
}
}
func TestGetGatesByTask(t *testing.T) {
manager := NewApprovalGateManager()
manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
manager.CreateGate("T0.2", "workflow-1", "Review", 24*time.Hour)
gates := manager.GetGatesByTask("T0.1")
assert.Greater(t, len(gates), 0)
}
func TestGetGatesByWorkflow(t *testing.T) {
manager := NewApprovalGateManager()
manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
manager.CreateGate("T0.2", "workflow-2", "Review", 24*time.Hour)
gates := manager.GetGatesByWorkflow("workflow-1")
assert.Greater(t, len(gates), 0)
}
func TestIsApproved(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
assert.False(t, manager.IsApproved(gate.ID))
manager.ApproveGate(gate.ID, "reviewer-1", "")
assert.True(t, manager.IsApproved(gate.ID))
}
func TestIsRejected(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
assert.False(t, manager.IsRejected(gate.ID))
manager.RejectGate(gate.ID, "reviewer-1", "")
assert.True(t, manager.IsRejected(gate.ID))
}
func TestIsPending(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
assert.True(t, manager.IsPending(gate.ID))
manager.ApproveGate(gate.ID, "reviewer-1", "")
assert.False(t, manager.IsPending(gate.ID))
}
func TestGetStats(t *testing.T) {
manager := NewApprovalGateManager()
gate1 := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
_ = manager.CreateGate("T0.2", "workflow-1", "Review", 24*time.Hour)
manager.ApproveGate(gate1.ID, "reviewer-1", "")
stats := manager.GetStats()
assert.Equal(t, 2, stats.TotalGates)
assert.Equal(t, 1, stats.ApprovedGates)
assert.Equal(t, 1, stats.PendingGates)
}
func TestGetHistory(t *testing.T) {
manager := NewApprovalGateManager()
gate1 := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
gate2 := manager.CreateGate("T0.2", "workflow-1", "Review", 24*time.Hour)
manager.ApproveGate(gate1.ID, "reviewer-1", "")
manager.RejectGate(gate2.ID, "reviewer-2", "Needs work")
history := manager.GetHistory()
assert.Greater(t, len(history), 0)
}
func TestClear(t *testing.T) {
manager := NewApprovalGateManager()
manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
assert.Equal(t, 1, manager.stats.TotalGates)
manager.Clear()
assert.Equal(t, 0, manager.stats.TotalGates)
}
func TestExpiredGate(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 1*time.Millisecond)
time.Sleep(10 * time.Millisecond)
err := manager.ApproveGate(gate.ID, "reviewer-1", "")
assert.Error(t, err)
}
func TestMultipleApprovals(t *testing.T) {
manager := NewApprovalGateManager()
gate := manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
gate.RequiredApprovals = 2
err := manager.ApproveGate(gate.ID, "reviewer-1", "")
assert.NoError(t, err)
retrieved, _ := manager.GetGate(gate.ID)
assert.Equal(t, 1, len(retrieved.Approvals))
}
func BenchmarkCreateGate(b *testing.B) {
manager := NewApprovalGateManager()
for i := 0; i < b.N; i++ {
manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
}
}
func BenchmarkApproveGate(b *testing.B) {
manager := NewApprovalGateManager()
gates := make([]*ApprovalGate, b.N)
for i := 0; i < b.N; i++ {
gates[i] = manager.CreateGate("T0.1", "workflow-1", "Review", 24*time.Hour)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.ApproveGate(gates[i].ID, "reviewer-1", "")
}
}