返回 DeepSeek-Reasonix
engine_test.go
根目录 / internal / runtimepolicy / engine_test.go
1 package runtimepolicy
2
3 import (
4 "encoding/json"
5 "sync"
6 "testing"
7
8 "reasonix/internal/evidence"
9 )
10
11 func TestMergeDecisionsIsMonotonic(t *testing.T) {
12 allow := GuardDecision{Action: GuardAllow}
13 ask := GuardDecision{Action: GuardAsk, Message: "ask"}
14 deny := GuardDecision{Action: GuardDeny, Message: "deny"}
15 got := MergeDecisions(allow, deny, ask)
16 if got.Action != GuardDeny || got.Message != "deny" {
17 t.Fatalf("merge = %+v", got)
18 }
19 got = MergeDecisions(ask, allow)
20 if got.Action != GuardAsk {
21 t.Fatalf("ask must outrank allow: %+v", got)
22 }
23 }
24
25 func TestPlanGuardBeatsYOLO(t *testing.T) {
26 ctx := CallContext{
27 PlanReadOnly: true,
28 Profile: evidence.EffectProfile{Known: true, WorkspaceWrite: true},
29 }
30 if (PlanGuard{}).BeforeTool(ctx).Action != GuardDeny {
31 t.Fatal("plan writes must deny even when permission would allow")
32 }
33 }
34
35 func TestOpaqueWriterAskOrDeny(t *testing.T) {
36 ctx := CallContext{Profile: evidence.EffectProfile{WorkspaceWrite: true, Reason: evidence.ReasonOpaqueWriter}}
37 if (OpaqueWriterGuard{}).BeforeTool(ctx).Action != GuardDeny {
38 t.Fatal("headless unknown writer must deny")
39 }
40 ctx.Interactive = true
41 if (OpaqueWriterGuard{}).BeforeTool(ctx).Action != GuardAsk {
42 t.Fatal("interactive unknown writer must ask")
43 }
44 }
45
46 func TestConstraintNoWrite(t *testing.T) {
47 g := ConstraintGuard{Constraints: Constraints{ForbidMutation: true}}
48 ctx := CallContext{Profile: evidence.EffectProfile{Known: true, WorkspaceWrite: true}}
49 if g.BeforeTool(ctx).Action != GuardDeny {
50 t.Fatal("explicit no-write must deny")
51 }
52 }
53
54 func TestConcurrentReadOnlyBeforeTool(t *testing.T) {
55 e := NewEngine(Constraints{})
56 ctx := CallContext{Profile: evidence.EffectProfile{Known: true, ReadOnly: true}}
57 start := make(chan struct{})
58 var wg sync.WaitGroup
59 wg.Add(2)
60 for range 2 {
61 go func() {
62 defer wg.Done()
63 <-start
64 if e.BeforeTool(ctx).Action == GuardDeny {
65 t.Error("read-only overlap must not deny")
66 }
67 }()
68 }
69 close(start)
70 wg.Wait()
71 }
72
73 func TestFailedWriterBarrier(t *testing.T) {
74 g := MutationDependencyGuard{Blocked: true}
75 ctx := CallContext{Profile: evidence.EffectProfile{Known: true, WorkspaceWrite: true}}
76 if g.BeforeTool(ctx).Action != GuardDeny {
77 t.Fatal("failed writer must block later mutations")
78 }
79 read := CallContext{Profile: evidence.EffectProfile{Known: true, ReadOnly: true}}
80 if g.BeforeTool(read).Action != GuardAbstain {
81 t.Fatal("read-only diagnosis may still run after a failed writer")
82 }
83 }
84
85 // Risk, file count, and verification requests do not become prerequisites.
86 func TestWritesNeedNoQualityContract(t *testing.T) {
87 for _, prompt := range []string{"fix the code", "完整验证并交付", "run all tests"} {
88 e := NewEngine(ParseConstraints(prompt))
89 var wg sync.WaitGroup
90 for _, path := range []string{"README.md", "internal/auth/session.go", "schema/migration.sql", "internal/agent/agent.go"} {
91 wg.Add(1)
92 go func(path string) {
93 defer wg.Done()
94 if got := e.BeforeTool(writeCall(path)); got.Action != GuardAbstain {
95 t.Errorf("%s: unexpected quality precondition: %+v", path, got)
96 }
97 }(path)
98 }
99 wg.Wait()
100 }
101 }
102
103 func writeCall(path string) CallContext {
104 args := json.RawMessage(`{"path":"` + path + `"}`)
105 return CallContext{
106 ToolName: "edit_file",
107 Args: args,
108 Profile: evidence.ClassifyEffect(evidence.EffectInput{
109 ToolName: "edit_file", Args: args, ActualPaths: []string{path},
110 }),
111 }
112 }
113
113 lines GO