mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
test: Finish initial storage test suite, closes #82
This commit is contained in:
5
Makefile
5
Makefile
@@ -3,7 +3,7 @@ SHELL = /bin/sh
|
||||
SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
PKGS := $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
.PHONY: all build clean fmt lint simplify test
|
||||
.PHONY: all build clean fmt lint reflex simplify test
|
||||
|
||||
commands = client inbucket
|
||||
|
||||
@@ -34,3 +34,6 @@ lint:
|
||||
@test -z "$(shell gofmt -l . | tee /dev/stderr)" || echo "[WARN] Fix formatting issues with 'make fmt'"
|
||||
@golint -set_exit_status $(PKGS)
|
||||
@go vet $(PKGS)
|
||||
|
||||
reflex:
|
||||
reflex -r '\.go$$' -- sh -c 'echo; date; echo; go test ./...'
|
||||
|
||||
@@ -22,15 +22,13 @@ import (
|
||||
|
||||
// TestSuite runs storage package test suite on file store.
|
||||
func TestSuite(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
test.StoreSuite(t, ds)
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging.
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure.
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
test.StoreSuite(t, func() (storage.Store, func(), error) {
|
||||
ds, _ := setupDataStore(config.DataStoreConfig{})
|
||||
destroy := func() {
|
||||
teardownDataStore(ds)
|
||||
}
|
||||
return ds, destroy, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Test directory structure created by filestore
|
||||
@@ -111,225 +109,6 @@ func TestFSDirStructure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFSVisitMailboxes tests VisitMailboxes
|
||||
func TestFSVisitMailboxes(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
boxes := []string{"abby", "bill", "christa", "donald", "evelyn"}
|
||||
for _, name := range boxes {
|
||||
// Create day old message
|
||||
date := time.Now().Add(-24 * time.Hour)
|
||||
deliverMessage(ds, name, "Old Message", date)
|
||||
|
||||
// Create current message
|
||||
date = time.Now()
|
||||
deliverMessage(ds, name, "New Message", date)
|
||||
}
|
||||
|
||||
seen := 0
|
||||
err := ds.VisitMailboxes(func(messages []storage.StoreMessage) bool {
|
||||
seen++
|
||||
count := len(messages)
|
||||
if count != 2 {
|
||||
t.Errorf("got: %v messages, want: 2", count)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, seen)
|
||||
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
}
|
||||
}
|
||||
|
||||
// Test delivering several messages to the same mailbox, meanwhile querying its
|
||||
// contents with a new mailbox object each time
|
||||
func TestFSDeliverMany(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
|
||||
mbName := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
|
||||
for i, subj := range subjects {
|
||||
// Check number of messages
|
||||
msgs, err := ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
assert.Equal(t, i, len(msgs), "Expected %v message(s), but got %v", i, len(msgs))
|
||||
|
||||
// Add a message
|
||||
deliverMessage(ds, mbName, subj, time.Now())
|
||||
}
|
||||
|
||||
msgs, err := ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
assert.Equal(t, len(subjects), len(msgs), "Expected %v message(s), but got %v",
|
||||
len(subjects), len(msgs))
|
||||
|
||||
// Confirm delivery order
|
||||
for i, expect := range subjects {
|
||||
subj := msgs[i].Subject()
|
||||
assert.Equal(t, expect, subj, "Expected subject %q, got %q", expect, subj)
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
}
|
||||
}
|
||||
|
||||
// Test deleting messages
|
||||
func TestFSDelete(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
|
||||
mbName := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
|
||||
for _, subj := range subjects {
|
||||
// Add a message
|
||||
deliverMessage(ds, mbName, subj, time.Now())
|
||||
}
|
||||
|
||||
msgs, err := ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
assert.Equal(t, len(subjects), len(msgs), "Expected %v message(s), but got %v",
|
||||
len(subjects), len(msgs))
|
||||
|
||||
// Delete a couple messages
|
||||
err = ds.RemoveMessage(mbName, msgs[1].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ds.RemoveMessage(mbName, msgs[3].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Confirm deletion
|
||||
msgs, err = ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
|
||||
subjects = []string{"alpha", "charlie", "echo"}
|
||||
assert.Equal(t, len(subjects), len(msgs), "Expected %v message(s), but got %v",
|
||||
len(subjects), len(msgs))
|
||||
for i, expect := range subjects {
|
||||
subj := msgs[i].Subject()
|
||||
assert.Equal(t, expect, subj, "Expected subject %q, got %q", expect, subj)
|
||||
}
|
||||
|
||||
// Try appending one more
|
||||
deliverMessage(ds, mbName, "foxtrot", time.Now())
|
||||
|
||||
msgs, err = ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
|
||||
subjects = []string{"alpha", "charlie", "echo", "foxtrot"}
|
||||
assert.Equal(t, len(subjects), len(msgs), "Expected %v message(s), but got %v",
|
||||
len(subjects), len(msgs))
|
||||
for i, expect := range subjects {
|
||||
subj := msgs[i].Subject()
|
||||
assert.Equal(t, expect, subj, "Expected subject %q, got %q", expect, subj)
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
}
|
||||
}
|
||||
|
||||
// Test purging a mailbox
|
||||
func TestFSPurge(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
|
||||
mbName := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
|
||||
for _, subj := range subjects {
|
||||
// Add a message
|
||||
deliverMessage(ds, mbName, subj, time.Now())
|
||||
}
|
||||
|
||||
msgs, err := ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
assert.Equal(t, len(subjects), len(msgs), "Expected %v message(s), but got %v",
|
||||
len(subjects), len(msgs))
|
||||
|
||||
// Purge mailbox
|
||||
err = ds.PurgeMessages(mbName)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Confirm deletion
|
||||
msgs, err = ds.GetMessages(mbName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mbName, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(msgs), 0, "Expected mailbox to have zero messages, got %v", len(msgs))
|
||||
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
}
|
||||
}
|
||||
|
||||
// Test message size calculation
|
||||
func TestFSSize(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
defer teardownDataStore(ds)
|
||||
|
||||
mbName := "fred"
|
||||
subjects := []string{"a", "br", "much longer than the others"}
|
||||
sentIds := make([]string, len(subjects))
|
||||
sentSizes := make([]int64, len(subjects))
|
||||
|
||||
for i, subj := range subjects {
|
||||
// Add a message
|
||||
id, size := deliverMessage(ds, mbName, subj, time.Now())
|
||||
sentIds[i] = id
|
||||
sentSizes[i] = size
|
||||
}
|
||||
|
||||
for i, id := range sentIds {
|
||||
msg, err := ds.GetMessage(mbName, id)
|
||||
assert.Nil(t, err)
|
||||
|
||||
expect := sentSizes[i]
|
||||
size := msg.Size()
|
||||
assert.Equal(t, expect, size, "Expected size of %v, got %v", expect, size)
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
// Wait for handler to finish logging
|
||||
time.Sleep(2 * time.Second)
|
||||
// Dump buffered log data if there was a failure
|
||||
_, _ = io.Copy(os.Stderr, logbuf)
|
||||
}
|
||||
}
|
||||
|
||||
// Test missing files
|
||||
func TestFSMissing(t *testing.T) {
|
||||
ds, logbuf := setupDataStore(config.DataStoreConfig{})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -10,23 +13,37 @@ import (
|
||||
"github.com/jhillyerd/inbucket/pkg/storage"
|
||||
)
|
||||
|
||||
// StoreSuite runs a set of general tests on the provided Store
|
||||
func StoreSuite(t *testing.T, store storage.Store) {
|
||||
// StoreFactory returns a new store for the test suite.
|
||||
type StoreFactory func() (store storage.Store, destroy func(), err error)
|
||||
|
||||
// StoreSuite runs a set of general tests on the provided Store.
|
||||
func StoreSuite(t *testing.T, factory StoreFactory) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
test func(*testing.T, storage.Store)
|
||||
}{
|
||||
{"metadata", testMetadata},
|
||||
{"content", testContent},
|
||||
{"delivery order", testDeliveryOrder},
|
||||
{"size", testSize},
|
||||
{"delete", testDelete},
|
||||
{"purge", testPurge},
|
||||
{"visit mailboxes", testVisitMailboxes},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
store, destroy, err := factory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.test(t, store)
|
||||
destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMetadata(t *testing.T, ds storage.Store) {
|
||||
// Store a message
|
||||
// testMetadata verifies message metadata is stored and retrieved correctly.
|
||||
func testMetadata(t *testing.T, store storage.Store) {
|
||||
mailbox := "testmailbox"
|
||||
from := &mail.Address{Name: "From Person", Address: "from@person.com"}
|
||||
to := []*mail.Address{
|
||||
@@ -38,7 +55,7 @@ func testMetadata(t *testing.T, ds storage.Store) {
|
||||
content := "doesn't matter"
|
||||
delivery := &message.Delivery{
|
||||
Meta: message.Metadata{
|
||||
// ID and Size will be determined by the Store
|
||||
// ID and Size will be determined by the Store.
|
||||
Mailbox: mailbox,
|
||||
From: from,
|
||||
To: to,
|
||||
@@ -47,15 +64,15 @@ func testMetadata(t *testing.T, ds storage.Store) {
|
||||
},
|
||||
Reader: strings.NewReader(content),
|
||||
}
|
||||
id, err := ds.AddMessage(delivery)
|
||||
id, err := store.AddMessage(delivery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id == "" {
|
||||
t.Fatal("Expected AddMessage() to return non-empty ID string")
|
||||
}
|
||||
// Retrieve and validate the message
|
||||
sm, err := ds.GetMessage(mailbox, id)
|
||||
// Retrieve and validate the message.
|
||||
sm, err := store.GetMessage(mailbox, id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,3 +104,227 @@ func testMetadata(t *testing.T, ds storage.Store) {
|
||||
t.Errorf("got size %v, want: %v", sm.Size(), len(content))
|
||||
}
|
||||
}
|
||||
|
||||
// testContent generates some binary content and makes sure it is correctly retrieved.
|
||||
func testContent(t *testing.T, store storage.Store) {
|
||||
content := make([]byte, 5000)
|
||||
for i := 0; i < len(content); i++ {
|
||||
content[i] = byte(i % 256)
|
||||
}
|
||||
mailbox := "testmailbox"
|
||||
from := &mail.Address{Name: "From Person", Address: "from@person.com"}
|
||||
to := []*mail.Address{
|
||||
{Name: "One Person", Address: "one@a.person.com"},
|
||||
}
|
||||
date := time.Now()
|
||||
subject := "fantastic test subject line"
|
||||
delivery := &message.Delivery{
|
||||
Meta: message.Metadata{
|
||||
// ID and Size will be determined by the Store.
|
||||
Mailbox: mailbox,
|
||||
From: from,
|
||||
To: to,
|
||||
Date: date,
|
||||
Subject: subject,
|
||||
},
|
||||
Reader: bytes.NewReader(content),
|
||||
}
|
||||
id, err := store.AddMessage(delivery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Get and check.
|
||||
m, err := store.GetMessage(mailbox, id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := m.RawReader()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != len(content) {
|
||||
t.Errorf("Got len(content) == %v, want: %v", len(got), len(content))
|
||||
}
|
||||
errors := 0
|
||||
for i, b := range got {
|
||||
if b != content[i] {
|
||||
t.Errorf("Got content[%v] == %v, want: %v", i, b, content[i])
|
||||
errors++
|
||||
}
|
||||
if errors > 5 {
|
||||
t.Fatalf("Too many content errors, aborting test.")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testDeliveryOrder delivers several messages to the same mailbox, meanwhile querying its contents
|
||||
// with a new GetMessages call each cycle.
|
||||
func testDeliveryOrder(t *testing.T, store storage.Store) {
|
||||
mailbox := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
for i, subj := range subjects {
|
||||
// Check mailbox count.
|
||||
getAndCountMessages(t, store, mailbox, i)
|
||||
deliverMessage(t, store, mailbox, subj, time.Now())
|
||||
}
|
||||
// Confirm delivery order.
|
||||
msgs := getAndCountMessages(t, store, mailbox, 5)
|
||||
for i, want := range subjects {
|
||||
got := msgs[i].Subject()
|
||||
if got != want {
|
||||
t.Errorf("Got subject %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testSize verifies message contnet size metadata values.
|
||||
func testSize(t *testing.T, store storage.Store) {
|
||||
mailbox := "fred"
|
||||
subjects := []string{"a", "br", "much longer than the others"}
|
||||
sentIds := make([]string, len(subjects))
|
||||
sentSizes := make([]int64, len(subjects))
|
||||
for i, subj := range subjects {
|
||||
id, size := deliverMessage(t, store, mailbox, subj, time.Now())
|
||||
sentIds[i] = id
|
||||
sentSizes[i] = size
|
||||
}
|
||||
for i, id := range sentIds {
|
||||
msg, err := store.GetMessage(mailbox, id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := sentSizes[i]
|
||||
got := msg.Size()
|
||||
if got != want {
|
||||
t.Errorf("Got size %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testDelete creates and deletes some messages.
|
||||
func testDelete(t *testing.T, store storage.Store) {
|
||||
mailbox := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
for _, subj := range subjects {
|
||||
deliverMessage(t, store, mailbox, subj, time.Now())
|
||||
}
|
||||
msgs := getAndCountMessages(t, store, mailbox, len(subjects))
|
||||
// Delete a couple messages.
|
||||
err := store.RemoveMessage(mailbox, msgs[1].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.RemoveMessage(mailbox, msgs[3].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Confirm deletion.
|
||||
subjects = []string{"alpha", "charlie", "echo"}
|
||||
msgs = getAndCountMessages(t, store, mailbox, len(subjects))
|
||||
for i, want := range subjects {
|
||||
got := msgs[i].Subject()
|
||||
if got != want {
|
||||
t.Errorf("Got subject %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
// Try appending one more.
|
||||
deliverMessage(t, store, mailbox, "foxtrot", time.Now())
|
||||
subjects = []string{"alpha", "charlie", "echo", "foxtrot"}
|
||||
msgs = getAndCountMessages(t, store, mailbox, len(subjects))
|
||||
for i, want := range subjects {
|
||||
got := msgs[i].Subject()
|
||||
if got != want {
|
||||
t.Errorf("Got subject %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testPurge makes sure mailboxes can be purged.
|
||||
func testPurge(t *testing.T, store storage.Store) {
|
||||
mailbox := "fred"
|
||||
subjects := []string{"alpha", "bravo", "charlie", "delta", "echo"}
|
||||
for _, subj := range subjects {
|
||||
deliverMessage(t, store, mailbox, subj, time.Now())
|
||||
}
|
||||
getAndCountMessages(t, store, mailbox, len(subjects))
|
||||
// Purge and verify.
|
||||
err := store.PurgeMessages(mailbox)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
getAndCountMessages(t, store, mailbox, 0)
|
||||
}
|
||||
|
||||
// testVisitMailboxes creates some mailboxes and confirms the VisitMailboxes method visits all of
|
||||
// them.
|
||||
func testVisitMailboxes(t *testing.T, ds storage.Store) {
|
||||
boxes := []string{"abby", "bill", "christa", "donald", "evelyn"}
|
||||
for _, name := range boxes {
|
||||
deliverMessage(t, ds, name, "Old Message", time.Now().Add(-24*time.Hour))
|
||||
deliverMessage(t, ds, name, "New Message", time.Now())
|
||||
}
|
||||
seen := 0
|
||||
err := ds.VisitMailboxes(func(messages []storage.StoreMessage) bool {
|
||||
seen++
|
||||
count := len(messages)
|
||||
if count != 2 {
|
||||
t.Errorf("got: %v messages, want: 2", count)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if seen != 5 {
|
||||
t.Errorf("saw %v messages in total, want: 5", seen)
|
||||
}
|
||||
}
|
||||
|
||||
// deliverMessage creates and delivers a message to the specific mailbox, returning the size of the
|
||||
// generated message.
|
||||
func deliverMessage(
|
||||
t *testing.T,
|
||||
store storage.Store,
|
||||
mailbox string,
|
||||
subject string,
|
||||
date time.Time,
|
||||
) (string, int64) {
|
||||
t.Helper()
|
||||
meta := message.Metadata{
|
||||
Mailbox: mailbox,
|
||||
To: []*mail.Address{{Name: "Some Body", Address: "somebody@host"}},
|
||||
From: &mail.Address{Name: "Some B. Else", Address: "somebodyelse@host"},
|
||||
Subject: subject,
|
||||
Date: date,
|
||||
}
|
||||
testMsg := fmt.Sprintf("To: %s\r\nFrom: %s\r\nSubject: %s\r\n\r\nTest Body\r\n",
|
||||
meta.To[0].Address, meta.From.Address, subject)
|
||||
delivery := &message.Delivery{
|
||||
Meta: meta,
|
||||
Reader: ioutil.NopCloser(strings.NewReader(testMsg)),
|
||||
}
|
||||
id, err := store.AddMessage(delivery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return id, int64(len(testMsg))
|
||||
}
|
||||
|
||||
// getAndCountMessages is a test helper that expects to receive count messages or fails the test, it
|
||||
// also checks return error.
|
||||
func getAndCountMessages(t *testing.T, s storage.Store, mailbox string, count int) []storage.StoreMessage {
|
||||
t.Helper()
|
||||
msgs, err := s.GetMessages(mailbox)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to GetMessages for %q: %v", mailbox, err)
|
||||
}
|
||||
if len(msgs) != count {
|
||||
t.Errorf("Got %v messages, want: %v", len(msgs), count)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user