mcp-framework/secretstore/bitwarden_cache_test.go

141 lines
3.9 KiB
Go

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