diff --git a/bot/history/history.go b/bot/history/history.go new file mode 100644 index 0000000..c011ae7 --- /dev/null +++ b/bot/history/history.go @@ -0,0 +1,84 @@ +package history + +import ( + "container/ring" + "fmt" + + "github.com/velour/catbase/bot/msg" +) + +// History is a ring buffer of messages +type History struct { + r *ring.Ring +} + +// New returns a history of sz size +func New(sz int) *History { + return &History{ring.New(sz)} +} + +// Edit looks for an entry and ammends it +// returns error if the id is not found +func (h *History) Edit(id string, update *msg.Message) error { + r := h.r + for i := 0; i < r.Len(); i++ { + m := r.Value.(*msg.Message) + if m != nil && m.ID == id { + r.Value = update + return nil + } + r = r.Next() + } + return fmt.Errorf("entry not found") +} + +// Append adds a message to the history +func (h *History) Append(m *msg.Message) { + h.r.Value = m + h.r = h.r.Next() +} + +// Find looks for an entry by id and returns an error if not found +func (h *History) Find(id string) (*msg.Message, error) { + r := h.r + for i := 0; i < r.Len(); i++ { + m := r.Value.(*msg.Message) + if m != nil && m.ID == id { + return m, nil + } + r = r.Next() + } + return nil, fmt.Errorf("entry not found") +} + +// Last gets the last known message +func (h *History) Last() *msg.Message { + return h.r.Prev().Value.(*msg.Message) +} + +// LastInChannel searches backwards for the last message matching channel ch +func (h *History) LastInChannel(ch string) (*msg.Message, error) { + r := h.r + for i := 0; i < r.Len(); i++ { + m := r.Value.(*msg.Message) + if m != nil && m.Channel == ch { + return m, nil + } + r = r.Prev() + } + return nil, fmt.Errorf("entry not found") +} + +// InChannel returns all knows messages from channel ch in reverse order +func (h *History) InChannel(ch string) []*msg.Message { + out := []*msg.Message{} + r := h.r + for i := 0; i < r.Len(); i++ { + m := r.Value.(*msg.Message) + if m != nil && m.Channel == ch { + out = append(out, m) + } + r = r.Prev() + } + return out +} diff --git a/bot/history/history_test.go b/bot/history/history_test.go new file mode 100644 index 0000000..7dc23b0 --- /dev/null +++ b/bot/history/history_test.go @@ -0,0 +1,82 @@ +package history + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot/msg" +) + +var sampleMessages []*msg.Message + +func init() { + sampleMessages = []*msg.Message{} + for i := 0; i < 100; i++ { + txt := fmt.Sprintf("Message #%d", i) + m := &msg.Message{ + ID: txt, + Body: txt, + } + + sampleMessages = append(sampleMessages, m) + } +} + +func TestAppend(t *testing.T) { + h := New(10) + for i := 0; i < 10; i++ { + h.Append(sampleMessages[i]) + } + previous := h.r.Prev().Value.(*msg.Message) + assert.Equal(t, sampleMessages[9].ID, previous.ID) +} + +func TestFindExists(t *testing.T) { + h := New(10) + for i := 0; i < 10; i++ { + h.Append(sampleMessages[i]) + } + id := sampleMessages[5].ID + elt, err := h.Find(id) + assert.Nil(t, err) + assert.Equal(t, id, elt.ID) +} + +func TestFindMissing(t *testing.T) { + h := New(10) + for i := 0; i < 10; i++ { + h.Append(sampleMessages[i]) + } + id := sampleMessages[15].ID + elt, err := h.Find(id) + assert.NotNil(t, err) + assert.Nil(t, elt) +} + +func TestEditExists(t *testing.T) { + h := New(10) + for i := 0; i < 10; i++ { + h.Append(sampleMessages[i]) + } + id := sampleMessages[5].ID + m := sampleMessages[15] + m.ID = id + err := h.Edit(id, m) + assert.Nil(t, err) + actual, err := h.Find(id) + assert.Nil(t, err) + assert.Equal(t, m.Body, actual.Body) +} + +func TestEditMissing(t *testing.T) { + h := New(10) + for i := 0; i < 10; i++ { + h.Append(sampleMessages[i]) + } + id := sampleMessages[10].ID + m := sampleMessages[6] + t.Logf("id: %s, editID: %s", id, m.ID) + err := h.Edit(id, m) + assert.NotNil(t, err) +}