package secretstore import ( "bytes" "os" "path/filepath" "strings" "testing" "time" ) func TestBitwardenCacheMemoryHit(t *testing.T) { cache := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: 10 * time.Minute, Now: func() time.Time { return time.Date(2026, 5, 2, 10, 0, 0, 0, time.UTC) }, CacheDir: t.TempDir(), Enabled: true, }) cache.store("api-token", "email-mcp/api-token", "secret-v1") got, ok := cache.loadMemory("api-token", "email-mcp/api-token") if !ok { t.Fatal("memory cache miss, want hit") } if got != "secret-v1" { t.Fatalf("memory cache value = %q, want secret-v1", got) } } func TestBitwardenCacheDiskRoundTripIsEncrypted(t *testing.T) { dir := t.TempDir() now := time.Date(2026, 5, 2, 10, 0, 0, 0, time.UTC) cache := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: 10 * time.Minute, Now: func() time.Time { return now }, CacheDir: dir, Enabled: true, }) cache.store("api-token", "email-mcp/api-token", "secret-v1") reopened := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: 10 * time.Minute, Now: func() time.Time { return now.Add(time.Minute) }, CacheDir: dir, Enabled: true, }) got, ok := reopened.loadDisk("api-token", "email-mcp/api-token") if !ok { t.Fatal("disk cache miss, want hit") } if got != "secret-v1" { t.Fatalf("disk cache value = %q, want secret-v1", got) } entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir cache dir: %v", err) } if len(entries) != 1 { t.Fatalf("cache file count = %d, want 1", len(entries)) } data, err := os.ReadFile(filepath.Join(dir, entries[0].Name())) if err != nil { t.Fatalf("ReadFile cache file: %v", err) } if bytes.Contains(data, []byte("secret-v1")) { t.Fatalf("cache file contains plaintext secret: %s", data) } if strings.Contains(entries[0].Name(), "api-token") { t.Fatalf("cache file name exposes secret name: %s", entries[0].Name()) } } func TestBitwardenCacheRejectsChangedSession(t *testing.T) { dir := t.TempDir() now := time.Date(2026, 5, 2, 10, 0, 0, 0, time.UTC) cache := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: 10 * time.Minute, Now: func() time.Time { return now }, CacheDir: dir, Enabled: true, }) cache.store("api-token", "email-mcp/api-token", "secret-v1") changed := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v2", TTL: 10 * time.Minute, Now: func() time.Time { return now.Add(time.Minute) }, CacheDir: dir, Enabled: true, }) if got, ok := changed.loadDisk("api-token", "email-mcp/api-token"); ok { t.Fatalf("disk cache hit with changed session = %q, want miss", got) } } func TestBitwardenCacheExpiresEntries(t *testing.T) { dir := t.TempDir() now := time.Date(2026, 5, 2, 10, 0, 0, 0, time.UTC) cache := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: time.Minute, Now: func() time.Time { return now }, CacheDir: dir, Enabled: true, }) cache.store("api-token", "email-mcp/api-token", "secret-v1") expired := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: "session-v1", TTL: time.Minute, Now: func() time.Time { return now.Add(2 * time.Minute) }, CacheDir: dir, Enabled: true, }) if got, ok := expired.load("api-token", "email-mcp/api-token"); ok { t.Fatalf("expired cache hit = %q, want miss", got) } } func withBitwardenUserCacheDir(t *testing.T, resolver func() (string, error)) { t.Helper() previous := bitwardenUserCacheDir bitwardenUserCacheDir = resolver t.Cleanup(func() { bitwardenUserCacheDir = previous }) }