6b80fd0c0f
Add a 'scope' TEXT column (default 'homelab') to the tools table so tools can be namespaced by scope (e.g. "homelab:restart", "datacenter:reboot"). Backward-compat: bare name defaults to "homelab" scope. Changes: - Migration #1: ALTER TABLE tools ADD COLUMN scope - store.Tool: add Scope field, update all SQL and scanTool() - ipc.Tool DTO and request types: add Scope field - CoreAPI interface: pass scope in ProposeTool/EnableTool - storeAPI adapters: forward scope - cmd/mavend/voice: pass scope (empty → homelab) - cmd/mavweb/tools: show scope column in UI tables, hidden fields - All tests updated for scope field - Migration test made dynamic (startVer = len(migrations))
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestToolLifecycle covers propose → enable → disable, the states the authed
|
|
// /tools page drives. Disable must revert an enabled tool to 'proposed' (kept
|
|
// in the store, won't run) and be idempotent.
|
|
func TestToolLifecycle(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now()
|
|
|
|
// Propose with empty scope → defaults to "homelab".
|
|
if _, err := s.ProposeTool(ctx, "restart_svc", "restart the service", "", now); err != nil {
|
|
t.Fatalf("propose: %v", err)
|
|
}
|
|
if err := s.EnableTool(ctx, "restart_svc", []string{"systemctl", "restart", "x"}, true, "", now); err != nil {
|
|
t.Fatalf("enable: %v", err)
|
|
}
|
|
tl, _ := s.LookupTool(ctx, "restart_svc")
|
|
if tl.Status != "enabled" {
|
|
t.Fatalf("after enable: status=%q want enabled", tl.Status)
|
|
}
|
|
if tl.Scope != "homelab" {
|
|
t.Fatalf("after enable: scope=%q want homelab", tl.Scope)
|
|
}
|
|
|
|
// Propose with explicit scope.
|
|
if _, err := s.ProposeTool(ctx, "reboot", "reboot the server", "datacenter", now); err != nil {
|
|
t.Fatalf("propose with scope: %v", err)
|
|
}
|
|
if err := s.EnableTool(ctx, "reboot", []string{"reboot"}, true, "datacenter", now); err != nil {
|
|
t.Fatalf("enable with scope: %v", err)
|
|
}
|
|
tl2, _ := s.LookupTool(ctx, "reboot")
|
|
if tl2.Scope != "datacenter" {
|
|
t.Fatalf("explicit scope: %q want datacenter", tl2.Scope)
|
|
}
|
|
|
|
if err := s.DisableTool(ctx, "restart_svc"); err != nil {
|
|
t.Fatalf("disable: %v", err)
|
|
}
|
|
tl, err := s.LookupTool(ctx, "restart_svc")
|
|
if err != nil {
|
|
t.Fatalf("lookup after disable: %v", err)
|
|
}
|
|
if tl.Status != "proposed" {
|
|
t.Fatalf("after disable: status=%q want proposed", tl.Status)
|
|
}
|
|
|
|
// idempotent: disabling an already-proposed (or absent) tool is a no-op.
|
|
if err := s.DisableTool(ctx, "restart_svc"); err != nil {
|
|
t.Fatalf("disable idempotent: %v", err)
|
|
}
|
|
if err := s.DisableTool(ctx, "does_not_exist"); err != nil {
|
|
t.Fatalf("disable absent must be no-op: %v", err)
|
|
}
|
|
}
|