email-mcp/internal/cli/app_test.go
2026-04-10 10:08:21 +02:00

172 lines
4.1 KiB
Go

package cli
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"email-mcp/internal/secretstore"
)
var _ func() *App = NewApp
type promptStub struct {
credential secretstore.Credential
err error
called bool
}
func (p *promptStub) PromptSetup(context.Context) (secretstore.Credential, error) {
p.called = true
if p.err != nil {
return secretstore.Credential{}, p.err
}
return p.credential, nil
}
type storeStub struct {
saved secretstore.Credential
savedKey string
saveErr error
saveCalled bool
}
func (s *storeStub) Save(_ context.Context, key string, cred secretstore.Credential) error {
s.saveCalled = true
s.savedKey = key
s.saved = cred
return s.saveErr
}
func (s *storeStub) Load(context.Context, string) (secretstore.Credential, error) {
return secretstore.Credential{}, nil
}
type runnerStub struct {
err error
called bool
}
func (r *runnerStub) Run(context.Context) error {
r.called = true
return r.err
}
func TestAppRunRejectsUnknownCommand(t *testing.T) {
app := NewApp()
err := app.Run([]string{"unknown"})
if err == nil {
t.Fatal("expected error for unknown command")
}
}
func TestAppRunShowsUsageWhenNoArgsProvided(t *testing.T) {
app := NewApp()
err := app.Run(nil)
if err == nil {
t.Fatal("expected error for missing command")
}
if !strings.Contains(err.Error(), "usage:") {
t.Fatalf("expected usage text in error, got %q", err.Error())
}
}
func TestAppRunSetupPromptsAndSavesDefaultCredential(t *testing.T) {
store := &storeStub{}
prompter := &promptStub{
credential: secretstore.Credential{
Host: "imap.example.com",
Username: "alice",
Password: "secret",
},
}
app := NewAppWithDependencies(prompter, store, nil, &bytes.Buffer{})
if err := app.Run([]string{"setup"}); err != nil {
t.Fatalf("Run returned error: %v", err)
}
if !prompter.called {
t.Fatal("expected setup prompter to be called")
}
if !store.saveCalled {
t.Fatal("expected store Save to be called")
}
if store.savedKey != secretstore.DefaultAccountKey {
t.Fatalf("expected saved key %q, got %q", secretstore.DefaultAccountKey, store.savedKey)
}
if store.saved.Host != "imap.example.com" || store.saved.Username != "alice" || store.saved.Password != "secret" {
t.Fatalf("unexpected saved credential: %#v", store.saved)
}
}
func TestAppRunSetupRejectsInvalidCredential(t *testing.T) {
store := &storeStub{}
app := NewAppWithDependencies(&promptStub{
credential: secretstore.Credential{
Host: "imap.example.com",
Username: "alice",
},
}, store, nil, &bytes.Buffer{})
err := app.Run([]string{"setup"})
if err == nil {
t.Fatal("expected setup to fail for invalid credential")
}
if !strings.Contains(err.Error(), "password is required") {
t.Fatalf("expected validation error, got %v", err)
}
if store.saveCalled {
t.Fatal("expected store Save not to be called when credential is invalid")
}
}
func TestAppRunReturnsPromptError(t *testing.T) {
expectedErr := errors.New("prompt failed")
app := NewAppWithDependencies(&promptStub{err: expectedErr}, &storeStub{}, nil, &bytes.Buffer{})
err := app.Run([]string{"setup"})
if !errors.Is(err, expectedErr) {
t.Fatalf("expected prompt error %v, got %v", expectedErr, err)
}
}
func TestAppRunMCPDelegatesToRunner(t *testing.T) {
runner := &runnerStub{}
app := NewAppWithDependencies(nil, nil, runner, &bytes.Buffer{})
if err := app.Run([]string{"mcp"}); err != nil {
t.Fatalf("expected mcp command to succeed, got %v", err)
}
if !runner.called {
t.Fatal("expected MCP runner to be called")
}
}
func TestAppRunReturnsClearErrorsWhenDependenciesMissing(t *testing.T) {
app := NewApp()
tests := []struct {
command string
want string
}{
{command: "setup", want: "setup prompter is not configured"},
{command: "mcp", want: "mcp runner is not configured"},
}
for _, tt := range tests {
t.Run(tt.command, func(t *testing.T) {
err := app.Run([]string{tt.command})
if err == nil {
t.Fatalf("expected %s to fail without dependencies", tt.command)
}
if !strings.Contains(err.Error(), tt.want) {
t.Fatalf("expected error to contain %q, got %v", tt.want, err)
}
})
}
}