603 lines
15 KiB
Go
603 lines
15 KiB
Go
package imap
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCanonicalFlag(t *testing.T) {
|
|
if got := CanonicalFlag("\\SEEN"); got != SeenFlag {
|
|
t.Errorf("Invalid canonical flag: expected %q but got %q", SeenFlag, got)
|
|
}
|
|
|
|
if got := CanonicalFlag("Junk"); got != "junk" {
|
|
t.Errorf("Invalid canonical flag: expected %q but got %q", "junk", got)
|
|
}
|
|
}
|
|
|
|
func TestNewMessage(t *testing.T) {
|
|
msg := NewMessage(42, []FetchItem{FetchBodyStructure, FetchFlags})
|
|
|
|
expected := &Message{
|
|
SeqNum: 42,
|
|
Items: map[FetchItem]interface{}{FetchBodyStructure: nil, FetchFlags: nil},
|
|
Body: make(map[*BodySectionName]Literal),
|
|
itemsOrder: []FetchItem{FetchBodyStructure, FetchFlags},
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, msg) {
|
|
t.Errorf("Invalid message: expected \n%+v\n but got \n%+v", expected, msg)
|
|
}
|
|
}
|
|
|
|
func formatFields(fields []interface{}) (string, error) {
|
|
b := &bytes.Buffer{}
|
|
w := NewWriter(b)
|
|
|
|
if err := w.writeList(fields); err != nil {
|
|
return "", fmt.Errorf("Cannot format \n%+v\n got error: \n%v", fields, err)
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
var messageTests = []struct {
|
|
message *Message
|
|
fields []interface{}
|
|
}{
|
|
{
|
|
message: &Message{
|
|
Items: map[FetchItem]interface{}{
|
|
FetchEnvelope: nil,
|
|
FetchBody: nil,
|
|
FetchFlags: nil,
|
|
FetchRFC822Size: nil,
|
|
FetchUid: nil,
|
|
},
|
|
Body: map[*BodySectionName]Literal{},
|
|
Envelope: envelopeTests[0].envelope,
|
|
BodyStructure: bodyStructureTests[0].bodyStructure,
|
|
Flags: []string{SeenFlag, AnsweredFlag},
|
|
Size: 4242,
|
|
Uid: 2424,
|
|
itemsOrder: []FetchItem{FetchEnvelope, FetchBody, FetchFlags, FetchRFC822Size, FetchUid},
|
|
},
|
|
fields: []interface{}{
|
|
"ENVELOPE", envelopeTests[0].fields,
|
|
"BODY", bodyStructureTests[0].fields,
|
|
"FLAGS", []interface{}{Atom(SeenFlag), Atom(AnsweredFlag)},
|
|
"RFC822.SIZE", "4242",
|
|
"UID", "2424",
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestMessage_Parse(t *testing.T) {
|
|
for i, test := range messageTests {
|
|
m := &Message{}
|
|
if err := m.Parse(test.fields); err != nil {
|
|
t.Errorf("Cannot parse message for #%v: %v", i, err)
|
|
} else if !reflect.DeepEqual(m, test.message) {
|
|
t.Errorf("Invalid parsed message for #%v: got \n%+v\n but expected \n%+v", i, m, test.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMessage_Format(t *testing.T) {
|
|
for i, test := range messageTests {
|
|
fields := test.message.Format()
|
|
|
|
got, err := formatFields(fields)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
expected, _ := formatFields(test.fields)
|
|
|
|
if got != expected {
|
|
t.Errorf("Invalid message fields for #%v: got \n%v\n but expected \n%v", i, got, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
var bodySectionNameTests = []struct {
|
|
raw string
|
|
parsed *BodySectionName
|
|
formatted string
|
|
}{
|
|
{
|
|
raw: "BODY[]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{}},
|
|
},
|
|
{
|
|
raw: "RFC822",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{}},
|
|
formatted: "BODY[]",
|
|
},
|
|
{
|
|
raw: "BODY[HEADER]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier}},
|
|
},
|
|
{
|
|
raw: "BODY.PEEK[]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{}, Peek: true},
|
|
},
|
|
{
|
|
raw: "BODY[TEXT]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: TextSpecifier}},
|
|
},
|
|
{
|
|
raw: "RFC822.TEXT",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: TextSpecifier}},
|
|
formatted: "BODY[TEXT]",
|
|
},
|
|
{
|
|
raw: "RFC822.HEADER",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier}, Peek: true},
|
|
formatted: "BODY.PEEK[HEADER]",
|
|
},
|
|
{
|
|
raw: "BODY[]<0.512>",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{}, Partial: []int{0, 512}},
|
|
},
|
|
{
|
|
raw: "BODY[]<512>",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{}, Partial: []int{512}},
|
|
},
|
|
{
|
|
raw: "BODY[1.2.3]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Path: []int{1, 2, 3}}},
|
|
},
|
|
{
|
|
raw: "BODY[1.2.3.HEADER]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Path: []int{1, 2, 3}}},
|
|
},
|
|
{
|
|
raw: "BODY[5.MIME]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: MIMESpecifier, Path: []int{5}}},
|
|
},
|
|
{
|
|
raw: "BODY[HEADER.FIELDS (From To)]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"From", "To"}}},
|
|
},
|
|
{
|
|
raw: "BODY[HEADER.FIELDS.NOT (Content-Id)]",
|
|
parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
|
|
},
|
|
}
|
|
|
|
func TestNewBodySectionName(t *testing.T) {
|
|
for i, test := range bodySectionNameTests {
|
|
bsn, err := ParseBodySectionName(FetchItem(test.raw))
|
|
if err != nil {
|
|
t.Errorf("Cannot parse #%v: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
if !reflect.DeepEqual(bsn.BodyPartName, test.parsed.BodyPartName) {
|
|
t.Errorf("Invalid body part name for #%v: %#+v", i, bsn.BodyPartName)
|
|
} else if bsn.Peek != test.parsed.Peek {
|
|
t.Errorf("Invalid peek value for #%v: %#+v", i, bsn.Peek)
|
|
} else if !reflect.DeepEqual(bsn.Partial, test.parsed.Partial) {
|
|
t.Errorf("Invalid partial for #%v: %#+v", i, bsn.Partial)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBodySectionName_String(t *testing.T) {
|
|
for i, test := range bodySectionNameTests {
|
|
s := string(test.parsed.FetchItem())
|
|
|
|
expected := test.formatted
|
|
if expected == "" {
|
|
expected = test.raw
|
|
}
|
|
|
|
if expected != s {
|
|
t.Errorf("Invalid body section name for #%v: got %v but expected %v", i, s, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBodySectionName_ExtractPartial(t *testing.T) {
|
|
tests := []struct {
|
|
bsn string
|
|
whole string
|
|
partial string
|
|
}{
|
|
{
|
|
bsn: "BODY[]",
|
|
whole: "Hello World!",
|
|
partial: "Hello World!",
|
|
},
|
|
{
|
|
bsn: "BODY[]<6.5>",
|
|
whole: "Hello World!",
|
|
partial: "World",
|
|
},
|
|
{
|
|
bsn: "BODY[]<6.1000>",
|
|
whole: "Hello World!",
|
|
partial: "World!",
|
|
},
|
|
{
|
|
bsn: "BODY[]<0.1>",
|
|
whole: "Hello World!",
|
|
partial: "H",
|
|
},
|
|
{
|
|
bsn: "BODY[]<1000.2000>",
|
|
whole: "Hello World!",
|
|
partial: "",
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
bsn, err := ParseBodySectionName(FetchItem(test.bsn))
|
|
if err != nil {
|
|
t.Errorf("Cannot parse body section name #%v: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
partial := string(bsn.ExtractPartial([]byte(test.whole)))
|
|
if partial != test.partial {
|
|
t.Errorf("Invalid partial for #%v: got %v but expected %v", i, partial, test.partial)
|
|
}
|
|
}
|
|
}
|
|
|
|
var t = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("", -6*60*60))
|
|
|
|
var envelopeTests = []struct {
|
|
envelope *Envelope
|
|
fields []interface{}
|
|
}{
|
|
{
|
|
envelope: &Envelope{
|
|
Date: t,
|
|
Subject: "Hello World!",
|
|
From: []*Address{addrTests[0].addr},
|
|
Sender: []*Address{},
|
|
ReplyTo: []*Address{},
|
|
To: []*Address{},
|
|
Cc: []*Address{},
|
|
Bcc: []*Address{},
|
|
InReplyTo: "42@example.org",
|
|
MessageId: "43@example.org",
|
|
},
|
|
fields: []interface{}{
|
|
"Tue, 10 Nov 2009 23:00:00 -0600",
|
|
"Hello World!",
|
|
[]interface{}{addrTests[0].fields},
|
|
[]interface{}{},
|
|
[]interface{}{},
|
|
[]interface{}{},
|
|
[]interface{}{},
|
|
[]interface{}{},
|
|
"42@example.org",
|
|
"43@example.org",
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestEnvelope_Parse(t *testing.T) {
|
|
for i, test := range envelopeTests {
|
|
e := &Envelope{}
|
|
if err := e.Parse(test.fields); err != nil {
|
|
t.Error("Error parsing envelope:", err)
|
|
} else if !reflect.DeepEqual(e, test.envelope) {
|
|
t.Errorf("Invalid envelope for #%v: got %v but expected %v", i, e, test.envelope)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnvelope_Parse_literal(t *testing.T) {
|
|
subject := "Hello World!"
|
|
l := bytes.NewBufferString(subject)
|
|
fields := []interface{}{
|
|
"Tue, 10 Nov 2009 23:00:00 -0600",
|
|
l,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
"42@example.org",
|
|
"43@example.org",
|
|
}
|
|
|
|
e := &Envelope{}
|
|
if err := e.Parse(fields); err != nil {
|
|
t.Error("Error parsing envelope:", err)
|
|
} else if e.Subject != subject {
|
|
t.Errorf("Invalid envelope subject: got %v but expected %v", e.Subject, subject)
|
|
}
|
|
}
|
|
|
|
func TestEnvelope_Format(t *testing.T) {
|
|
for i, test := range envelopeTests {
|
|
fields := test.envelope.Format()
|
|
|
|
got, err := formatFields(fields)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
expected, _ := formatFields(test.fields)
|
|
|
|
if got != expected {
|
|
t.Errorf("Invalid envelope fields for #%v: got %v but expected %v", i, got, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
var addrTests = []struct {
|
|
fields []interface{}
|
|
addr *Address
|
|
}{
|
|
{
|
|
fields: []interface{}{"The NSA", nil, "root", "nsa.gov"},
|
|
addr: &Address{
|
|
PersonalName: "The NSA",
|
|
MailboxName: "root",
|
|
HostName: "nsa.gov",
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestAddress_Parse(t *testing.T) {
|
|
for i, test := range addrTests {
|
|
addr := &Address{}
|
|
|
|
if err := addr.Parse(test.fields); err != nil {
|
|
t.Error("Error parsing address:", err)
|
|
} else if !reflect.DeepEqual(addr, test.addr) {
|
|
t.Errorf("Invalid address for #%v: got %v but expected %v", i, addr, test.addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddress_Format(t *testing.T) {
|
|
for i, test := range addrTests {
|
|
fields := test.addr.Format()
|
|
if !reflect.DeepEqual(fields, test.fields) {
|
|
t.Errorf("Invalid address fields for #%v: got %v but expected %v", i, fields, test.fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddressList(t *testing.T) {
|
|
fields := make([]interface{}, len(addrTests))
|
|
addrs := make([]*Address, len(addrTests))
|
|
for i, test := range addrTests {
|
|
fields[i] = test.fields
|
|
addrs[i] = test.addr
|
|
}
|
|
|
|
gotAddrs := ParseAddressList(fields)
|
|
if !reflect.DeepEqual(gotAddrs, addrs) {
|
|
t.Error("Invalid address list: got", gotAddrs, "but expected", addrs)
|
|
}
|
|
|
|
gotFields := FormatAddressList(addrs)
|
|
if !reflect.DeepEqual(gotFields, fields) {
|
|
t.Error("Invalid address list fields: got", gotFields, "but expected", fields)
|
|
}
|
|
}
|
|
|
|
var paramsListTest = []struct {
|
|
fields []interface{}
|
|
params map[string]string
|
|
}{
|
|
{
|
|
fields: nil,
|
|
params: map[string]string{},
|
|
},
|
|
{
|
|
fields: []interface{}{"a", Quoted("b")},
|
|
params: map[string]string{"a": "b"},
|
|
},
|
|
}
|
|
|
|
func TestParseParamList(t *testing.T) {
|
|
for i, test := range paramsListTest {
|
|
if params, err := ParseParamList(test.fields); err != nil {
|
|
t.Errorf("Cannot parse params fields for #%v: %v", i, err)
|
|
} else if !reflect.DeepEqual(params, test.params) {
|
|
t.Errorf("Invalid params for #%v: got %v but expected %v", i, params, test.params)
|
|
}
|
|
}
|
|
|
|
// Malformed params lists
|
|
|
|
fields := []interface{}{"cc", []interface{}{"dille"}}
|
|
if params, err := ParseParamList(fields); err == nil {
|
|
t.Error("Parsed invalid params list:", params)
|
|
}
|
|
|
|
fields = []interface{}{"cc"}
|
|
if params, err := ParseParamList(fields); err == nil {
|
|
t.Error("Parsed invalid params list:", params)
|
|
}
|
|
}
|
|
|
|
func TestFormatParamList(t *testing.T) {
|
|
for i, test := range paramsListTest {
|
|
fields := FormatParamList(test.params)
|
|
|
|
if !reflect.DeepEqual(fields, test.fields) {
|
|
t.Errorf("Invalid params fields for #%v: got %v but expected %v", i, fields, test.fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
var bodyStructureTests = []struct {
|
|
fields []interface{}
|
|
bodyStructure *BodyStructure
|
|
}{
|
|
{
|
|
fields: []interface{}{"image", "jpeg", []interface{}{}, "<foo4%25foo1@bar.net>", "A picture of cat", "base64", "4242"},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "image",
|
|
MIMESubType: "jpeg",
|
|
Params: map[string]string{},
|
|
Id: "<foo4%25foo1@bar.net>",
|
|
Description: "A picture of cat",
|
|
Encoding: "base64",
|
|
Size: 4242,
|
|
},
|
|
},
|
|
{
|
|
fields: []interface{}{"text", "plain", []interface{}{"charset", Quoted("utf-8")}, nil, nil, "us-ascii", "42", "2"},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "text",
|
|
MIMESubType: "plain",
|
|
Params: map[string]string{"charset": "utf-8"},
|
|
Encoding: "us-ascii",
|
|
Size: 42,
|
|
Lines: 2,
|
|
},
|
|
},
|
|
{
|
|
fields: []interface{}{
|
|
"message", "rfc822", []interface{}{}, nil, nil, "us-ascii", "42",
|
|
(&Envelope{}).Format(),
|
|
(&BodyStructure{}).Format(),
|
|
"67",
|
|
},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "message",
|
|
MIMESubType: "rfc822",
|
|
Params: map[string]string{},
|
|
Encoding: "us-ascii",
|
|
Size: 42,
|
|
Lines: 67,
|
|
Envelope: &Envelope{
|
|
From: []*Address{},
|
|
Sender: []*Address{},
|
|
ReplyTo: []*Address{},
|
|
To: []*Address{},
|
|
Cc: []*Address{},
|
|
Bcc: []*Address{},
|
|
},
|
|
BodyStructure: &BodyStructure{
|
|
Params: map[string]string{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
fields: []interface{}{
|
|
"application", "pdf", []interface{}{}, nil, nil, "base64", "4242",
|
|
"e0323a9039add2978bf5b49550572c7c",
|
|
[]interface{}{"attachment", []interface{}{"filename", Quoted("document.pdf")}},
|
|
[]interface{}{"en-US"}, []interface{}{},
|
|
},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "application",
|
|
MIMESubType: "pdf",
|
|
Params: map[string]string{},
|
|
Encoding: "base64",
|
|
Size: 4242,
|
|
Extended: true,
|
|
MD5: "e0323a9039add2978bf5b49550572c7c",
|
|
Disposition: "attachment",
|
|
DispositionParams: map[string]string{"filename": "document.pdf"},
|
|
Language: []string{"en-US"},
|
|
Location: []string{},
|
|
},
|
|
},
|
|
{
|
|
fields: []interface{}{
|
|
[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", "87", "22"},
|
|
[]interface{}{"text", "html", []interface{}{}, nil, nil, "us-ascii", "106", "36"},
|
|
"alternative",
|
|
},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "multipart",
|
|
MIMESubType: "alternative",
|
|
Params: map[string]string{},
|
|
Parts: []*BodyStructure{
|
|
{
|
|
MIMEType: "text",
|
|
MIMESubType: "plain",
|
|
Params: map[string]string{},
|
|
Encoding: "us-ascii",
|
|
Size: 87,
|
|
Lines: 22,
|
|
},
|
|
{
|
|
MIMEType: "text",
|
|
MIMESubType: "html",
|
|
Params: map[string]string{},
|
|
Encoding: "us-ascii",
|
|
Size: 106,
|
|
Lines: 36,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
fields: []interface{}{
|
|
[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", "87", "22"},
|
|
"alternative", []interface{}{"hello", Quoted("world")},
|
|
[]interface{}{"inline", []interface{}{}},
|
|
[]interface{}{"en-US"}, []interface{}{},
|
|
},
|
|
bodyStructure: &BodyStructure{
|
|
MIMEType: "multipart",
|
|
MIMESubType: "alternative",
|
|
Params: map[string]string{"hello": "world"},
|
|
Parts: []*BodyStructure{
|
|
{
|
|
MIMEType: "text",
|
|
MIMESubType: "plain",
|
|
Params: map[string]string{},
|
|
Encoding: "us-ascii",
|
|
Size: 87,
|
|
Lines: 22,
|
|
},
|
|
},
|
|
Extended: true,
|
|
Disposition: "inline",
|
|
DispositionParams: map[string]string{},
|
|
Language: []string{"en-US"},
|
|
Location: []string{},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestBodyStructure_Parse(t *testing.T) {
|
|
for i, test := range bodyStructureTests {
|
|
bs := &BodyStructure{}
|
|
|
|
if err := bs.Parse(test.fields); err != nil {
|
|
t.Errorf("Cannot parse #%v: %v", i, err)
|
|
} else if !reflect.DeepEqual(bs, test.bodyStructure) {
|
|
t.Errorf("Invalid body structure for #%v: got \n%+v\n but expected \n%+v", i, bs, test.bodyStructure)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBodyStructure_Format(t *testing.T) {
|
|
for i, test := range bodyStructureTests {
|
|
fields := test.bodyStructure.Format()
|
|
got, err := formatFields(fields)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
expected, _ := formatFields(test.fields)
|
|
|
|
if got != expected {
|
|
t.Errorf("Invalid body structure fields for #%v: has \n%v\n but expected \n%v", i, got, expected)
|
|
}
|
|
}
|
|
}
|