| 1 | package planmode |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | func TestDecideLeavesSafetyToPermissionsAndSandbox(t *testing.T) { |
| 9 | p := Policy{ |
| 10 | AllowedTools: []string{"legacy_reader"}, |
| 11 | ReadOnlyCommands: []string{"gh issue view"}, |
| 12 | } |
| 13 | for _, call := range []Call{ |
| 14 | {Name: "read_file", ReadOnly: true}, |
| 15 | {Name: "write_file", ReadOnly: false}, |
| 16 | {Name: "bash", ReadOnly: false}, |
| 17 | {Name: "mcp__srv__query", ReadOnly: true}, |
| 18 | {Name: "mcp__srv__write", ReadOnly: false}, |
| 19 | {Name: "self_reported_writer", ReadOnly: false, Safety: PlanSafetySafe}, |
| 20 | } { |
| 21 | if got := p.Decide(call); got.Blocked { |
| 22 | t.Errorf("ordinary call %q was phase-blocked: %s", call.Name, got.Message) |
| 23 | } |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | func TestDecideBlocksOnlyExplicitPhaseOptOut(t *testing.T) { |
| 28 | got := (Policy{}).Decide(Call{Name: "complete_step", ReadOnly: true, Safety: PlanSafetyUnsafe}) |
| 29 | if !got.Blocked || !strings.Contains(got.Message, "only available after plan approval") { |
| 30 | t.Fatalf("complete_step decision = %+v", got) |
| 31 | } |
| 32 | |
| 33 | got = (Policy{}).Decide(Call{Name: "custom_phase_tool", Safety: PlanSafetyUnsafe}) |
| 34 | if !got.Blocked || !strings.Contains(got.Message, "planning workflow") { |
| 35 | t.Fatalf("custom phase opt-out decision = %+v", got) |
| 36 | } |
| 37 | } |
| 38 |