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) } } // Hexis owns the tier of a Hexis capability, so this reads rather than derives // (Vikunja #523). The cases that matter are the ones where the three fields // disagree, or where the tier is a word this package has never seen: every one // of those goes up to a confirm, never down to running freely. func TestRiskOfCapabilityReadsHexis(t *testing.T) { for _, c := range []struct { name string risk string ro bool confirm bool want Risk }{ {"hexis says irreversible", "irreversible", false, true, TierIrreversible}, {"case and space do not change the tier", " Irreversible ", false, true, TierIrreversible}, {"hexis says destructive", "destructive", false, true, TierDestructive}, {"a read hexis calls safe", "safe", true, false, TierSafe}, {"safe but mutating is a contradiction", "safe", false, false, TierDestructive}, {"safe but wants a confirm is a contradiction", "safe", true, true, TierDestructive}, {"no tier, read-only, no confirm", "", true, false, TierSafe}, {"no tier and mutating", "", false, false, TierDestructive}, {"no tier but hexis wants a confirm", "", true, true, TierDestructive}, {"a word we have never seen", "spicy", true, false, TierDestructive}, } { if got := RiskOfCapability(c.risk, c.ro, c.confirm); got != c.want { t.Errorf("%s: RiskOfCapability(%q, ro=%v, confirm=%v) = %q; want %q", c.name, c.risk, c.ro, c.confirm, got, c.want) } } }