Repository navigation
Expand file tree
/
Copy pathapproval_storage_test.go
More file actions
95 lines (88 loc) · 3.81 KB
/
Copy pathapproval_storage_test.go
File metadata and controls
95 lines (88 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package main
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"github.com/renezander030/draftcat/internal/config"
statestore "github.com/renezander030/draftcat/internal/state"
)
type storageApprovalChannel struct {
stubApprovalChannel
prompts, sends int
}
func (s *storageApprovalChannel) Send(string) error { s.sends++; return nil }
func (s *storageApprovalChannel) SendForApproval(ctx context.Context, draft string, approvers []int64) (OperatorDecision, error) {
s.prompts++
return s.stubApprovalChannel.SendForApproval(ctx, draft, approvers)
}
func TestPipelineStorageFailureStopsRelease(t *testing.T) {
for _, table := range []string{"pending_approvals", "action_approvals"} {
t.Run(table, func(t *testing.T) {
st, err := statestore.OpenStateStore(filepath.Join(t.TempDir(), "state.db"))
if err != nil {
t.Fatal(err)
}
old := state
state = st
t.Cleanup(func() { state = old; _ = st.Close() })
_, err = st.DB().ExecContext(context.Background(), "CREATE TRIGGER reject_storage BEFORE INSERT ON "+table+" BEGIN SELECT RAISE(ABORT,'storage unavailable'); END")
if err != nil {
t.Fatal(err)
}
cfg := &config.Config{Timeouts: config.TimeoutConfig{OperatorApproval: "1s"}}
ch := &storageApprovalChannel{stubApprovalChannel: stubApprovalChannel{action: "approve", id: 7}}
p := config.PipelineConfig{Name: "pipeline", Steps: []config.StepConfig{{Name: "review", Type: "approval"}, {Name: "notify", Type: "deterministic", Action: "notify"}}}
err = runPipeline(cfg, p, &BudgetTracker{dayStart: time.Now()}, ch, nil, nil)
if err == nil || !strings.Contains(err.Error(), "unavailable") {
t.Fatalf("failure must stop release: %v", err)
}
if ch.sends != 0 {
t.Fatal("pipeline executed after storage failure")
}
if table == "pending_approvals" && ch.prompts != 0 {
t.Fatal("prompt sent before durable pending write")
}
})
}
}
func TestAutomaticApprovalRequiresReceiptStorage(t *testing.T) {
st, err := statestore.OpenStateStore(filepath.Join(t.TempDir(), "state.db"))
if err != nil {
t.Fatal(err)
}
old := state
state = st
t.Cleanup(func() { state = old; _ = st.Close() })
_, err = st.DB().ExecContext(context.Background(), "CREATE TRIGGER reject_receipts BEFORE INSERT ON action_approvals BEGIN SELECT RAISE(ABORT,'storage unavailable'); END")
if err != nil {
t.Fatal(err)
}
cfg := &config.Config{Policy: config.ApprovalPolicy{AutoApprove: []config.AutoApproveRule{{Risk: config.RiskLow}}}}
ch := &storageApprovalChannel{}
p := config.PipelineConfig{Name: "pipeline", Steps: []config.StepConfig{{Name: "review", Type: "approval", Risk: config.RiskLow}, {Name: "notify", Type: "deterministic", Action: "notify"}}}
if err := runPipeline(cfg, p, &BudgetTracker{dayStart: time.Now()}, ch, nil, nil); err == nil {
t.Fatal("automatic approval ignored receipt failure")
}
if ch.sends != 0 {
t.Fatal("automatic approval released next step")
}
}
func TestModelReviewRequiresReceiptStorage(t *testing.T) {
st, err := statestore.OpenStateStore(filepath.Join(t.TempDir(), "state.db"))
if err != nil {
t.Fatal(err)
}
old, oldCh := state, opChan
state, opChan = st, &stubApprovalChannel{action: "approve", id: 7}
t.Cleanup(func() { state, opChan = old, oldCh; _ = st.Close() })
_, err = st.DB().ExecContext(context.Background(), "CREATE TRIGGER reject_receipts BEFORE INSERT ON action_approvals BEGIN SELECT RAISE(ABORT,'storage unavailable'); END")
if err != nil {
t.Fatal(err)
}
cfg := &config.Config{Timeouts: config.TimeoutConfig{OperatorApproval: "1s"}, ModelPolicy: config.ModelPolicyConfig{Rules: []config.ModelPolicyRule{{ID: "review", Phase: "output", Pattern: "claim", Action: "review"}}}}
if err := enforceModelPolicy(context.Background(), cfg, "drafter", "output", "claim"); err == nil {
t.Fatal("model output released without durable approval receipt")
}
}