From b6b818253fce525e23630be51d91889c13e14326 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 19 Dec 2018 13:24:23 -0600 Subject: [PATCH] make notifier store to local interface for testability --- backend/app/notify/notify.go | 21 +++++++------ backend/app/notify/notify_test.go | 49 ++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/backend/app/notify/notify.go b/backend/app/notify/notify.go index 7aa5defd..4d4f15f4 100644 --- a/backend/app/notify/notify.go +++ b/backend/app/notify/notify.go @@ -9,18 +9,11 @@ import ( "sync/atomic" "github.com/umputun/remark/backend/app/store" - "github.com/umputun/remark/backend/app/store/service" ) -// Destination defines interface for a given destination service, like telegram, email and so on -type Destination interface { - fmt.Stringer - Send(ctx context.Context, req request) error -} - // Service delivers notifications to multiple destinations type Service struct { - dataService *service.DataStore + dataService Store destinations []Destination queue chan request @@ -29,6 +22,16 @@ type Service struct { cancel context.CancelFunc } +// Destination defines interface for a given destination service, like telegram, email and so on +type Destination interface { + fmt.Stringer + Send(ctx context.Context, req request) error +} + +// Store defines the minimal interface accessing stored commens used by notifier +type Store interface { + Get(locator store.Locator, id string) (store.Comment, error) +} type request struct { comment store.Comment parent store.Comment @@ -38,7 +41,7 @@ const defaultQueueSize = 100 const uiNav = "#remark42__comment-" // NewService makes notification service routing comments to all destinations. -func NewService(dataService *service.DataStore, size int, destinations ...Destination) *Service { +func NewService(dataService Store, size int, destinations ...Destination) *Service { if size <= 0 { size = defaultQueueSize } diff --git a/backend/app/notify/notify_test.go b/backend/app/notify/notify_test.go index fdcaa6a8..a57bd98c 100644 --- a/backend/app/notify/notify_test.go +++ b/backend/app/notify/notify_test.go @@ -2,6 +2,7 @@ package notify import ( "context" + "errors" "fmt" "log" "math/rand" @@ -39,9 +40,9 @@ func TestService_WithDestinations(t *testing.T) { assert.Equal(t, 3, len(d1.get()), "got all comments to d1") assert.Equal(t, 3, len(d2.get()), "got all comments to d2") - assert.Equal(t, "100", d1.get()[0].ID) - assert.Equal(t, "101", d1.get()[1].ID) - assert.Equal(t, "102", d1.get()[2].ID) + assert.Equal(t, "100", d1.get()[0].comment.ID) + assert.Equal(t, "101", d1.get()[1].comment.ID) + assert.Equal(t, "102", d1.get()[2].comment.ID) } func TestService_WithDrops(t *testing.T) { @@ -81,6 +82,30 @@ func TestService_Many(t *testing.T) { assert.True(t, d2.closed) } +func TestService_WithParent(t *testing.T) { + dest := &mockDest{id: 1} + dataStore := &mockStore{data: map[string]store.Comment{}} + + dataStore.data["p1"] = store.Comment{ID: "p1"} + dataStore.data["p2"] = store.Comment{ID: "p2"} + + s := NewService(dataStore, 1, dest) + assert.NotNil(t, s) + + s.Submit(store.Comment{ID: "c1", ParentID: "p1"}) + time.Sleep(time.Millisecond * 110) + s.Submit(store.Comment{ID: "c11", ParentID: "p11"}) + time.Sleep(time.Millisecond * 110) + s.Close() + + destRes := dest.get() + assert.Equal(t, 2, len(destRes), "two comment notified") + assert.Equal(t, "p1", destRes[0].comment.ParentID) + assert.Equal(t, "p1", destRes[0].parent.ID) + assert.Equal(t, "p11", destRes[1].comment.ParentID) + assert.Equal(t, "", destRes[1].parent.ID) +} + func TestService_Nop(t *testing.T) { s := NopService s.Submit(store.Comment{}) @@ -89,7 +114,7 @@ func TestService_Nop(t *testing.T) { } type mockDest struct { - data []store.Comment + data []request id int closed bool lock sync.Mutex @@ -100,7 +125,7 @@ func (m *mockDest) Send(ctx context.Context, r request) error { defer m.lock.Unlock() select { case <-time.After(100 * time.Millisecond): - m.data = append(m.data, r.comment) + m.data = append(m.data, r) log.Printf("sent %s -> %d", r.comment.ID, m.id) case <-ctx.Done(): log.Printf("ctx closed %d", m.id) @@ -109,11 +134,21 @@ func (m *mockDest) Send(ctx context.Context, r request) error { return nil } -func (m *mockDest) get() []store.Comment { +func (m *mockDest) get() []request { m.lock.Lock() defer m.lock.Unlock() - res := make([]store.Comment, len(m.data)) + res := make([]request, len(m.data)) copy(res, m.data) return res } func (m *mockDest) String() string { return fmt.Sprintf("mock id=%d, closed=%v", m.id, m.closed) } + +type mockStore struct{ data map[string]store.Comment } + +func (m *mockStore) Get(_ store.Locator, id string) (store.Comment, error) { + res, ok := m.data[id] + if !ok { + return store.Comment{}, errors.New("no such id") + } + return res, nil +}