mirror of
https://github.com/jhillyerd/inbucket.git
synced 2026-10-29 05:32:31 +00:00
bd4cc4b842
* feat(luahost): capture config settings set by Lua scripts Add inbucket.config, a Lua-settable settings namespace, with its first field decode_headers registering additional message headers for Inbucket to decode. Values are captured once at startup from the first pooled LState and exposed to the assembly via Host.ScriptConfig(). Settings that only have value to the Lua script belong here rather than in env config. No consumers of decode_headers yet; that follows. * feat(message): expose decoded headers to extensions StoreManager gains ExtraDecodeHeaders, passed through to enmime.DecodeHeaders, and the decoded header set (defaults plus any registered extras) is attached to the InboundMessage event as Headers (net/textproto.MIMEHeader). Lua scripts access it via the read-only inbound_message.header field, added with the new inbound_header binding. Lookups are exact-then- case-insensitive, since enmime canonicalizes keys with email-specific rules that differ from net/textproto. FullAssembly wires ScriptConfig().DecodeHeaders into StoreManager. Part of #627. * docs(extension): document shared and untrusted header semantics Reviewer note: BeforeMessageStored listeners receive the same decoded header map, so a Go listener's mutations are visible to later listeners. Also record that sender-supplied header values (Authentication-Results, X-Spam-Status) are untrusted unless the deployment controls provenance. Part of #627. --------- Co-authored-by: hillyerd-bot <hillyerd-bot@users.noreply.github.com>
655 lines
20 KiB
Go
655 lines
20 KiB
Go
package message_test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/mail"
|
|
"net/textproto"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/inbucket/inbucket/v3/pkg/config"
|
|
"github.com/inbucket/inbucket/v3/pkg/extension"
|
|
"github.com/inbucket/inbucket/v3/pkg/extension/event"
|
|
"github.com/inbucket/inbucket/v3/pkg/message"
|
|
"github.com/inbucket/inbucket/v3/pkg/policy"
|
|
"github.com/inbucket/inbucket/v3/pkg/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDeliverStoresMessages(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Attempt to deliver a message to two mailboxes.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte(`From: from@example.com
|
|
To: u1@example.com, u2@example.com
|
|
Subject: tsub
|
|
|
|
test email`),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assertMessageCount(t, sm, "u1@example.com", 1)
|
|
assertMessageCount(t, sm, "u2@example.com", 1)
|
|
}
|
|
|
|
func TestDeliverStoresMessageNoFromHeader(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Attempt to deliver a message to two mailboxes.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte(`To: u1@example.com, u2@example.com
|
|
Subject: tsub
|
|
|
|
test email`),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assertMessageCount(t, sm, "u1@example.com", 1)
|
|
assertMessageCount(t, sm, "u2@example.com", 1)
|
|
}
|
|
|
|
func TestDeliverStoresMessageNoToHeader(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Attempt to deliver a message to two mailboxes.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte(`From: from@example.com
|
|
Subject: tsub
|
|
|
|
test email`),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assertMessageCount(t, sm, "u1@example.com", 1)
|
|
assertMessageCount(t, sm, "u2@example.com", 1)
|
|
}
|
|
|
|
func TestDeliverRespectsRecipientPolicy(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Attempt to deliver a message to two mailboxes.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@nostore.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Expect empty mailbox for nostore domain.
|
|
assertMessageCount(t, sm, "u1@nostore.com", 0)
|
|
assertMessageCount(t, sm, "u2@example.com", 1)
|
|
}
|
|
|
|
// Delivers a message with an X-Spam-Status header, capturing the
|
|
// InboundMessage event seen by a listener.
|
|
func deliverWithSpamHeader(t *testing.T, sm *message.StoreManager, extHost *extension.Host) event.InboundMessage {
|
|
t.Helper()
|
|
|
|
var got event.InboundMessage
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
got = msg
|
|
return nil
|
|
})
|
|
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1},
|
|
"Received: xyz\r\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\nX-Spam-Status: Yes\nX-Not-Registered: no\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return got
|
|
}
|
|
|
|
func TestDeliverDecodesExtraHeaders(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
sm.ExtraDecodeHeaders = []string{"x-spam-status"} // Not canonical on purpose.
|
|
|
|
got := deliverWithSpamHeader(t, sm, extHost)
|
|
|
|
// Registered extra header is decoded, with a canonical key.
|
|
assert.Equal(t, "Yes", got.Headers.Get("X-Spam-Status"))
|
|
|
|
// Default set is still decoded.
|
|
assert.Equal(t, "tsub", got.Headers.Get("Subject"))
|
|
assert.Equal(t, "from@example.com", got.Headers.Get("From"))
|
|
|
|
// Unregistered headers are not decoded.
|
|
_, ok := got.Headers[textproto.CanonicalMIMEHeaderKey("X-Not-Registered")]
|
|
assert.False(t, ok, "unregistered header should not be decoded")
|
|
}
|
|
|
|
func TestDeliverDefaultHeaderSetOnly(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
got := deliverWithSpamHeader(t, sm, extHost)
|
|
|
|
// Without registration, the extra header stays undecoded.
|
|
_, ok := got.Headers[textproto.CanonicalMIMEHeaderKey("X-Spam-Status")]
|
|
assert.False(t, ok, "unregistered header should not be decoded")
|
|
|
|
// Defaults are present.
|
|
assert.Equal(t, "tsub", got.Headers.Get("Subject"))
|
|
}
|
|
|
|
func TestDeliverEmitsBeforeMessageStoredEventToHeader(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive event.
|
|
var got *event.InboundMessage
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
got = &msg
|
|
return nil
|
|
})
|
|
|
|
// Deliver a message to trigger event, To header differs from RCPT TO.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte(`From: from@example.com
|
|
To: u1@example.com, u3@external.com
|
|
Subject: tsub
|
|
|
|
test email`),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
require.NotNil(t, got, "BeforeMessageStored listener did not receive InboundMessage")
|
|
assert.Equal(t, []string{"u1@example.com", "u2@example.com"}, got.Mailboxes, "Mailboxes not equal")
|
|
assert.Equal(t, &mail.Address{Name: "", Address: "from@example.com"}, got.From, "From not equal")
|
|
assert.Equal(t, []*mail.Address{
|
|
{Name: "", Address: "u1@example.com"},
|
|
{Name: "", Address: "u3@external.com"},
|
|
}, got.To, "To not equal")
|
|
assert.Equal(t, "tsub", got.Subject, "Subject not equal")
|
|
assert.Equal(t, int64(84), got.Size, "Size not equal")
|
|
}
|
|
|
|
func TestDeliverEmitsBeforeMessageStoredEventRcptTo(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive event.
|
|
var got *event.InboundMessage
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
got = &msg
|
|
return nil
|
|
})
|
|
|
|
// Deliver a message to trigger event, lacks To header.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
require.NotNil(t, got, "BeforeMessageStored listener did not receive InboundMessage")
|
|
assert.Equal(t, []string{"u1@example.com", "u2@example.com"}, got.Mailboxes, "Mailboxes not equal")
|
|
assert.Equal(t, &mail.Address{Name: "", Address: "from@example.com"}, got.From, "From not equal")
|
|
assert.Equal(t, []*mail.Address{
|
|
{Name: "", Address: "u1@example.com"},
|
|
{Name: "", Address: "u2@example.com"},
|
|
}, got.To, "To not equal")
|
|
assert.Equal(t, "tsub", got.Subject, "Subject not equal")
|
|
assert.Equal(t, int64(48), got.Size, "Size not equal")
|
|
}
|
|
|
|
func TestDeliverUsesBeforeMessageStoredEventResponseMailboxes(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive event.
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
// Listener rewrites destination mailboxes.
|
|
resp := msg
|
|
resp.Mailboxes = []string{"new1@example.com", "new2@nostore.com"}
|
|
return &resp
|
|
})
|
|
|
|
// Deliver a message to trigger event.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\r\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Expect messages in only the mailboxes in the event response, and for the DiscardDomains
|
|
// policy to be ignored for nostore.com.
|
|
assertMessageCount(t, sm, "u1@example.com", 0)
|
|
assertMessageCount(t, sm, "u2@example.com", 0)
|
|
assertMessageCount(t, sm, "new1@example.com", 1)
|
|
assertMessageCount(t, sm, "new2@nostore.com", 1)
|
|
}
|
|
|
|
func TestDeliverUsesBeforeMessageStoredEventResponseMailboxesEmpty(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive event.
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
// Listener clears destination mailboxes.
|
|
resp := msg
|
|
resp.Mailboxes = []string{}
|
|
return &resp
|
|
})
|
|
|
|
// Deliver a message to trigger event.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\r\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Expect no messages the mailboxes.
|
|
assertMessageCount(t, sm, "u1@example.com", 0)
|
|
assertMessageCount(t, sm, "u2@example.com", 0)
|
|
}
|
|
|
|
func TestDeliverUsesBeforeMessageStoredEventResponseFields(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive event.
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
// Listener rewrites destination mailboxes.
|
|
msg.Subject = "event subj"
|
|
msg.From = &mail.Address{Address: "from@event.com", Name: "From Event"}
|
|
|
|
// Changing To does not affect destination mailbox(es).
|
|
msg.To = []*mail.Address{
|
|
{Address: "to@event.com", Name: "To Event"},
|
|
{Address: "to2@event.com", Name: "To 2 Event"},
|
|
}
|
|
|
|
// Size is read only, should have no effect.
|
|
msg.Size = 12345
|
|
|
|
return &msg
|
|
})
|
|
|
|
// Deliver a message to trigger event.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1},
|
|
"Received: xyz\r\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify single message stored.
|
|
metadata, err := sm.GetMetadata("u1@example.com")
|
|
require.NoError(t, err)
|
|
require.Len(t, metadata, 1, "mailbox has incorrect # of messages")
|
|
got := metadata[0]
|
|
|
|
// Verify metadata fields were overridden by event response values.
|
|
assert.Equal(t, "event subj", got.Subject, "Subject didn't match")
|
|
assert.Equal(t, "from@event.com", got.From.Address, "From Address didn't match")
|
|
assert.Equal(t, "From Event", got.From.Name, "From Name didn't match")
|
|
require.Len(t, got.To, 2)
|
|
assert.Equal(t, "to@event.com", got.To[0].Address, "To Address didn't match")
|
|
assert.Equal(t, "To Event", got.To[0].Name, "To Name didn't match")
|
|
assert.Equal(t, "to2@event.com", got.To[1].Address, "To Address didn't match")
|
|
assert.Equal(t, "To 2 Event", got.To[1].Name, "To Name didn't match")
|
|
assert.NotEqual(t, 12345, got.Size, "Size is read only")
|
|
}
|
|
|
|
func TestDeliverEmitsAfterMessageStoredEvent(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
listener := extHost.Events.AfterMessageStored.AsyncTestListener("manager", 1)
|
|
|
|
// Deliver a message to trigger event.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip, _ := sm.AddrPolicy.NewRecipient("to@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip},
|
|
"Received: xyz\n",
|
|
[]byte("From: from@example.com\nSubject: events\n\ntest email."),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := listener()
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, got, "No event received, or it was nil")
|
|
assertMessageCount(t, sm, "to@example.com", 1)
|
|
|
|
// Verify event content.
|
|
assert.Equal(t, "to@example.com", got.Mailbox)
|
|
assert.Equal(t, "from@example.com", got.From.Address)
|
|
|
|
assert.WithinDuration(t, time.Now(), got.Date, 5*time.Second)
|
|
assert.Equal(t, "events", got.Subject, nil)
|
|
assert.Equal(t, int64(51), got.Size)
|
|
|
|
require.Len(t, got.To, 1)
|
|
assert.Equal(t, "to@example.com", got.To[0].Address)
|
|
}
|
|
|
|
func TestDeliverBeforeAndAfterMessageStoredEvents(t *testing.T) {
|
|
sm, extHost := testStoreManager()
|
|
|
|
// Register function to receive Before event.
|
|
extHost.Events.BeforeMessageStored.AddListener(
|
|
"test",
|
|
func(msg event.InboundMessage) *event.InboundMessage {
|
|
// Listener rewrites destination mailboxes.
|
|
resp := msg
|
|
resp.Mailboxes = []string{"new1@example.com", "new2@example.com"}
|
|
return &resp
|
|
})
|
|
|
|
// After event listener.
|
|
listener := extHost.Events.AfterMessageStored.AsyncTestListener("manager", 2)
|
|
|
|
// Deliver a message to trigger events.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
recip2, _ := sm.AddrPolicy.NewRecipient("u2@example.com")
|
|
if err := sm.Deliver(
|
|
origin,
|
|
[]*policy.Recipient{recip1, recip2},
|
|
"Received: xyz\r\n",
|
|
[]byte("From: from@example.com\nSubject: tsub\n\ntest email"),
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Confirm mailbox names overridden by `Before` were sent to `After` event. Order is
|
|
// not guaranteed.
|
|
got1, err := listener()
|
|
require.NoError(t, err)
|
|
got2, err := listener()
|
|
require.NoError(t, err)
|
|
got := []string{got1.Mailbox, got2.Mailbox}
|
|
assert.Contains(t, got, "new1@example.com")
|
|
assert.Contains(t, got, "new2@example.com")
|
|
}
|
|
|
|
func TestGetMessage(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Add a test message.
|
|
subject := "getMessage1"
|
|
id := addTestMessage(sm, "get-box", subject)
|
|
|
|
// Verify retrieval of the test message.
|
|
msg, err := sm.GetMessage("get-box", id)
|
|
require.NoError(t, err, "GetMessage must succeed")
|
|
require.NotNil(t, msg, "GetMessage must return a result")
|
|
assert.Equal(t, subject, msg.Subject)
|
|
assert.Contains(t, msg.Text(), fmt.Sprintf("about %q", subject))
|
|
}
|
|
|
|
func TestMarkSeen(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Add a test message.
|
|
subject := "getMessage1"
|
|
id := addTestMessage(sm, "seen-box", subject)
|
|
|
|
// Verify test message unseen.
|
|
msg, err := sm.GetMessage("seen-box", id)
|
|
require.NoError(t, err, "GetMessage must succeed")
|
|
require.NotNil(t, msg, "GetMessage must return a result")
|
|
assert.False(t, msg.Seen, "msg should be unseen")
|
|
|
|
err = sm.MarkSeen("seen-box", id)
|
|
require.NoError(t, err, "MarkSeen should succeed")
|
|
|
|
// Verify test message seen.
|
|
msg, err = sm.GetMessage("seen-box", id)
|
|
require.NoError(t, err, "GetMessage must succeed")
|
|
require.NotNil(t, msg, "GetMessage must return a result")
|
|
assert.True(t, msg.Seen, "msg should have been seen")
|
|
}
|
|
|
|
func TestRemoveMessage(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Add test messages.
|
|
id1 := addTestMessage(sm, "rm-box", "subject 1")
|
|
id2 := addTestMessage(sm, "rm-box", "subject 2")
|
|
id3 := addTestMessage(sm, "rm-box", "subject 3")
|
|
got, err := sm.GetMetadata("rm-box")
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 3)
|
|
|
|
// Delete message 2 and verify.
|
|
err = sm.RemoveMessage("rm-box", id2)
|
|
require.NoError(t, err)
|
|
got, err = sm.GetMetadata("rm-box")
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 2, "Should be 2 messages remaining")
|
|
|
|
gotIDs := make([]string, 0, 3)
|
|
for _, msg := range got {
|
|
gotIDs = append(gotIDs, msg.ID)
|
|
}
|
|
assert.Contains(t, gotIDs, id1)
|
|
assert.Contains(t, gotIDs, id3)
|
|
}
|
|
|
|
func TestPurgeMessages(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
// Add test messages.
|
|
_ = addTestMessage(sm, "purge-box", "subject 1")
|
|
_ = addTestMessage(sm, "purge-box", "subject 2")
|
|
_ = addTestMessage(sm, "purge-box", "subject 3")
|
|
got, err := sm.GetMetadata("purge-box")
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 3)
|
|
|
|
// Purge and verify.
|
|
err = sm.PurgeMessages("purge-box")
|
|
require.NoError(t, err)
|
|
got, err = sm.GetMetadata("purge-box")
|
|
require.NoError(t, err)
|
|
assert.Empty(t, got, "Purge should remove all mailbox messages")
|
|
}
|
|
|
|
func TestSourceReader(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
recvdHeader := "Received: xyz\n"
|
|
msgSource := `From: from@example.com
|
|
To: u1@example.com, u2@example.com
|
|
Subject: tsub
|
|
|
|
test email`
|
|
|
|
// Deliver mesage.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("from@example.com")
|
|
recip1, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
err := sm.Deliver(origin, []*policy.Recipient{recip1}, recvdHeader, []byte(msgSource))
|
|
require.NoError(t, err)
|
|
|
|
// Find message ID.
|
|
msgs, err := sm.GetMetadata("u1@example.com")
|
|
require.NoError(t, err, "Failed to read mailbox")
|
|
require.Len(t, msgs, 1, "Unexpected mailbox len")
|
|
id := msgs[0].ID
|
|
|
|
// Read back and verify source.
|
|
r, err := sm.SourceReader("u1@example.com", id)
|
|
require.NoError(t, err, "SourceReader must succeed")
|
|
gotBytes, err := io.ReadAll(r)
|
|
require.NoError(t, err, "Failed to read source")
|
|
|
|
got := string(gotBytes)
|
|
assert.Contains(t, got, recvdHeader, "Source should contain received header")
|
|
assert.Contains(t, got, msgSource, "Source should contain original message source")
|
|
}
|
|
|
|
func TestMailboxForAddress(t *testing.T) {
|
|
// Configured for FullNaming.
|
|
sm, _ := testStoreManager()
|
|
|
|
addr := "u1@example.com"
|
|
got, err := sm.MailboxForAddress(addr)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, addr, got, "FullNaming mode should return a full address for mailbox")
|
|
}
|
|
|
|
func TestReturnPath(t *testing.T) {
|
|
sm, _ := testStoreManager()
|
|
|
|
recvdHeader := "Received: xyz\n"
|
|
msgSource := `From: from@example.com
|
|
To: u1@example.com
|
|
Subject: return path
|
|
|
|
test email`
|
|
|
|
// Deliver message.
|
|
origin, _ := sm.AddrPolicy.ParseOrigin("821from@example.com")
|
|
recipient, _ := sm.AddrPolicy.NewRecipient("u1@example.com")
|
|
err := sm.Deliver(origin, []*policy.Recipient{recipient}, recvdHeader, []byte(msgSource))
|
|
require.NoError(t, err)
|
|
|
|
// Find message ID.
|
|
msgs, err := sm.GetMetadata("u1@example.com")
|
|
require.NoError(t, err, "Failed to read mailbox")
|
|
require.Len(t, msgs, 1, "Unexpected mailbox len")
|
|
id := msgs[0].ID
|
|
|
|
// Read back and verify source.
|
|
r, err := sm.SourceReader("u1@example.com", id)
|
|
require.NoError(t, err, "SourceReader must succeed")
|
|
gotBytes, err := io.ReadAll(r)
|
|
require.NoError(t, err, "Failed to read source")
|
|
|
|
got := string(gotBytes)
|
|
assert.Contains(t, got, "Return-Path: <821from@example.com>\r\n", "Source should contain return-path")
|
|
}
|
|
|
|
// Returns an empty StoreManager and extension Host pair, configured for testing.
|
|
func testStoreManager() (*message.StoreManager, *extension.Host) {
|
|
extHost := extension.NewHost()
|
|
|
|
sm := &message.StoreManager{
|
|
AddrPolicy: &policy.Addressing{
|
|
Config: &config.Root{
|
|
MailboxNaming: config.FullNaming,
|
|
SMTP: config.SMTP{
|
|
DefaultAccept: true,
|
|
DefaultStore: true,
|
|
RejectDomains: []string{"noaccept.com"},
|
|
DiscardDomains: []string{"nostore.com"},
|
|
},
|
|
},
|
|
},
|
|
Store: test.NewStore(),
|
|
ExtHost: extHost,
|
|
}
|
|
|
|
return sm, extHost
|
|
}
|
|
|
|
// Adds a test message to the provided store, returning the new message ID.
|
|
func addTestMessage(sm *message.StoreManager, mailbox string, subject string) string {
|
|
from := mail.Address{Name: "From Test", Address: "from@example.com"}
|
|
to := mail.Address{Name: "To Test", Address: "to@example.com"}
|
|
delivery := &message.Delivery{
|
|
Meta: event.MessageMetadata{
|
|
Mailbox: mailbox,
|
|
From: &from,
|
|
To: []*mail.Address{&to},
|
|
Date: time.Now(),
|
|
Subject: subject,
|
|
},
|
|
Reader: strings.NewReader(fmt.Sprintf(
|
|
"From: %s\nTo: %s\nSubject: %s\n\nTest message about %q\n",
|
|
from, to, subject, subject,
|
|
)),
|
|
}
|
|
|
|
id, err := sm.Store.AddMessage(delivery)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
func assertMessageCount(t *testing.T, sm *message.StoreManager, mailbox string, count int) {
|
|
t.Helper()
|
|
|
|
metas, err := sm.GetMetadata(mailbox)
|
|
require.NoError(t, err, "StoreManager GetMetadata failed")
|
|
|
|
got := len(metas)
|
|
if got != count {
|
|
t.Errorf("Mailbox %q got %v messages, wanted %v", mailbox, got, count)
|
|
}
|
|
}
|