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 := NewAppWithDependencies(nil, nil, nil, &bytes.Buffer{}) err := app.Run([]string{"unknown"}) if err == nil { t.Fatal("expected error for unknown command") } } func TestAppRunShowsUsageWhenNoArgsProvided(t *testing.T) { app := NewAppWithDependencies(nil, nil, nil, &bytes.Buffer{}) 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 := NewAppWithDependencies(nil, nil, nil, &bytes.Buffer{}) 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) } }) } } func TestNewAppBuildsProductionDependencies(t *testing.T) { app := NewApp() if app == nil { t.Fatal("expected app instance") } if app.prompter == nil { t.Fatal("expected setup prompter to be configured") } if app.store == nil { t.Fatal("expected secret store to be configured") } if app.runner == nil { t.Fatal("expected MCP runner to be configured") } }