email-mcp/internal/imapclient/service_test.go

194 lines
6 KiB
Go
Raw Permalink Normal View History

2026-04-10 09:11:01 +00:00
package imapclient
import (
"context"
"errors"
2026-04-10 09:11:01 +00:00
"testing"
"email-mcp/internal/secretstore"
)
type backendStub struct {
listMailboxes func(context.Context, secretstore.Credential) ([]Mailbox, error)
listMessages func(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error)
getMessage func(context.Context, secretstore.Credential, string, uint32) (Message, error)
}
2026-04-10 09:11:01 +00:00
func (b backendStub) ListMailboxes(ctx context.Context, cred secretstore.Credential) ([]Mailbox, error) {
return b.listMailboxes(ctx, cred)
2026-04-10 09:11:01 +00:00
}
func (b backendStub) ListMessages(ctx context.Context, cred secretstore.Credential, mailbox string, limit int) ([]MessageSummary, error) {
return b.listMessages(ctx, cred, mailbox, limit)
2026-04-10 09:11:01 +00:00
}
func (b backendStub) GetMessage(ctx context.Context, cred secretstore.Credential, mailbox string, uid uint32) (Message, error) {
return b.getMessage(ctx, cred, mailbox, uid)
2026-04-10 09:11:01 +00:00
}
func TestServiceListMailboxesUsesBackend(t *testing.T) {
svc := NewService(backendStub{
listMailboxes: func(_ context.Context, cred secretstore.Credential) ([]Mailbox, error) {
if cred.Host != "imap.example.com" || cred.Username != "alice" || cred.Password != "secret" {
t.Fatalf("unexpected credential: %#v", cred)
}
return []Mailbox{{Name: "INBOX"}}, nil
},
listMessages: func(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error) {
t.Fatal("ListMessages should not be called")
return nil, nil
},
getMessage: func(context.Context, secretstore.Credential, string, uint32) (Message, error) {
t.Fatal("GetMessage should not be called")
return Message{}, nil
},
})
2026-04-10 09:11:01 +00:00
boxes, err := svc.ListMailboxes(context.Background(), secretstore.Credential{
Host: "imap.example.com",
Username: "alice",
Password: "secret",
})
if err != nil {
t.Fatalf("ListMailboxes returned error: %v", err)
}
if len(boxes) != 1 || boxes[0].Name != "INBOX" {
t.Fatalf("unexpected mailboxes: %#v", boxes)
}
}
func TestServiceListMessagesUsesBackend(t *testing.T) {
svc := NewService(backendStub{
listMailboxes: func(context.Context, secretstore.Credential) ([]Mailbox, error) {
t.Fatal("ListMailboxes should not be called")
return nil, nil
},
listMessages: func(_ context.Context, cred secretstore.Credential, mailbox string, limit int) ([]MessageSummary, error) {
if cred.Host != "imap.example.com" || mailbox != "INBOX" || limit != 10 {
t.Fatalf("unexpected call: cred=%#v mailbox=%q limit=%d", cred, mailbox, limit)
}
return []MessageSummary{{
UID: 42,
Subject: "hello",
From: "alice@example.com",
}}, nil
},
getMessage: func(context.Context, secretstore.Credential, string, uint32) (Message, error) {
t.Fatal("GetMessage should not be called")
return Message{}, nil
},
})
messages, err := svc.ListMessages(context.Background(), secretstore.Credential{
Host: "imap.example.com",
Username: "alice",
Password: "secret",
}, "INBOX", 10)
if err != nil {
t.Fatalf("ListMessages returned error: %v", err)
}
if len(messages) != 1 || messages[0].UID != 42 {
t.Fatalf("unexpected messages: %#v", messages)
}
}
func TestServiceGetMessageUsesBackend(t *testing.T) {
svc := NewService(backendStub{
listMailboxes: func(context.Context, secretstore.Credential) ([]Mailbox, error) {
t.Fatal("ListMailboxes should not be called")
return nil, nil
},
listMessages: func(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error) {
t.Fatal("ListMessages should not be called")
return nil, nil
},
getMessage: func(_ context.Context, cred secretstore.Credential, mailbox string, uid uint32) (Message, error) {
if cred.Host != "imap.example.com" || mailbox != "INBOX" || uid != 42 {
t.Fatalf("unexpected call: cred=%#v mailbox=%q uid=%d", cred, mailbox, uid)
}
return Message{
UID: 42,
Mailbox: "INBOX",
Headers: []Header{
{Name: "Received", Value: "first"},
{Name: "Received", Value: "second"},
{Name: "Subject", Value: "hello"},
},
Body: "body",
}, nil
},
})
message, err := svc.GetMessage(context.Background(), secretstore.Credential{
Host: "imap.example.com",
Username: "alice",
Password: "secret",
}, "INBOX", 42)
if err != nil {
t.Fatalf("GetMessage returned error: %v", err)
}
if message.UID != 42 {
t.Fatalf("unexpected message UID: %#v", message)
}
if len(message.Headers) != 3 {
t.Fatalf("unexpected headers: %#v", message.Headers)
}
if message.Headers[0].Name != "Received" || message.Headers[0].Value != "first" {
t.Fatalf("unexpected first header: %#v", message.Headers[0])
}
if message.Headers[1].Name != "Received" || message.Headers[1].Value != "second" {
t.Fatalf("unexpected second header: %#v", message.Headers[1])
}
}
func TestServicePropagatesBackendErrors(t *testing.T) {
wantErr := errors.New("backend failed")
tests := []struct {
name string
run func(Service) error
}{
{
name: "ListMailboxes",
run: func(svc Service) error {
_, err := svc.ListMailboxes(context.Background(), secretstore.Credential{})
return err
},
},
{
name: "ListMessages",
run: func(svc Service) error {
_, err := svc.ListMessages(context.Background(), secretstore.Credential{}, "INBOX", 10)
return err
},
},
{
name: "GetMessage",
run: func(svc Service) error {
_, err := svc.GetMessage(context.Background(), secretstore.Credential{}, "INBOX", 42)
return err
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
svc := NewService(backendStub{
listMailboxes: func(context.Context, secretstore.Credential) ([]Mailbox, error) {
return nil, wantErr
},
listMessages: func(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error) {
return nil, wantErr
},
getMessage: func(context.Context, secretstore.Credential, string, uint32) (Message, error) {
return Message{}, wantErr
},
})
if err := tc.run(svc); !errors.Is(err, wantErr) {
t.Fatalf("expected error %v, got %v", wantErr, err)
}
})
}
}