package cli import ( "bytes" "context" "testing" "email-mcp/internal/imapclient" "email-mcp/internal/mcpserver" "email-mcp/internal/secretstore" "email-mcp/internal/secretstore/kwallet" ) type entrypointPromptStub struct { credential secretstore.Credential } func (p *entrypointPromptStub) PromptSetup(context.Context) (secretstore.Credential, error) { return p.credential, nil } type entrypointStoreStub struct { saveErr error loadErr error } func (s *entrypointStoreStub) Save(context.Context, string, secretstore.Credential) error { return s.saveErr } func (s *entrypointStoreStub) Load(context.Context, string) (secretstore.Credential, error) { return secretstore.Credential{}, s.loadErr } type entrypointMailServiceStub struct{} func (entrypointMailServiceStub) ListMailboxes(context.Context, secretstore.Credential) ([]imapclient.Mailbox, error) { return nil, nil } func (entrypointMailServiceStub) ListMessages(context.Context, secretstore.Credential, string, int) ([]imapclient.MessageSummary, error) { return nil, nil } func (entrypointMailServiceStub) GetMessage(context.Context, secretstore.Credential, string, uint32) (imapclient.Message, error) { return imapclient.Message{}, nil } func TestExecuteSetupWritesWalletGuidanceAndReturnsExitCodeOne(t *testing.T) { app := NewAppWithDependencies( &entrypointPromptStub{ credential: secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }, }, &entrypointStoreStub{saveErr: kwallet.ErrKWalletUnavailable}, nil, nil, ) stderr := &bytes.Buffer{} if code := Execute(app, []string{"setup"}, stderr); code != 1 { t.Fatalf("expected exit code 1, got %d", code) } if got := stderr.String(); got != "kwallet is not available; make sure KDE Wallet is installed and your session D-Bus is running\n" { t.Fatalf("unexpected stderr: %q", got) } } func TestExecuteMCPReturnsMissingCredentialErrorOnToolCall(t *testing.T) { store := &entrypointStoreStub{loadErr: kwallet.ErrCredentialNotFound} mail := entrypointMailServiceStub{} input := bytes.NewBufferString( "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}\n" + "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n" + "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"list_mailboxes\"}}\n", ) output := &bytes.Buffer{} runner := mcpserver.NewRunner(mcpserver.New(store, mail), input, output, &bytes.Buffer{}) app := NewAppWithDependencies(nil, store, runner, nil) stderr := &bytes.Buffer{} if code := Execute(app, []string{"mcp"}, stderr); code != 0 { t.Fatalf("expected exit code 0, got %d; stderr: %s", code, stderr.String()) } // Verify the credential error appears in the tool call response got := output.String() if !bytes.Contains([]byte(got), []byte("credentials not configured")) { t.Fatalf("expected credential error in output, got %q", got) } }