Files
2026-09-15 23:16:06 +01:00

86 lines
2.9 KiB
Go

package plugins
import (
"errors"
"testing"
"github.com/kandev/kandev/internal/plugins/manifest"
"github.com/kandev/kandev/internal/plugins/store"
)
func TestApprovalAPIExportsCurrentRowsAndDecision(t *testing.T) {
dir := t.TempDir()
svc := &Service{}
if err := svc.SetPluginsDir(dir); err != nil {
t.Fatalf("SetPluginsDir: %v", err)
}
t.Cleanup(func() { _ = svc.Close() })
if _, err := svc.approvalGrant("inst-1", "ws-1", 1, "digest-a", []string{"host.v2.read:tasks"}, "human", "grant", "audit-1"); err != nil {
t.Fatalf("grant: %v", err)
}
rows, err := svc.ListCapabilityApprovals("inst-1")
if err != nil {
t.Fatalf("ListCapabilityApprovals: %v", err)
}
if len(rows) != 1 || rows[0].WorkspaceID != "ws-1" {
t.Fatalf("rows = %#v", rows)
}
row, ok, err := svc.GetCapabilityApproval("inst-1", "ws-1")
if err != nil || !ok {
t.Fatalf("GetCapabilityApproval: ok=%v err=%v", ok, err)
}
if row.Revision != 1 {
t.Fatalf("row = %#v", row)
}
decision := svc.AuthorizeCapability("inst-1", "ws-1", "host.v2.read:tasks", 1, "req", "method")
if !decision.Allowed {
t.Fatalf("decision = %#v", decision)
}
}
func TestApprovalAPIRevokeRetryReplaysOriginalResult(t *testing.T) {
svc := &Service{}
if err := svc.SetPluginsDir(t.TempDir()); err != nil {
t.Fatalf("SetPluginsDir: %v", err)
}
t.Cleanup(func() { _ = svc.Close() })
if _, err := svc.GrantCapabilityApproval("inst-1", "ws-1", 1, "digest-a", []string{"host.v2.read:tasks"}, "human", "grant", "grant-1"); err != nil {
t.Fatalf("grant: %v", err)
}
first, err := svc.RevokeCapabilityApproval("inst-1", "ws-1", 1, "human", "revoke", "revoke-1")
if err != nil {
t.Fatalf("first revoke: %v", err)
}
replayed, err := svc.RevokeCapabilityApproval("inst-1", "ws-1", 1, "human", "revoke", "revoke-1")
if err != nil {
t.Fatalf("exact retry: %v", err)
}
if replayed.Revision != first.Revision || replayed.UpdatedAt != first.UpdatedAt {
t.Fatalf("retry changed original result: first=%#v replayed=%#v", first, replayed)
}
if _, err := svc.RevokeCapabilityApproval("inst-1", "ws-1", 2, "human", "revoke", "revoke-1"); !errors.Is(err, ErrApprovalIdempotencyConflict) {
t.Fatalf("changed expected revision error = %v, want idempotency conflict", err)
}
}
func TestGrantCapabilityApprovalRequiresInstalledManifestBinding(t *testing.T) {
svc := &Service{registry: NewRegistry()}
if err := svc.SetPluginsDir(t.TempDir()); err != nil {
t.Fatalf("SetPluginsDir: %v", err)
}
t.Cleanup(func() { _ = svc.Close() })
installed := &store.Record{
Manifest: manifest.Manifest{ID: "plugin-a", Capabilities: manifest.Capabilities{APIRead: []string{"tasks"}}},
InstallationID: "inst-1",
}
svc.registry.Add(installed)
_, err := svc.GrantCapabilityApproval("inst-1", "ws-1", 1, ManifestCapabilityDigest(installed.Manifest), []string{"host.v2.read:messages"}, "human", "grant", "audit-1")
if err == nil {
t.Fatal("GrantCapabilityApproval accepted a capability outside the installed manifest")
}
}