package ccsettings
import (
"os"
"path/filepath"
"testing"
)
func TestFingerprint(t *testing.T) {
base := Grant{Allow: []string{"Bash(make:*)", "Edit"}}
same := Grant{Allow: []string{"Edit", "Bash(make:*)"}}
if base.Fingerprint() != same.Fingerprint() {
t.Error("the same rules in another order should be the same fingerprint")
}
if (Grant{Allow: []string{"Bash(make:*)", "Edit", "Bash"}}).Fingerprint() == base.Fingerprint() {
t.Error("one more rule did not change the fingerprint")
}
// Which file a rule came from is not part of what is trusted.
if (Grant{Allow: base.Allow, AllowFrom: []string{"elsewhere"}}).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) {
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(perms)
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()) != 2 {
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")
}
}