Vectrum
Sign In
114 lines · 4.1 KB · Go
package ccsettings

import (
	"os"
	"path/filepath"
	"testing"
)

func TestFingerprint(t *testing.T) {
	base := Grant{
		Servers: map[string]ServerDef{"a": {Command: "x", Env: map[string]string{"K": "1", "J": "2"}}, "b": {Command: "y"}},
		Allow:   []string{"Bash(make:*)", "Edit"},
	}
	same := Grant{
		Servers: map[string]ServerDef{"b": {Command: "y"}, "a": {Command: "x", Env: map[string]string{"J": "2", "K": "1"}}},
		Allow:   []string{"Edit", "Bash(make:*)"},
	}
	if base.Fingerprint() != same.Fingerprint() {
		t.Error("the same definitions in another order should be the same fingerprint")
	}
	changed := []Grant{
		{Servers: map[string]ServerDef{"a": {Command: "x", Env: map[string]string{"K": "1", "J": "2"}}, "b": {Command: "y", Args: []string{"--evil"}}}, Allow: base.Allow},
		{Servers: map[string]ServerDef{"a": {Command: "x", Env: map[string]string{"K": "1", "J": "2"}}}, Allow: base.Allow},
		{Servers: base.Servers, Allow: []string{"Bash(make:*)", "Edit", "Bash"}},
		{Servers: base.Servers, Allow: []string{"Bash(make:*)", "Edit"}, AllowFrom: []string{"elsewhere"}},
	}
	for i, g := range changed[:3] {
		if g.Fingerprint() == base.Fingerprint() {
			t.Errorf("change %d did not change the fingerprint", i)
		}
	}
	// Which file a rule came from is not part of what is trusted.
	if changed[3].Fingerprint() != base.Fingerprint() {
		t.Error("AllowFrom should not affect the fingerprint")
	}
	if !(Grant{}).Empty() || base.Empty() {
		t.Error("Empty is wrong")
	}
}

func TestNewGrantTakesOnlyProjectScope(t *testing.T) {
	mcp := []MCPFile{
		{Scope: ScopeLocal, Servers: map[string]ServerDef{"l": {Command: "l"}}},
		{Scope: ScopeProject, Servers: map[string]ServerDef{"p": {Command: "p"}}},
		{Scope: ScopeUser, Servers: map[string]ServerDef{"u": {Command: "u"}}},
	}
	perms := []Permissions{
		{Scope: ScopeUser, Allow: []string{"Bash"}},
		{Scope: ScopeProject, Shown: ".claude/settings.json", Allow: []string{"Edit"}, Deny: []string{"Read(.env)"}},
		{Scope: ScopeLocal, Allow: []string{"Write"}},
	}
	g := NewGrant(mcp, perms)
	if len(g.Servers) != 1 || g.Servers["p"].Command != "p" {
		t.Errorf("servers %v", g.Servers)
	}
	if len(g.Allow) != 1 || g.Allow[0] != "Edit" || g.AllowFrom[0] != ".claude/settings.json" {
		t.Errorf("allow %v from %v", g.Allow, g.AllowFrom)
	}
	if len(g.Describe()) != 4 {
		t.Errorf("describe: %q", g.Describe())
	}
}

func TestTrustStore(t *testing.T) {
	s := TrustStore{Path: filepath.Join(t.TempDir(), "codey", TrustFilename)}
	root := "/src/proj"

	if decided, _, err := s.Lookup(root, "fp1"); decided || err != nil {
		t.Fatalf("a fresh store decided=%v err=%v", decided, err)
	}
	if err := s.Record(root, "fp1", true); err != nil {
		t.Fatal(err)
	}
	if decided, trusted, _ := s.Lookup(root, "fp1"); !decided || !trusted {
		t.Error("the recorded decision was not found")
	}
	// Changed definitions are a new question.
	if decided, _, _ := s.Lookup(root, "fp2"); decided {
		t.Error("a decision about other definitions counted")
	}
	// Another checkout is another question.
	if decided, _, _ := s.Lookup("/src/other", "fp1"); decided {
		t.Error("a decision about another root counted")
	}
	// A refusal is remembered as a refusal.
	if err := s.Record(root, "fp2", false); err != nil {
		t.Fatal(err)
	}
	if decided, trusted, _ := s.Lookup(root, "fp2"); !decided || trusted {
		t.Error("the refusal was not remembered")
	}
	if decided, _, _ := s.Lookup(root, "fp1"); decided {
		t.Error("the old decision should have been replaced")
	}
	// Other roots' decisions survive a write.
	s.Record("/src/other", "x", true)
	if decided, _, _ := s.Lookup(root, "fp2"); !decided {
		t.Error("recording one root lost another")
	}
}

// A store that cannot be read is an error, not "nobody decided": the caller
// treats it as untrusted, and must not overwrite it.
func TestTrustStoreCorrupt(t *testing.T) {
	path := filepath.Join(t.TempDir(), TrustFilename)
	os.WriteFile(path, []byte("{"), 0o644)
	s := TrustStore{Path: path}
	if _, _, err := s.Lookup("/r", "fp"); err == nil {
		t.Error("want an error")
	}
	if err := s.Record("/r", "fp", true); err == nil {
		t.Error("recording over a corrupt store should fail rather than replace it")
	}
}