perf(mail): fetch only headers for list/search (no body download)
list and search now fetch BODY.PEEK[HEADER] + BODYSTRUCTURE instead of the whole RFC822 message, so listing a large mailbox no longer downloads every message body and attachment. Header parsing reuses the same go-message path (RFC2047 decoding/formatting preserved); has_attachments is derived from the BODYSTRUCTURE tree. FetchFull keeps fetching the full message for get. Validated end-to-end against a live IMAP account: list/search/get output identical to the prior full-fetch behaviour, has_attachments correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
func TestHasAttachment(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
bs *imap.BodyStructure
|
||||
want bool
|
||||
}{
|
||||
{"nil", nil, false},
|
||||
{
|
||||
"single inline text",
|
||||
&imap.BodyStructure{MIMEType: "text", MIMESubType: "plain", Disposition: "inline"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"single attachment",
|
||||
&imap.BodyStructure{MIMEType: "application", MIMESubType: "pdf", Disposition: "attachment"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"disposition case-insensitive",
|
||||
&imap.BodyStructure{MIMEType: "image", MIMESubType: "png", Disposition: "ATTACHMENT"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"multipart with one attachment child",
|
||||
&imap.BodyStructure{
|
||||
MIMEType: "multipart",
|
||||
MIMESubType: "mixed",
|
||||
Parts: []*imap.BodyStructure{
|
||||
{MIMEType: "text", MIMESubType: "plain", Disposition: "inline"},
|
||||
{MIMEType: "application", MIMESubType: "octet-stream", Disposition: "attachment"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"multipart all inline",
|
||||
&imap.BodyStructure{
|
||||
MIMEType: "multipart",
|
||||
MIMESubType: "alternative",
|
||||
Parts: []*imap.BodyStructure{
|
||||
{MIMEType: "text", MIMESubType: "plain", Disposition: "inline"},
|
||||
{MIMEType: "text", MIMESubType: "html", Disposition: "inline"},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"nested multipart with deep attachment",
|
||||
&imap.BodyStructure{
|
||||
MIMEType: "multipart",
|
||||
MIMESubType: "mixed",
|
||||
Parts: []*imap.BodyStructure{
|
||||
{
|
||||
MIMEType: "multipart",
|
||||
MIMESubType: "alternative",
|
||||
Parts: []*imap.BodyStructure{
|
||||
{MIMEType: "text", MIMESubType: "plain", Disposition: "inline"},
|
||||
{MIMEType: "application", MIMESubType: "pdf", Disposition: "attachment"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := hasAttachment(c.bs); got != c.want {
|
||||
t.Errorf("%s: hasAttachment = %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user