1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00

test: Finish initial storage test suite, closes #82

This commit is contained in:
James Hillyerd
2018-03-16 22:05:07 -07:00
parent 5e13e50763
commit 9b3d3c2ea8
3 changed files with 260 additions and 237 deletions

View File

@@ -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 ./...'

View File

@@ -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{})

View File

@@ -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
}