package secretstore import ( "bytes" "encoding/base64" "encoding/json" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "regexp" "strings" "testing" "unicode/utf8" "forge.lclr.dev/AI/mcp-framework/manifest" ) func TestOpenSupportsBitwardenCLIBackendPolicy(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if _, ok := store.(*bitwardenStore); !ok { t.Fatalf("store type = %T, want *bitwardenStore", store) } if fakeCLI.versionChecked { t.Fatal("Open should not check bitwarden CLI version before a cache miss or write") } if fakeCLI.statusChecked { t.Fatal("Open should not check bitwarden CLI status before a cache miss or write") } } func TestBitwardenStoreMissReturnsUnavailableWhenCommandIsMissing(t *testing.T) { withBitwardenSession(t) withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { return nil, &exec.Error{Name: command, Err: exec.ErrNotFound} }) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } _, err = store.GetSecret("api-token") if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBackendUnavailable) { t.Fatalf("error = %v, want ErrBackendUnavailable", err) } } func TestBitwardenStoreMissFailsWhenSessionIsMissing(t *testing.T) { fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, LookupEnv: func(name string) (string, bool) { return "", false }, }) if err != nil { t.Fatalf("Open returned error: %v", err) } _, err = store.GetSecret("api-token") if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWLocked) { t.Fatalf("error = %v, want ErrBWLocked", err) } } func TestEnsureBitwardenReadyGuidesLoginAndUnlock(t *testing.T) { t.Run("unauthenticated", func(t *testing.T) { withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 1 && args[0] == "status" { return []byte(`{"status":"unauthenticated"}`), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) err := EnsureBitwardenReady(Options{BitwardenCommand: "bw"}) if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWNotLoggedIn) { t.Fatalf("error = %v, want ErrBWNotLoggedIn", err) } if !strings.Contains(err.Error(), "bw login") { t.Fatalf("error = %v, want guidance with bw login", err) } }) t.Run("locked", func(t *testing.T) { withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 1 && args[0] == "status" { return []byte(`{"status":"locked"}`), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) err := EnsureBitwardenReady(Options{BitwardenCommand: "bw"}) if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWLocked) { t.Fatalf("error = %v, want ErrBWLocked", err) } if !strings.Contains(err.Error(), "bw unlock --raw") { t.Fatalf("error = %v, want guidance with bw unlock", err) } }) } func TestEnsureBitwardenReadyAcceptsUnlockedSession(t *testing.T) { withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 1 && args[0] == "status" { return []byte(`{"status":"unlocked"}`), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) err := EnsureBitwardenReady(Options{ BitwardenCommand: "bw", LookupEnv: func(name string) (string, bool) { if name == "BW_SESSION" { return "session-token", true } return "", false }, }) if err != nil { t.Fatalf("EnsureBitwardenReady returned error: %v", err) } } func TestEnsureBitwardenReadyAdaptsUnlockRemediationToShell(t *testing.T) { t.Run("fish", func(t *testing.T) { withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 1 && args[0] == "status" { return []byte(`{"status":"locked"}`), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) err := EnsureBitwardenReady(Options{BitwardenCommand: "bw", Shell: "fish"}) if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWLocked) { t.Fatalf("error = %v, want ErrBWLocked", err) } if !strings.Contains(err.Error(), "set -x BW_SESSION (bw unlock --raw)") { t.Fatalf("error = %v, want fish unlock remediation", err) } }) t.Run("powershell", func(t *testing.T) { withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 1 && args[0] == "status" { return []byte(`{"status":"unlocked"}`), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) err := EnsureBitwardenReady(Options{ BitwardenCommand: "bw", Shell: "powershell", LookupEnv: func(name string) (string, bool) { return "", false }, }) if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWLocked) { t.Fatalf("error = %v, want ErrBWLocked", err) } if !strings.Contains(err.Error(), "$env:BW_SESSION = (bw unlock --raw)") { t.Fatalf("error = %v, want powershell unlock remediation", err) } }) } func TestBitwardenStoreSetGetDeleteSecret(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "graylog-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if err := store.SetSecret("api-token", "API token", "secret-v1"); err != nil { t.Fatalf("SetSecret returned error: %v", err) } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "secret-v1" { t.Fatalf("GetSecret = %q, want secret-v1", value) } if err := store.SetSecret("api-token", "API token", "secret-v2"); err != nil { t.Fatalf("SetSecret (update) returned error: %v", err) } value, err = store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret (updated) returned error: %v", err) } if value != "secret-v2" { t.Fatalf("GetSecret (updated) = %q, want secret-v2", value) } if err := store.DeleteSecret("api-token"); err != nil { t.Fatalf("DeleteSecret returned error: %v", err) } _, err = store.GetSecret("api-token") if !errors.Is(err, ErrNotFound) { t.Fatalf("GetSecret after delete error = %v, want ErrNotFound", err) } } func TestBitwardenStoreWritesMarkerFields(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if err := store.SetSecret("api-token", "API token", "secret-v1"); err != nil { t.Fatalf("SetSecret returned error: %v", err) } var found fakeBitwardenItem for _, item := range fakeCLI.itemsByID { if item.Name == "email-mcp/api-token" { found = item break } } if found.ID == "" { t.Fatal("expected bitwarden item to be created") } if found.MarkerService != "email-mcp" { t.Fatalf("marker service = %q, want email-mcp", found.MarkerService) } if found.MarkerSecretName != "api-token" { t.Fatalf("marker secret = %q, want api-token", found.MarkerSecretName) } } func TestBitwardenStorePrefersStrictMarkerMatchWhenNameCollides(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "wrong-secret", MarkerService: "other-service", MarkerSecretName: "api-token", } fakeCLI.itemsByID["item-2"] = fakeBitwardenItem{ ID: "item-2", Name: "email-mcp/api-token", Secret: "good-secret", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "good-secret" { t.Fatalf("GetSecret = %q, want good-secret", value) } } func TestBitwardenStoreFallsBackToSingleLegacyItemWithoutMarkers(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["legacy-1"] = fakeBitwardenItem{ ID: "legacy-1", Name: "email-mcp/api-token", Secret: "legacy-secret", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "legacy-secret" { t.Fatalf("GetSecret = %q, want legacy-secret", value) } } func TestBitwardenStoreGetSecretReadsSelectedItemOnlyOnce(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "secret-v1", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "secret-v1" { t.Fatalf("GetSecret = %q, want secret-v1", value) } if fakeCLI.getItemCalls != 1 { t.Fatalf("bw get item count = %d, want 1", fakeCLI.getItemCalls) } } func TestBitwardenStoreGetSecretUsesMemoryCache(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "secret-v1", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } for i := 0; i < 2; i++ { value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret #%d returned error: %v", i+1, err) } if value != "secret-v1" { t.Fatalf("GetSecret #%d = %q, want secret-v1", i+1, value) } } if fakeCLI.getItemCalls != 1 { t.Fatalf("bw get item count = %d, want 1 with memory cache", fakeCLI.getItemCalls) } } func TestBitwardenStoreDiskCacheHitSkipsBitwardenCLI(t *testing.T) { withBitwardenSession(t) cacheRoot := t.TempDir() withBitwardenUserCacheDir(t, func() (string, error) { return cacheRoot, nil }) cache := newBitwardenCache(bitwardenCacheOptions{ ServiceName: "email-mcp", Session: os.Getenv("BW_SESSION"), TTL: defaultBitwardenCacheTTL, CacheDir: resolveBitwardenCacheDir("email-mcp"), Enabled: true, }) cache.store("api-token", "email-mcp/api-token", "secret-from-cache") withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { return nil, fmt.Errorf("unexpected bitwarden invocation: %v", args) }) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "secret-from-cache" { t.Fatalf("GetSecret = %q, want secret-from-cache", value) } } func TestBitwardenStoreCacheMissChecksReadinessLazily(t *testing.T) { withBitwardenSession(t) withBitwardenUserCacheDir(t, func() (string, error) { return t.TempDir(), nil }) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "secret-v1", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if fakeCLI.statusChecked { t.Fatal("Open should not check bitwarden status") } value, err := store.GetSecret("api-token") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "secret-v1" { t.Fatalf("GetSecret = %q, want secret-v1", value) } if !fakeCLI.statusChecked { t.Fatal("cache miss should check bitwarden status before CLI lookup") } } func TestBitwardenStoreCacheDisabledByEnv(t *testing.T) { withBitwardenSession(t) t.Setenv("MCP_FRAMEWORK_BITWARDEN_CACHE", "0") fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "secret-v1", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } for i := 0; i < 2; i++ { if _, err := store.GetSecret("api-token"); err != nil { t.Fatalf("GetSecret #%d returned error: %v", i+1, err) } } if fakeCLI.getItemCalls != 2 { t.Fatalf("bw get item count = %d, want 2 when env disables cache", fakeCLI.getItemCalls) } } func TestBitwardenStoreSetSecretRefreshesCache(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if err := store.SetSecret("api-token", "API token", "secret-v1"); err != nil { t.Fatalf("SetSecret v1 returned error: %v", err) } if got, err := store.GetSecret("api-token"); err != nil || got != "secret-v1" { t.Fatalf("GetSecret after v1 = %q, %v; want secret-v1, nil", got, err) } if err := store.SetSecret("api-token", "API token", "secret-v2"); err != nil { t.Fatalf("SetSecret v2 returned error: %v", err) } if got, err := store.GetSecret("api-token"); err != nil || got != "secret-v2" { t.Fatalf("GetSecret after v2 = %q, %v; want secret-v2, nil", got, err) } } func TestBitwardenStoreDeleteSecretInvalidatesCache(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if err := store.SetSecret("api-token", "API token", "secret-v1"); err != nil { t.Fatalf("SetSecret returned error: %v", err) } if _, err := store.GetSecret("api-token"); err != nil { t.Fatalf("GetSecret before delete returned error: %v", err) } if err := store.DeleteSecret("api-token"); err != nil { t.Fatalf("DeleteSecret returned error: %v", err) } _, err = store.GetSecret("api-token") if !errors.Is(err, ErrNotFound) { t.Fatalf("GetSecret after delete error = %v, want ErrNotFound", err) } } func TestBitwardenFindItemTreatsEmptyListOutputAsNotFound(t *testing.T) { store := &bitwardenStore{command: "bw", serviceName: "email-mcp"} withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) { if len(args) == 4 && args[0] == "list" && args[1] == "items" && args[2] == "--search" { return []byte(""), nil } return nil, fmt.Errorf("unexpected args: %v", args) }) _, _, err := store.findItem("email-mcp/api-token", "api-token") if !errors.Is(err, ErrNotFound) { t.Fatalf("error = %v, want ErrNotFound", err) } } func TestExecuteBitwardenCLIClassifiesLoginError(t *testing.T) { if _, err := exec.LookPath("sh"); err != nil { t.Skip("sh is required for this test") } _, err := executeBitwardenCLI("sh", nil, "-c", "echo 'You are not logged in.' 1>&2; echo ' at Foo (node:internal/x)' 1>&2; exit 1") if err == nil { t.Fatal("expected error") } if !errors.Is(err, ErrBWNotLoggedIn) { t.Fatalf("error = %v, want ErrBWNotLoggedIn", err) } if strings.Contains(err.Error(), "node:internal") { t.Fatalf("error = %v, stack trace should be stripped", err) } } func TestOpenBitwardenCLIDebugFromEnvPrintsBitwardenCalls(t *testing.T) { withBitwardenSession(t) withBitwardenUserCacheDir(t, func() (string, error) { return t.TempDir(), nil }) fakeCLI := newFakeBitwardenCLI("bw") fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{ ID: "item-1", Name: "email-mcp/api-token", Secret: "secret-v1", MarkerService: "email-mcp", MarkerSecretName: "api-token", } withBitwardenRunner(t, fakeCLI.run) t.Setenv("MCP_FRAMEWORK_BITWARDEN_DEBUG", "1") var logs bytes.Buffer withBitwardenDebugOutput(t, &logs) store, err := Open(Options{ ServiceName: "email-mcp", BackendPolicy: BackendBitwardenCLI, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if _, err := store.GetSecret("api-token"); err != nil { t.Fatalf("GetSecret returned error: %v", err) } text := logs.String() if !strings.Contains(text, "bw --version") { t.Fatalf("debug logs = %q, want command bw --version", text) } if !strings.Contains(text, "bw status") { t.Fatalf("debug logs = %q, want command bw status", text) } } func TestBitwardenDebugRedactsSensitivePayloadArguments(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) var logs bytes.Buffer withBitwardenDebugOutput(t, &logs) store, err := Open(Options{ ServiceName: "graylog-mcp", BackendPolicy: BackendBitwardenCLI, BitwardenDebug: true, }) if err != nil { t.Fatalf("Open returned error: %v", err) } if err := store.SetSecret("api-token", "API token", "secret-v1"); err != nil { t.Fatalf("SetSecret returned error: %v", err) } text := logs.String() if !strings.Contains(text, "bw create item ") { t.Fatalf("debug logs = %q, want redacted create payload", text) } } func TestBitwardenLoaderFrameUsesSingleLineRewriteAndMessage(t *testing.T) { frame := bitwardenLoaderFrame(0) if !strings.HasPrefix(frame, "\r\033[2K") { t.Fatalf("frame prefix = %q, want carriage return + clear line", frame) } if !strings.Contains(frame, "\033[38;5;117mW") { t.Fatalf("frame = %q, want highlighted first rune", frame) } cleaned := stripANSIControlSequences(frame) if cleaned != bitwardenLoaderMessage { t.Fatalf("cleaned frame = %q, want %q", cleaned, bitwardenLoaderMessage) } } func TestBitwardenLoaderFrameMovesAndWrapsTheWave(t *testing.T) { firstFrame := bitwardenLoaderFrame(0) secondFrame := bitwardenLoaderFrame(1) if firstFrame == secondFrame { t.Fatal("expected different frames between two ticks") } if !strings.Contains(secondFrame, "\033[38;5;117ma") { t.Fatalf("second frame = %q, want highlighted second rune", secondFrame) } wrapped := bitwardenLoaderFrame(utf8.RuneCountInString(bitwardenLoaderMessage)) if !strings.Contains(wrapped, "\033[38;5;117mW") { t.Fatalf("wrapped frame = %q, want wave to wrap to first rune", wrapped) } } func TestExecuteBitwardenCLIInteractiveSkipsLoader(t *testing.T) { loaderStartCount := 0 withBitwardenLoaderStarter(t, func() func() { loaderStartCount++ return func() {} }) _, err := executeBitwardenCLIInteractive( os.Args[0], nil, io.Discard, io.Discard, "-test.run=^$", ) if err != nil { t.Fatalf("executeBitwardenCLIInteractive returned error: %v", err) } if loaderStartCount != 0 { t.Fatalf("loader start count = %d, want 0 for interactive command", loaderStartCount) } } var ansiControlSequencePattern = regexp.MustCompile(`\x1b\[[0-9;]*[A-Za-z]`) func stripANSIControlSequences(value string) string { noANSI := ansiControlSequencePattern.ReplaceAllString(value, "") return strings.ReplaceAll(noANSI, "\r", "") } func TestOpenFromManifestSupportsBitwardenCLIBackendPolicy(t *testing.T) { withBitwardenSession(t) fakeCLI := newFakeBitwardenCLI("bw") withBitwardenRunner(t, fakeCLI.run) store, err := OpenFromManifest(OpenFromManifestOptions{ ServiceName: "email-mcp", ExecutableResolver: func() (string, error) { return filepath.Join(string(filepath.Separator), "opt", "email-mcp", "bin", "email-mcp"), nil }, ManifestLoader: func(startDir string) (manifest.File, string, error) { return manifest.File{ SecretStore: manifest.SecretStore{BackendPolicy: string(BackendBitwardenCLI)}, }, filepath.Join(startDir, manifest.DefaultFile), nil }, }) if err != nil { t.Fatalf("OpenFromManifest returned error: %v", err) } if err := store.SetSecret("smtp-password", "SMTP password", "super-secret"); err != nil { t.Fatalf("SetSecret returned error: %v", err) } value, err := store.GetSecret("smtp-password") if err != nil { t.Fatalf("GetSecret returned error: %v", err) } if value != "super-secret" { t.Fatalf("GetSecret = %q, want super-secret", value) } } func withBitwardenSession(t *testing.T) { t.Helper() sessionName := strings.NewReplacer("/", "-", " ", "-").Replace(t.Name()) t.Setenv("BW_SESSION", "test-session-"+sessionName) withBitwardenUserCacheDir(t, func() (string, error) { return t.TempDir(), nil }) } func withBitwardenRunner( t *testing.T, runner func(command string, stdin []byte, args ...string) ([]byte, error), ) { t.Helper() previous := runBitwardenCLI runBitwardenCLI = runner t.Cleanup(func() { runBitwardenCLI = previous }) } func withBitwardenDebugOutput(t *testing.T, writer io.Writer) { t.Helper() previous := bitwardenDebugOutput bitwardenDebugOutput = writer t.Cleanup(func() { bitwardenDebugOutput = previous }) } func withBitwardenLoaderStarter(t *testing.T, starter func() func()) { t.Helper() previous := startBitwardenLoaderFunc startBitwardenLoaderFunc = starter t.Cleanup(func() { startBitwardenLoaderFunc = previous }) } type fakeBitwardenCLI struct { command string itemsByID map[string]fakeBitwardenItem nextID int status string versionChecked bool statusChecked bool getItemCalls int } type fakeBitwardenItem struct { ID string Name string Notes string Secret string MarkerService string MarkerSecretName string } func newFakeBitwardenCLI(command string) *fakeBitwardenCLI { return &fakeBitwardenCLI{ command: strings.TrimSpace(command), itemsByID: map[string]fakeBitwardenItem{}, nextID: 1, status: "unlocked", } } func (f *fakeBitwardenCLI) run(command string, stdin []byte, args ...string) ([]byte, error) { if strings.TrimSpace(command) != f.command { return nil, fmt.Errorf("unexpected command %q", command) } if len(args) == 0 { return nil, errors.New("missing bitwarden CLI arguments") } if len(args) == 1 && args[0] == "--version" { f.versionChecked = true return []byte("2026.1.0\n"), nil } if len(args) == 1 && args[0] == "status" { f.statusChecked = true return []byte(fmt.Sprintf(`{"status":%q}`, strings.TrimSpace(f.status))), nil } switch { case len(args) == 4 && args[0] == "list" && args[1] == "items" && args[2] == "--search": return f.handleListItems(args[3]) case len(args) == 3 && args[0] == "get" && args[1] == "item": f.getItemCalls++ return f.handleGetItem(args[2]) case len(args) == 3 && args[0] == "get" && args[1] == "template" && args[2] == "item": return []byte(`{"type":2,"name":"","notes":"","secureNote":{"type":0},"fields":[]}`), nil case len(args) == 1 && args[0] == "encode": return []byte(base64.StdEncoding.EncodeToString(stdin)), nil case len(args) == 3 && args[0] == "create" && args[1] == "item": return f.handleCreateItem(args[2]) case len(args) == 4 && args[0] == "edit" && args[1] == "item": return f.handleEditItem(args[2], args[3]) case len(args) == 3 && args[0] == "delete" && args[1] == "item": delete(f.itemsByID, strings.TrimSpace(args[2])) return []byte(`{"success":true}`), nil default: return nil, fmt.Errorf("unsupported bitwarden CLI invocation: %v", args) } } func (f *fakeBitwardenCLI) handleListItems(search string) ([]byte, error) { needle := strings.TrimSpace(search) items := make([]map[string]any, 0) for _, item := range f.itemsByID { if !strings.Contains(item.Name, needle) { continue } items = append(items, map[string]any{ "id": item.ID, "name": item.Name, "notes": item.Notes, "fields": item.fieldsPayload(), }) } payload, err := json.Marshal(items) if err != nil { return nil, err } return payload, nil } func (f *fakeBitwardenCLI) handleGetItem(id string) ([]byte, error) { item, ok := f.itemsByID[strings.TrimSpace(id)] if !ok { return nil, errors.New("item not found") } payload, err := json.Marshal(map[string]any{ "id": item.ID, "name": item.Name, "notes": item.Notes, "fields": item.fieldsPayload(), "secureNote": map[string]any{"type": 0}, }) if err != nil { return nil, err } return payload, nil } func (f *fakeBitwardenCLI) handleCreateItem(encoded string) ([]byte, error) { payload, err := decodeBitwardenPayload(encoded) if err != nil { return nil, err } item := fakeBitwardenItem{ ID: fmt.Sprintf("item-%d", f.nextID), Name: readString(payload, "name"), Notes: readString(payload, "notes"), Secret: readFakeBitwardenField(payload, bitwardenSecretFieldName), MarkerService: readFakeBitwardenField(payload, bitwardenServiceFieldName), MarkerSecretName: readFakeBitwardenField(payload, bitwardenSecretNameFieldName), } f.nextID++ f.itemsByID[item.ID] = item payload["id"] = item.ID encodedPayload, err := json.Marshal(payload) if err != nil { return nil, err } return encodedPayload, nil } func (f *fakeBitwardenCLI) handleEditItem(id, encoded string) ([]byte, error) { trimmedID := strings.TrimSpace(id) if _, ok := f.itemsByID[trimmedID]; !ok { return nil, errors.New("item not found") } payload, err := decodeBitwardenPayload(encoded) if err != nil { return nil, err } item := fakeBitwardenItem{ ID: trimmedID, Name: readString(payload, "name"), Notes: readString(payload, "notes"), Secret: readFakeBitwardenField(payload, bitwardenSecretFieldName), MarkerService: readFakeBitwardenField(payload, bitwardenServiceFieldName), MarkerSecretName: readFakeBitwardenField(payload, bitwardenSecretNameFieldName), } f.itemsByID[trimmedID] = item payload["id"] = trimmedID encodedPayload, err := json.Marshal(payload) if err != nil { return nil, err } return encodedPayload, nil } func decodeBitwardenPayload(encoded string) (map[string]any, error) { raw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(encoded)) if err != nil { return nil, fmt.Errorf("decode encoded payload: %w", err) } var payload map[string]any if err := json.Unmarshal(raw, &payload); err != nil { return nil, fmt.Errorf("decode payload JSON: %w", err) } return payload, nil } func readFakeBitwardenField(payload map[string]any, fieldName string) string { rawFields, ok := payload["fields"] if !ok { return "" } fields, ok := rawFields.([]any) if !ok { return "" } for _, rawField := range fields { field, ok := rawField.(map[string]any) if !ok { continue } name := strings.TrimSpace(readString(field, "name")) if name != fieldName { continue } return readString(field, "value") } return "" } func (i fakeBitwardenItem) fieldsPayload() []map[string]any { fields := []map[string]any{ {"name": bitwardenSecretFieldName, "value": i.Secret, "type": 1}, } if strings.TrimSpace(i.MarkerService) != "" { fields = append(fields, map[string]any{"name": bitwardenServiceFieldName, "value": i.MarkerService, "type": 0}) } if strings.TrimSpace(i.MarkerSecretName) != "" { fields = append(fields, map[string]any{"name": bitwardenSecretNameFieldName, "value": i.MarkerSecretName, "type": 0}) } return fields } func readString(payload map[string]any, key string) string { value, _ := payload[key].(string) return strings.TrimSpace(value) }