go-crypt/trust/approval_test.go
Snider ee58e790a0
Some checks failed
Security Scan / security (push) Failing after 11s
Test / test (push) Failing after 29s
feat: modernise to Go 1.26 iterators and stdlib helpers
Add iter.Seq iterators for trust registry (ListSeq), audit log
(EventsSeq, QuerySeq), and approval store (PendingSeq). Use
slices.DeleteFunc in session store, slices.SortFunc in testcmd,
range-over-int in benchmarks.

Co-Authored-By: Gemini <noreply@google.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 05:47:13 +00:00

312 lines
7.6 KiB
Go

package trust
import (
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- ApprovalStatus ---
func TestApprovalStatusString_Good(t *testing.T) {
assert.Equal(t, "pending", ApprovalPending.String())
assert.Equal(t, "approved", ApprovalApproved.String())
assert.Equal(t, "denied", ApprovalDenied.String())
}
func TestApprovalStatusString_Bad_Unknown(t *testing.T) {
assert.Contains(t, ApprovalStatus(99).String(), "unknown")
}
// --- Submit ---
func TestApprovalSubmit_Good(t *testing.T) {
q := NewApprovalQueue()
id, err := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, err)
assert.NotEmpty(t, id)
assert.Equal(t, 1, q.Len())
}
func TestApprovalSubmit_Good_MultipleRequests(t *testing.T) {
q := NewApprovalQueue()
id1, err := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, err)
id2, err := q.Submit("Hypnos", CapMergePR, "host-uk/docs")
require.NoError(t, err)
assert.NotEqual(t, id1, id2, "each request should get a unique ID")
assert.Equal(t, 2, q.Len())
}
func TestApprovalSubmit_Good_EmptyRepo(t *testing.T) {
q := NewApprovalQueue()
id, err := q.Submit("Clotho", CapMergePR, "")
require.NoError(t, err)
assert.NotEmpty(t, id)
req := q.Get(id)
require.NotNil(t, req)
assert.Empty(t, req.Repo)
}
func TestApprovalSubmit_Bad_EmptyAgent(t *testing.T) {
q := NewApprovalQueue()
_, err := q.Submit("", CapMergePR, "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "agent name is required")
}
func TestApprovalSubmit_Bad_EmptyCapability(t *testing.T) {
q := NewApprovalQueue()
_, err := q.Submit("Clotho", "", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "capability is required")
}
// --- Get ---
func TestApprovalGet_Good(t *testing.T) {
q := NewApprovalQueue()
id, err := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, err)
req := q.Get(id)
require.NotNil(t, req)
assert.Equal(t, id, req.ID)
assert.Equal(t, "Clotho", req.Agent)
assert.Equal(t, CapMergePR, req.Cap)
assert.Equal(t, "host-uk/core", req.Repo)
assert.Equal(t, ApprovalPending, req.Status)
assert.False(t, req.RequestedAt.IsZero())
assert.True(t, req.ReviewedAt.IsZero())
}
func TestApprovalGet_Good_ReturnsSnapshot(t *testing.T) {
q := NewApprovalQueue()
id, err := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, err)
req := q.Get(id)
require.NotNil(t, req)
req.Status = ApprovalApproved // Mutate the copy
// Original should be unchanged.
original := q.Get(id)
assert.Equal(t, ApprovalPending, original.Status)
}
func TestApprovalGet_Bad_NotFound(t *testing.T) {
q := NewApprovalQueue()
assert.Nil(t, q.Get("nonexistent"))
}
// --- Approve ---
func TestApprovalApprove_Good(t *testing.T) {
q := NewApprovalQueue()
id, _ := q.Submit("Clotho", CapMergePR, "host-uk/core")
err := q.Approve(id, "admin", "looks good")
require.NoError(t, err)
req := q.Get(id)
require.NotNil(t, req)
assert.Equal(t, ApprovalApproved, req.Status)
assert.Equal(t, "admin", req.ReviewedBy)
assert.Equal(t, "looks good", req.Reason)
assert.False(t, req.ReviewedAt.IsZero())
}
func TestApprovalApprove_Bad_NotFound(t *testing.T) {
q := NewApprovalQueue()
err := q.Approve("nonexistent", "admin", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
func TestApprovalApprove_Bad_AlreadyApproved(t *testing.T) {
q := NewApprovalQueue()
id, _ := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, q.Approve(id, "admin", ""))
err := q.Approve(id, "admin2", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "already approved")
}
func TestApprovalApprove_Bad_AlreadyDenied(t *testing.T) {
q := NewApprovalQueue()
id, _ := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, q.Deny(id, "admin", "nope"))
err := q.Approve(id, "admin2", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "already denied")
}
// --- Deny ---
func TestApprovalDeny_Good(t *testing.T) {
q := NewApprovalQueue()
id, _ := q.Submit("Clotho", CapMergePR, "host-uk/core")
err := q.Deny(id, "admin", "not appropriate")
require.NoError(t, err)
req := q.Get(id)
require.NotNil(t, req)
assert.Equal(t, ApprovalDenied, req.Status)
assert.Equal(t, "admin", req.ReviewedBy)
assert.Equal(t, "not appropriate", req.Reason)
assert.False(t, req.ReviewedAt.IsZero())
}
func TestApprovalDeny_Bad_NotFound(t *testing.T) {
q := NewApprovalQueue()
err := q.Deny("nonexistent", "admin", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
func TestApprovalDeny_Bad_AlreadyDenied(t *testing.T) {
q := NewApprovalQueue()
id, _ := q.Submit("Clotho", CapMergePR, "host-uk/core")
require.NoError(t, q.Deny(id, "admin", ""))
err := q.Deny(id, "admin2", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "already denied")
}
// --- Pending ---
func TestApprovalPending_Good(t *testing.T) {
q := NewApprovalQueue()
q.Submit("Clotho", CapMergePR, "host-uk/core")
q.Submit("Hypnos", CapMergePR, "host-uk/docs")
id3, _ := q.Submit("Darbs", CapMergePR, "host-uk/tools")
q.Approve(id3, "admin", "")
pending := q.Pending()
assert.Len(t, pending, 2)
}
func TestApprovalPending_Good_Empty(t *testing.T) {
q := NewApprovalQueue()
assert.Empty(t, q.Pending())
}
func TestApprovalPendingSeq_Good(t *testing.T) {
q := NewApprovalQueue()
q.Submit("Clotho", CapMergePR, "host-uk/core")
q.Submit("Hypnos", CapMergePR, "host-uk/docs")
id3, _ := q.Submit("Darbs", CapMergePR, "host-uk/tools")
q.Approve(id3, "admin", "")
count := 0
for req := range q.PendingSeq() {
assert.Equal(t, ApprovalPending, req.Status)
count++
}
assert.Equal(t, 2, count)
}
// --- Concurrent operations ---
func TestApprovalConcurrent_Good(t *testing.T) {
q := NewApprovalQueue()
const n = 10
var wg sync.WaitGroup
wg.Add(n)
ids := make([]string, n)
var mu sync.Mutex
// Submit concurrently
for i := range n {
go func(idx int) {
defer wg.Done()
id, err := q.Submit(
fmt.Sprintf("agent-%d", idx),
CapMergePR,
"host-uk/core",
)
assert.NoError(t, err)
mu.Lock()
ids[idx] = id
mu.Unlock()
}(i)
}
wg.Wait()
assert.Equal(t, n, q.Len())
// Approve/deny concurrently
wg.Add(n)
for i := range n {
go func(idx int) {
defer wg.Done()
mu.Lock()
id := ids[idx]
mu.Unlock()
if idx%2 == 0 {
_ = q.Approve(id, "admin", "ok")
} else {
_ = q.Deny(id, "admin", "no")
}
}(i)
}
wg.Wait()
assert.Empty(t, q.Pending())
}
// --- Integration: PolicyEngine + ApprovalQueue ---
func TestApprovalWorkflow_Good_EndToEnd(t *testing.T) {
pe := newTestEngine(t)
q := NewApprovalQueue()
// Clotho (Tier 2) tries to merge a PR — should get NeedsApproval
result := pe.Evaluate("Clotho", CapMergePR, "host-uk/core")
assert.Equal(t, NeedsApproval, result.Decision)
// Submit an approval request
id, err := q.Submit(result.Agent, result.Cap, "host-uk/core")
require.NoError(t, err)
// Admin approves
err = q.Approve(id, "Virgil", "PR reviewed, merge approved")
require.NoError(t, err)
// Verify approval
req := q.Get(id)
require.NotNil(t, req)
assert.Equal(t, ApprovalApproved, req.Status)
assert.Equal(t, "Virgil", req.ReviewedBy)
}
func TestApprovalWorkflow_Good_DenyEndToEnd(t *testing.T) {
pe := newTestEngine(t)
q := NewApprovalQueue()
result := pe.Evaluate("Clotho", CapMergePR, "host-uk/core")
assert.Equal(t, NeedsApproval, result.Decision)
id, err := q.Submit(result.Agent, result.Cap, "host-uk/core")
require.NoError(t, err)
err = q.Deny(id, "Virgil", "needs more review")
require.NoError(t, err)
req := q.Get(id)
require.NotNil(t, req)
assert.Equal(t, ApprovalDenied, req.Status)
}