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") } }