package notify import ( "context" "errors" "fmt" "math/rand" "sync" "sync/atomic" "testing" "time" log "github.com/go-pkgz/lgr" "github.com/stretchr/testify/assert" "github.com/umputun/remark/backend/app/store" ) func TestService_NoDestinations(t *testing.T) { s := NewService(nil, 1) assert.NotNil(t, s) s.Submit(store.Comment{ID: "123"}) s.Submit(store.Comment{ID: "123"}) s.Submit(store.Comment{ID: "123"}) s.Close() } func TestService_WithDestinations(t *testing.T) { d1, d2 := &mockDest{id: 1}, &mockDest{id: 2} s := NewService(nil, 1, d1, d2) assert.NotNil(t, s) s.Submit(store.Comment{ID: "100"}) time.Sleep(time.Millisecond * 110) s.Submit(store.Comment{ID: "101"}) time.Sleep(time.Millisecond * 110) s.Submit(store.Comment{ID: "102"}) time.Sleep(time.Millisecond * 110) s.Close() 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].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) { d1, d2 := &mockDest{id: 1}, &mockDest{id: 2} s := NewService(nil, 1, d1, d2) assert.NotNil(t, s) s.Submit(store.Comment{ID: "100"}) s.Submit(store.Comment{ID: "101"}) time.Sleep(time.Millisecond * 110) s.Submit(store.Comment{ID: "102"}) time.Sleep(time.Millisecond * 110) s.Close() s.Submit(store.Comment{ID: "111"}) // safe to send after close assert.Equal(t, 2, len(d1.get()), "one comment dropped from d1") assert.Equal(t, 2, len(d2.get()), "one comment dropped from d2") } func TestService_Many(t *testing.T) { d1, d2 := &mockDest{id: 1}, &mockDest{id: 2} s := NewService(nil, 5, d1, d2) assert.NotNil(t, s) for i := 0; i < 10; i++ { s.Submit(store.Comment{ID: fmt.Sprintf("%d", 100+i)}) time.Sleep(time.Millisecond * time.Duration(rand.Int31n(200))) } s.Close() time.Sleep(time.Millisecond * 10) assert.NotEqual(t, 10, len(d1.get()), "some comments dropped from d1") assert.NotEqual(t, 10, len(d2.get()), "some comments dropped from d2") assert.True(t, d1.closed) 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{}) s.Close() assert.Equal(t, uint32(1), atomic.LoadUint32(&s.closed)) } type mockDest struct { data []request id int closed bool lock sync.Mutex } func (m *mockDest) Send(ctx context.Context, r request) error { m.lock.Lock() defer m.lock.Unlock() select { case <-time.After(100 * time.Millisecond): 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) m.closed = true } return nil } func (m *mockDest) get() []request { m.lock.Lock() defer m.lock.Unlock() 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 }