82 lines
3.1 KiB
Go
82 lines
3.1 KiB
Go
package tool
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/ipc"
|
|
)
|
|
|
|
func TestRiskOfReadsTheRow(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
tool ipc.Tool
|
|
want Risk
|
|
}{
|
|
{"a plain read", ipc.Tool{Cmd: []string{"systemctl", "status"}}, TierSafe},
|
|
{"the checkbox", ipc.Tool{Cmd: []string{"systemctl", "restart"}, Destructive: true}, TierDestructive},
|
|
{"a wipe", ipc.Tool{Cmd: []string{"rm", "-rf"}}, TierIrreversible},
|
|
{"a wipe behind a wrapper", ipc.Tool{Cmd: []string{"sudo", "/bin/rm"}}, TierIrreversible},
|
|
{"a prune behind a subcommand", ipc.Tool{Cmd: []string{"docker", "volume", "prune"}}, TierIrreversible},
|
|
{"the house", ipc.Tool{Cmd: []string{"smarthome", "light.kitchen", "turn_off"}}, TierDestructive},
|
|
{"the house with the box unticked", ipc.Tool{Cmd: []string{"smarthome", "lock.front", "unlock"}}, TierDestructive},
|
|
{"an mcp read", ipc.Tool{Cmd: []string{"mcp", "vikunja", "list_tasks"}}, TierSafe},
|
|
{"an mcp write", ipc.Tool{Cmd: []string{"mcp", "vikunja", "delete_task"}, Destructive: true}, TierDestructive},
|
|
{"a shape nobody wrote yet", ipc.Tool{}, TierDestructive},
|
|
}
|
|
for _, c := range cases {
|
|
if got := RiskOf(c.tool); got != c.want {
|
|
t.Errorf("%s: RiskOf = %q; want %q", c.name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The default is the confirm turn. A tier this file does not know is not a
|
|
// tier that runs freely.
|
|
func TestPolicyForDefaultsToConfirming(t *testing.T) {
|
|
for _, r := range []Risk{TierDestructive, Risk("whatever-lands-here-next")} {
|
|
p := PolicyFor(r)
|
|
if !p.Confirm || !p.VoiceMayRun {
|
|
t.Errorf("PolicyFor(%q) = %+v; want a confirm turn she may run", r, p)
|
|
}
|
|
}
|
|
if p := PolicyFor(TierSafe); p.Confirm || !p.VoiceMayRun {
|
|
t.Errorf("PolicyFor(safe) = %+v; want it to run", p)
|
|
}
|
|
if p := PolicyFor(TierIrreversible); !p.Confirm || p.VoiceMayRun {
|
|
t.Errorf("PolicyFor(irreversible) = %+v; want voice refused", p)
|
|
}
|
|
}
|
|
|
|
// An irreversible act is refused whether or not he said "да", because there is
|
|
// no second answer that changes what it would do.
|
|
func TestExecRefusesIrreversibleEvenConfirmed(t *testing.T) {
|
|
api := fakeAPI{tools: map[string]ipc.Tool{
|
|
"wipe": {Name: "wipe", Status: "enabled", Cmd: []string{"rm", "-rf"}, Destructive: true},
|
|
}}
|
|
e := NewExecutor(api, 0)
|
|
ran := false
|
|
e.run = func(context.Context, []string) (string, error) { ran = true; return "", nil }
|
|
for _, confirmed := range []bool{false, true} {
|
|
if _, err := e.Exec(context.Background(), "wipe", []string{"/data"}, confirmed); !errors.Is(err, ErrNeedsAuthedSurface) {
|
|
t.Errorf("confirmed=%v: %v; want ErrNeedsAuthedSurface", confirmed, err)
|
|
}
|
|
}
|
|
if ran {
|
|
t.Fatal("an irreversible act ran from the voice path")
|
|
}
|
|
}
|
|
|
|
// A row with no cmd at all is not a shape this file reads, and it must not
|
|
// slide through as safe.
|
|
func TestExecConfirmsAnUnreadableRow(t *testing.T) {
|
|
api := fakeAPI{tools: map[string]ipc.Tool{
|
|
"mystery": {Name: "mystery", Status: "enabled"},
|
|
}}
|
|
e := NewExecutor(api, 0)
|
|
if _, err := e.Exec(context.Background(), "mystery", nil, false); !errors.Is(err, ErrNeedsConfirm) {
|
|
t.Errorf("%v; want ErrNeedsConfirm", err)
|
|
}
|
|
}
|