From 1b5a783dbd26677b3d73d38c7e7113dc12d4194a Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 18 Nov 2023 11:21:09 -0800 Subject: [PATCH] test: impl StoreStub.MarkSeen (#443) * test: impl StoreStub.MarkSeen Signed-off-by: James Hillyerd * continue to Message interface in StoreStub Signed-off-by: James Hillyerd * test errors Signed-off-by: James Hillyerd --------- Signed-off-by: James Hillyerd --- pkg/test/storage.go | 58 +++++++++++++++++++------ pkg/test/storage_test.go | 91 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 15 deletions(-) diff --git a/pkg/test/storage.go b/pkg/test/storage.go index 4137688..fc4df3c 100644 --- a/pkg/test/storage.go +++ b/pkg/test/storage.go @@ -9,8 +9,8 @@ import ( // StoreStub stubs storage.Store for testing. type StoreStub struct { storage.Store - mailboxes map[string][]storage.Message - deleted map[storage.Message]struct{} + mailboxes map[string][]storage.Message // Stored messages, by mailbox. + deleted map[storage.Message]struct{} // Deleted message references. } // NewStore creates a new StoreStub. @@ -25,7 +25,7 @@ func NewStore() *StoreStub { func (s *StoreStub) AddMessage(m storage.Message) (id string, err error) { mb := m.Mailbox() msgs := s.mailboxes[mb] - s.mailboxes[mb] = append(msgs, m) + s.mailboxes[mb] = append(msgs, &MessageStub{Message: m}) return m.ID(), nil } @@ -50,34 +50,57 @@ func (s *StoreStub) GetMessages(mailbox string) ([]storage.Message, error) { return s.mailboxes[mailbox], nil } +// MarkSeen marks the message as having been seen. +func (s *StoreStub) MarkSeen(mailbox, id string) error { + if mailbox == "messageerr" { + return errors.New("internal error") + } + for _, m := range s.mailboxes[mailbox] { + if m.ID() == id { + if stub, ok := m.(*MessageStub); ok { + stub.seen = true + return nil + } + return errors.New("unexpected type in StoreStub.mailboxes") + } + } + return storage.ErrNotExist +} + // RemoveMessage deletes a message by ID from the specified mailbox. func (s *StoreStub) RemoveMessage(mailbox, id string) error { - mb, ok := s.mailboxes[mailbox] - if ok { - var msg storage.Message + if mb, ok := s.mailboxes[mailbox]; ok { + var removed storage.Message for i, m := range mb { if m.ID() == id { - msg = m + removed = m s.mailboxes[mailbox] = append(mb[:i], mb[i+1:]...) break } } - if msg != nil { - s.deleted[msg] = struct{}{} - return nil + + if removed != nil { + // Clients will be checking for their original storage.Message, not our wrapper. + if stub, ok := removed.(*MessageStub); ok { + s.deleted[stub.Message] = struct{}{} + return nil + } + return errors.New("unexpected type in StoreStub.mailboxes") } } + return storage.ErrNotExist } // VisitMailboxes accepts a function that will be called with the messages in each mailbox while it // continues to return true. func (s *StoreStub) VisitMailboxes(f func([]storage.Message) (cont bool)) error { - for _, v := range s.mailboxes { - if !f(v) { + for _, msgs := range s.mailboxes { + if !f(msgs) { return nil } } + return nil } @@ -86,3 +109,14 @@ func (s *StoreStub) MessageDeleted(m storage.Message) bool { _, ok := s.deleted[m] return ok } + +// MessageStub wraps a storage.Message with "seen" functionality. +type MessageStub struct { + storage.Message + seen bool +} + +// Seen returns true if the message has been marked as seen previously. +func (m *MessageStub) Seen() bool { + return m.seen +} diff --git a/pkg/test/storage_test.go b/pkg/test/storage_test.go index 08a1994..454ce86 100644 --- a/pkg/test/storage_test.go +++ b/pkg/test/storage_test.go @@ -80,17 +80,25 @@ func TestStoreStubMailboxAddGetVisit(t *testing.T) { // Verify entire mailbox contents. gotMsgs, err := ss.GetMessages(tc.mailbox) require.NoError(t, err, "GetMessages() should not error") - require.NoError(t, err) assert.Len(t, gotMsgs, tc.count, "GetMessages() returned wrong number of items") + input: for _, want := range inputMsgs { - assert.Contains(t, gotMsgs, want, "GetMessages() did not return expected message") + for _, got := range gotMsgs { + if got.ID() == want.ID() && got.Mailbox() == want.Mailbox() { + continue input + } + } + t.Errorf("GetMessages() did not return message %q for mailbox %q", + want.ID(), want.Mailbox()) } // Fetch and verify individual messages. for _, want := range inputMsgs { got, err := ss.GetMessage(tc.mailbox, want.ID()) require.NoError(t, err, "GetMessage() should not error") - assert.Equal(t, want, got, "GetMessage() returned unexpected value") + assert.Equal(t, want.Mailbox(), got.Mailbox(), + "GetMessage() returned unexpected Mailbox") + assert.Equal(t, want.ID(), got.ID(), "GetMessage() returned unexpected ID") } }) } @@ -122,6 +130,83 @@ func TestStoreStubMailboxAddGetVisit(t *testing.T) { }) } +func TestStoreStubMarkSeen(t *testing.T) { + ss := test.NewStore() + + // Add messages. + inputMsgs := make([]*message.Delivery, 5) + for i := range inputMsgs { + subject := fmt.Sprintf("%s message %v", "box1", i) + inputMsgs[i] = makeTestMessage("box1", subject) + id, err := ss.AddMessage(inputMsgs[i]) + require.NoError(t, err) + require.NotEmpty(t, id, "AddMessage() must return an ID") + } + + // Mark second message as seen. + seen := inputMsgs[1] + err := ss.MarkSeen("box1", seen.ID()) + assert.NoError(t, err, "MarkSeen must not fail") + + // Verify message has seen flag. + got, err := ss.GetMessage("box1", seen.ID()) + require.NoError(t, err) + assert.True(t, got.Seen(), "Message should have been seen") + + // Verify only one message seen. + gotMsgs, err := ss.GetMessages("box1") + require.NoError(t, err, "GetMessages() should not error") + assert.Len(t, gotMsgs, len(inputMsgs), "GetMessages() returned wrong number of items") + gotCount := 0 + for _, msg := range gotMsgs { + if msg.Seen() { + gotCount++ + } + } + assert.Equal(t, 1, gotCount, "Incorrect number of seen messages") +} + +func TestStoreStubForcedErrors(t *testing.T) { + ss := test.NewStore() + var err error + + // Add message to forced error mailboxes. + msg := makeTestMessage("messageerr", "test 1") + id1, err := ss.AddMessage(msg) + require.NoError(t, err) + msg = makeTestMessage("messageserr", "test 2") + _, err = ss.AddMessage(msg) + require.NoError(t, err) + + // Verify methods return error. + _, err = ss.GetMessage("messageerr", id1) + assert.Error(t, err, "GetMessage()") + assert.NotEqual(t, storage.ErrNotExist, err) + + _, err = ss.GetMessages("messageserr") + assert.Error(t, err, "GetMessages()") + assert.NotEqual(t, storage.ErrNotExist, err) + + err = ss.MarkSeen("messageerr", id1) + assert.Error(t, err, "MarkSeen()") + assert.NotEqual(t, storage.ErrNotExist, err) +} + +func TestStoreStubNotExistErrors(t *testing.T) { + ss := test.NewStore() + var err error + + // Verify methods return error. + _, err = ss.GetMessage("fake", "1") + assert.Equal(t, storage.ErrNotExist, err, "GetMessage()") + + err = ss.MarkSeen("fake", "1") + assert.Equal(t, storage.ErrNotExist, err, "MarkSeen()") + + err = ss.RemoveMessage("fake", "1") + assert.Equal(t, storage.ErrNotExist, err, "RemoveMessage()") +} + func makeTestMessage(mailbox string, subject string) *message.Delivery { id := fmt.Sprintf("%06d", atomic.AddUint32(&testMessageIDSource, 1)) from := mail.Address{Name: "From Test", Address: "from@example.com"}