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

Basic retention scanner w/ unit tests

Not wired into anything yet!
This commit is contained in:
James Hillyerd
2012-10-25 22:15:53 -07:00
parent b665612190
commit d8d0d1b4ff
3 changed files with 178 additions and 110 deletions

View File

@@ -17,6 +17,7 @@ import (
type DataStore interface {
MailboxFor(emailAddress string) (Mailbox, error)
AllMailboxes() ([]Mailbox, error)
}
type Mailbox interface {
@@ -95,6 +96,11 @@ func (ds *FileDataStore) MailboxFor(emailAddress string) (Mailbox, error) {
return &FileMailbox{store: ds, name: name, dirName: dir, path: path}, nil
}
// AllMailboxes returns a slice with all Mailboxes
func (ds *FileDataStore) AllMailboxes() ([]Mailbox, error) {
return nil, nil
}
// A Mailbox manages the mail for a specific user and correlates to a particular
// directory on disk.
type FileMailbox struct {

View File

@@ -1,12 +1,37 @@
package smtpd
import (
//"github.com/jhillyerd/inbucket/config"
//"github.com/jhillyerd/inbucket/log"
// "io/ioutil"
"github.com/jhillyerd/inbucket/log"
"time"
)
// retentionScan does a single pass of all mailboxes looking for messages that can be purged
func retentionScan(ds *DataStore) {
func retentionScan(ds DataStore, maxAge time.Duration, sleep time.Duration) error {
log.Trace("Starting retention scan")
cutoff := time.Now().Add(-1 * maxAge)
mboxes, err := ds.AllMailboxes()
if err != nil {
return err
}
for _, mb := range mboxes {
messages, err := mb.GetMessages()
if err != nil {
return err
}
for _, msg := range messages {
if msg.Date().Before(cutoff) {
log.Trace("Purging expired message %v", msg.Id())
err = msg.Delete()
if err != nil {
// Log but don't abort
log.Error("Failed to purge message %v: %v", msg.Id(), err)
}
}
}
// Sleep after completing a mailbox
time.Sleep(sleep)
}
return nil
}

View File

@@ -2,123 +2,160 @@ package smtpd
import (
"fmt"
"github.com/stretchrcom/testify/assert"
"github.com/stretchrcom/testify/mock"
"io/ioutil"
"os"
"path/filepath"
"net/mail"
"testing"
"time"
)
func TestSometing(t *testing.T) {
// Create mock objects
mds := &MockDataStore{}
mb1 := &MockMailbox{}
mb2 := &MockMailbox{}
mb3 := &MockMailbox{}
// Mockup some different aged messages (num is in hours)
new1 := mockMessage(0)
new2 := mockMessage(1)
new3 := mockMessage(2)
old1 := mockMessage(4)
old2 := mockMessage(12)
old3 := mockMessage(24)
// First it should ask for all mailboxes
mds.On("AllMailboxes").Return([]Mailbox{mb1, mb2, mb3}, nil)
// Then for all messages on each box
mb1.On("GetMessages").Return([]Message{new1, old1, old2}, nil)
mb2.On("GetMessages").Return([]Message{old3, new2}, nil)
mb3.On("GetMessages").Return([]Message{new3}, nil)
// Test 4 hour retention
retentionScan(mds, 4*time.Hour, 0)
// Check our assertions
mds.AssertExpectations(t)
mb1.AssertExpectations(t)
mb2.AssertExpectations(t)
mb3.AssertExpectations(t)
// Delete should not have been called on new messages
new1.AssertNotCalled(t, "Delete")
new2.AssertNotCalled(t, "Delete")
new3.AssertNotCalled(t, "Delete")
// Delete should have been called once on old messages
old1.AssertNumberOfCalls(t, "Delete", 1)
old2.AssertNumberOfCalls(t, "Delete", 1)
old3.AssertNumberOfCalls(t, "Delete", 1)
}
// Make a MockMessage of a specific age
func mockMessage(ageHours int) *MockMessage {
msg := &MockMessage{}
msg.On("Id").Return(fmt.Sprintf("MSG[age=%vh]", ageHours))
msg.On("Date").Return(time.Now().Add(time.Duration(ageHours*-1) * time.Hour))
msg.On("Delete").Return(nil)
return msg
}
// Mock DataStore object
type MockDataStore struct {
mock.Mock
path string
mailPath string
}
var names = []string{"abby", "bill", "christa", "donald", "evelyn"}
func TestSometing(t *testing.T) {
ds := setupDataStore()
fmt.Println(ds)
//defer teardownDataStore(ds)
assert.Equal(t, true, true)
func (m *MockDataStore) MailboxFor(name string) (Mailbox, error) {
return nil, nil
}
// setupDataStore will build the following structure in a temporary
// directory:
//
// /tmp/inbucket?????????
// └── mail
// ├── 53e
// │   └── 53e11e
// │   └── 53e11eb7b24cc39e33733a0ff06640f1b39425ea
// │   ├── 20121024T164239-0000.gob
// │   ├── 20121024T164239-0000.raw
// │   ├── 20121025T164239-0000.gob
// │   └── 20121025T164239-0000.raw
// ├── 60c
// │   └── 60c596
// │   └── 60c5963a56da1425f133d28166ca4fe70dcb25f5
// │   ├── 20121024T164239-0000.gob
// │   ├── 20121024T164239-0000.raw
// │   ├── 20121025T164239-0000.gob
// │   └── 20121025T164239-0000.raw
// ├── 88d
// │   └── 88db92
// │   └── 88db9292c772b38311e1778f6f6b18216443abf0
// │   ├── 20121024T164239-0000.gob
// │   ├── 20121024T164239-0000.raw
// │   ├── 20121025T164239-0000.gob
// │   └── 20121025T164239-0000.raw
// ├── c69
// │   └── c692d6
// │   └── c692d6a10598e0a801576fdd4ecf3c37e45bfbc4
// │   ├── 20121024T164239-0000.gob
// │   ├── 20121024T164239-0000.raw
// │   ├── 20121025T164239-0000.gob
// │   └── 20121025T164239-0000.raw
// └── e76
// └── e76cef
// └── e76ceff3c47adb10f62b1acd7109f88fbd5e9ca7
// ├── 20121024T164239-0000.gob
// ├── 20121024T164239-0000.raw
// ├── 20121025T164239-0000.gob
// └── 20121025T164239-0000.raw
func setupDataStore() *FileDataStore {
// Build fake SMTP message for delivery
testMsg := make([]byte, 0, 300)
testMsg = append(testMsg, []byte("To: somebody@host\r\n")...)
testMsg = append(testMsg, []byte("From: somebodyelse@host\r\n")...)
testMsg = append(testMsg, []byte("Subject: test message\r\n")...)
testMsg = append(testMsg, []byte("\r\n")...)
testMsg = append(testMsg, []byte("Test Body\r\n")...)
path, err := ioutil.TempDir("", "inbucket")
if err != nil {
panic(err)
}
mailPath := filepath.Join(path, "mail")
ds := &FileDataStore{path: path, mailPath: mailPath}
for _, name := range names {
mb, err := ds.MailboxFor(name)
if err != nil {
panic(err)
}
// Create day old message
date := time.Now().Add(-24 * time.Hour)
msg := &FileMessage{
mailbox: mb.(*FileMailbox),
writable: true,
Fdate: date,
Fid: generatePrefix(date) + "-0000",
}
msg.Append(testMsg)
if err = msg.Close(); err != nil {
panic(err)
}
// Create current message
date = time.Now()
msg = &FileMessage{
mailbox: mb.(*FileMailbox),
writable: true,
Fdate: date,
Fid: generatePrefix(date) + "-0000",
}
msg.Append(testMsg)
if err = msg.Close(); err != nil {
panic(err)
}
}
return ds
func (m *MockDataStore) AllMailboxes() ([]Mailbox, error) {
args := m.Called()
return args.Get(0).([]Mailbox), args.Error(1)
}
func teardownDataStore(ds *FileDataStore) {
if err := os.RemoveAll(ds.path); err != nil {
panic(err)
}
// Mock Mailbox object
type MockMailbox struct {
mock.Mock
}
func (m *MockMailbox) GetMessages() ([]Message, error) {
args := m.Called()
return args.Get(0).([]Message), args.Error(1)
}
func (m *MockMailbox) GetMessage(id string) (Message, error) {
args := m.Called(id)
return args.Get(0).(Message), args.Error(1)
}
func (m *MockMailbox) NewMessage() Message {
args := m.Called()
return args.Get(0).(Message)
}
func (m *MockMailbox) String() string {
args := m.Called()
return args.String(0)
}
// Mock Message object
type MockMessage struct {
mock.Mock
}
func (m *MockMessage) Id() string {
args := m.Called()
return args.String(0)
}
func (m *MockMessage) From() string {
args := m.Called()
return args.String(0)
}
func (m *MockMessage) Date() time.Time {
args := m.Called()
return args.Get(0).(time.Time)
}
func (m *MockMessage) Subject() string {
args := m.Called()
return args.String(0)
}
func (m *MockMessage) ReadHeader() (msg *mail.Message, err error) {
args := m.Called()
return args.Get(0).(*mail.Message), args.Error(1)
}
func (m *MockMessage) ReadBody() (msg *mail.Message, body *MIMEBody, err error) {
args := m.Called()
return args.Get(0).(*mail.Message), args.Get(1).(*MIMEBody), args.Error(2)
}
func (m *MockMessage) ReadRaw() (raw *string, err error) {
args := m.Called()
return args.Get(0).(*string), args.Error(1)
}
func (m *MockMessage) Append(data []byte) error {
args := m.Called(data)
return args.Error(0)
}
func (m *MockMessage) Close() error {
args := m.Called()
return args.Error(0)
}
func (m *MockMessage) Delete() error {
args := m.Called()
return args.Error(0)
}
func (m *MockMessage) String() string {
args := m.Called()
return args.String(0)
}