fix: tighten imap service message contract
This commit is contained in:
parent
00fa0699f5
commit
e360f9bffa
3 changed files with 177 additions and 18 deletions
|
|
@ -22,6 +22,6 @@ func (s Service) ListMessages(ctx context.Context, cred secretstore.Credential,
|
|||
return s.backend.ListMessages(ctx, cred, mailbox, limit)
|
||||
}
|
||||
|
||||
func (s Service) GetMessage(ctx context.Context, cred secretstore.Credential, mailbox string, id string) (Message, error) {
|
||||
return s.backend.GetMessage(ctx, cred, mailbox, id)
|
||||
func (s Service) GetMessage(ctx context.Context, cred secretstore.Credential, mailbox string, uid uint32) (Message, error) {
|
||||
return s.backend.GetMessage(ctx, cred, mailbox, uid)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,47 @@ package imapclient
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"email-mcp/internal/secretstore"
|
||||
)
|
||||
|
||||
type backendStub struct{}
|
||||
|
||||
func (backendStub) ListMailboxes(context.Context, secretstore.Credential) ([]Mailbox, error) {
|
||||
return []Mailbox{{Name: "INBOX"}}, nil
|
||||
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)
|
||||
}
|
||||
|
||||
func (backendStub) ListMessages(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error) {
|
||||
return nil, nil
|
||||
func (b backendStub) ListMailboxes(ctx context.Context, cred secretstore.Credential) ([]Mailbox, error) {
|
||||
return b.listMailboxes(ctx, cred)
|
||||
}
|
||||
|
||||
func (backendStub) GetMessage(context.Context, secretstore.Credential, string, string) (Message, error) {
|
||||
return Message{}, nil
|
||||
func (b backendStub) ListMessages(ctx context.Context, cred secretstore.Credential, mailbox string, limit int) ([]MessageSummary, error) {
|
||||
return b.listMessages(ctx, cred, mailbox, limit)
|
||||
}
|
||||
|
||||
func (b backendStub) GetMessage(ctx context.Context, cred secretstore.Credential, mailbox string, uid uint32) (Message, error) {
|
||||
return b.getMessage(ctx, cred, mailbox, uid)
|
||||
}
|
||||
|
||||
func TestServiceListMailboxesUsesBackend(t *testing.T) {
|
||||
svc := NewService(backendStub{})
|
||||
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
|
||||
},
|
||||
})
|
||||
|
||||
boxes, err := svc.ListMailboxes(context.Background(), secretstore.Credential{
|
||||
Host: "imap.example.com",
|
||||
|
|
@ -36,3 +56,138 @@ func TestServiceListMailboxesUsesBackend(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,21 +11,25 @@ type Mailbox struct {
|
|||
}
|
||||
|
||||
type MessageSummary struct {
|
||||
ID string `json:"id"`
|
||||
UID uint32 `json:"uid"`
|
||||
Subject string `json:"subject"`
|
||||
From string `json:"from"`
|
||||
UID uint32 `json:"uid"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID string `json:"id"`
|
||||
Mailbox string `json:"mailbox"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
UID uint32 `json:"uid"`
|
||||
Mailbox string `json:"mailbox"`
|
||||
Headers []Header `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type Backend interface {
|
||||
ListMailboxes(context.Context, secretstore.Credential) ([]Mailbox, error)
|
||||
ListMessages(context.Context, secretstore.Credential, string, int) ([]MessageSummary, error)
|
||||
GetMessage(context.Context, secretstore.Credential, string, string) (Message, error)
|
||||
GetMessage(context.Context, secretstore.Credential, string, uint32) (Message, error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue