Merge pull request #480 from paskal/email_interface_clarification

Switch notify.Submit to receive notify.Request instead of store.Comment
This commit is contained in:
Umputun
2019-11-23 13:22:17 -06:00
committed by GitHub
5 changed files with 47 additions and 48 deletions
+11 -12
View File
@@ -16,7 +16,7 @@ import (
type Service struct {
dataService Store
destinations []Destination
queue chan request
queue chan Request
closed uint32 // non-zero means closed. uses uint instead of bool for atomic
ctx context.Context
@@ -26,7 +26,7 @@ type Service struct {
// 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
Send(ctx context.Context, req Request) error
}
// Store defines the minimal interface accessing stored comments used by notifier
@@ -34,8 +34,8 @@ type Store interface {
Get(locator store.Locator, id string, user store.User) (store.Comment, error)
}
type request struct {
comment store.Comment
type Request struct {
Comment store.Comment
parent store.Comment
}
@@ -50,7 +50,7 @@ func NewService(dataService Store, size int, destinations ...Destination) *Servi
ctx, cancel := context.WithCancel(context.Background())
res := Service{
dataService: dataService,
queue: make(chan request, size),
queue: make(chan Request, size),
destinations: destinations,
ctx: ctx,
cancel: cancel,
@@ -62,21 +62,20 @@ func NewService(dataService Store, size int, destinations ...Destination) *Servi
return &res
}
// Submit comment to internal channel if not busy, drop if can't send
func (s *Service) Submit(comment store.Comment) {
// Submit Request to internal channel if not busy, drop if can't send
func (s *Service) Submit(req Request) {
if len(s.destinations) == 0 || atomic.LoadUint32(&s.closed) != 0 {
return
}
parentComment := store.Comment{}
if s.dataService != nil {
if p, err := s.dataService.Get(comment.Locator, comment.ParentID, store.User{}); err == nil {
parentComment = p
if p, err := s.dataService.Get(req.Comment.Locator, req.Comment.ParentID, store.User{}); err == nil {
req.parent = p
}
}
select {
case s.queue <- request{comment: comment, parent: parentComment}:
case s.queue <- req:
default:
log.Printf("[WARN] can't send comment notification to queue, %+v", comment)
log.Printf("[WARN] can't send comment notification to queue, %+v", req.Comment)
}
}
+24 -24
View File
@@ -19,9 +19,9 @@ import (
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.Submit(Request{Comment: store.Comment{ID: "123"}})
s.Submit(Request{Comment: store.Comment{ID: "123"}})
s.Submit(Request{Comment: store.Comment{ID: "123"}})
s.Close()
}
@@ -30,20 +30,20 @@ func TestService_WithDestinations(t *testing.T) {
s := NewService(nil, 1, d1, d2)
assert.NotNil(t, s)
s.Submit(store.Comment{ID: "100"})
s.Submit(Request{Comment: store.Comment{ID: "100"}})
time.Sleep(time.Millisecond * 110)
s.Submit(store.Comment{ID: "101"})
s.Submit(Request{Comment: store.Comment{ID: "101"}})
time.Sleep(time.Millisecond * 110)
s.Submit(store.Comment{ID: "102"})
s.Submit(Request{Comment: 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)
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) {
@@ -51,14 +51,14 @@ func TestService_WithDrops(t *testing.T) {
s := NewService(nil, 1, d1, d2)
assert.NotNil(t, s)
s.Submit(store.Comment{ID: "100"})
s.Submit(store.Comment{ID: "101"})
s.Submit(Request{Comment: store.Comment{ID: "100"}})
s.Submit(Request{Comment: store.Comment{ID: "101"}})
time.Sleep(time.Millisecond * 110)
s.Submit(store.Comment{ID: "102"})
s.Submit(Request{Comment: store.Comment{ID: "102"}})
time.Sleep(time.Millisecond * 110)
s.Close()
s.Submit(store.Comment{ID: "111"}) // safe to send after close
s.Submit(Request{Comment: 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")
@@ -70,7 +70,7 @@ func TestService_Many(t *testing.T) {
assert.NotNil(t, s)
for i := 0; i < 10; i++ {
s.Submit(store.Comment{ID: fmt.Sprintf("%d", 100+i)})
s.Submit(Request{Comment: store.Comment{ID: fmt.Sprintf("%d", 100+i)}})
time.Sleep(time.Millisecond * time.Duration(rand.Int31n(200)))
}
s.Close()
@@ -93,41 +93,41 @@ func TestService_WithParent(t *testing.T) {
s := NewService(dataStore, 1, dest)
assert.NotNil(t, s)
s.Submit(store.Comment{ID: "c1", ParentID: "p1"})
s.Submit(Request{Comment: store.Comment{ID: "c1", ParentID: "p1"}})
time.Sleep(time.Millisecond * 110)
s.Submit(store.Comment{ID: "c11", ParentID: "p11"})
s.Submit(Request{Comment: 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].Comment.ParentID)
assert.Equal(t, "p1", destRes[0].parent.ID)
assert.Equal(t, "p11", destRes[1].comment.ParentID)
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.Submit(Request{Comment: store.Comment{}})
s.Close()
assert.Equal(t, uint32(1), atomic.LoadUint32(&s.closed))
}
type mockDest struct {
data []request
data []Request
id int
closed bool
lock sync.Mutex
}
func (m *mockDest) Send(ctx context.Context, r request) error {
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)
log.Printf("sent %s -> %d", r.Comment.ID, m.id)
case <-ctx.Done():
log.Printf("ctx closed %d", m.id)
m.closed = true
@@ -135,10 +135,10 @@ func (m *mockDest) Send(ctx context.Context, r request) error {
return nil
}
func (m *mockDest) get() []request {
func (m *mockDest) get() []Request {
m.lock.Lock()
defer m.lock.Unlock()
res := make([]request, len(m.data))
res := make([]Request, len(m.data))
copy(res, m.data)
return res
}
+8 -8
View File
@@ -85,23 +85,23 @@ func NewTelegram(token string, channelID string, timeout time.Duration, api stri
}
// Send to telegram channel
func (t *Telegram) Send(ctx context.Context, req request) error {
func (t *Telegram) Send(ctx context.Context, req Request) error {
client := http.Client{Timeout: telegramTimeOut}
log.Printf("[DEBUG] send telegram notification to %s, comment id %s", t.channelID, req.comment.ID)
log.Printf("[DEBUG] send telegram notification to %s, comment id %s", t.channelID, req.Comment.ID)
from := req.comment.User.Name
if req.comment.ParentID != "" {
from := req.Comment.User.Name
if req.Comment.ParentID != "" {
from += " → " + req.parent.User.Name
}
from = "*" + from + "*"
link := fmt.Sprintf("↦ [original comment](%s)", req.comment.Locator.URL+uiNav+req.comment.ID)
if req.comment.PostTitle != "" {
link = fmt.Sprintf("↦ [%s](%s)", req.comment.PostTitle, req.comment.Locator.URL+uiNav+req.comment.ID)
link := fmt.Sprintf("↦ [original comment](%s)", req.Comment.Locator.URL+uiNav+req.Comment.ID)
if req.Comment.PostTitle != "" {
link = fmt.Sprintf("↦ [%s](%s)", req.Comment.PostTitle, req.Comment.Locator.URL+uiNav+req.Comment.ID)
}
u := fmt.Sprintf("%s%s/sendMessage?chat_id=%s&parse_mode=Markdown&disable_web_page_preview=true",
t.apiPrefix, t.token, t.channelID)
msg := fmt.Sprintf("%s\n\n%s\n\n%s", from, req.comment.Orig, link)
msg := fmt.Sprintf("%s\n\n%s\n\n%s", from, req.Comment.Orig, link)
msg = html.UnescapeString(msg)
body := struct {
Text string `json:"text"`
+3 -3
View File
@@ -63,15 +63,15 @@ func TestTelegram_Send(t *testing.T) {
cp := store.Comment{Text: "some parent text"}
cp.User.Name = "to"
err = tb.Send(context.TODO(), request{comment: c, parent: cp})
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
assert.NoError(t, err)
c.PostTitle = "test title"
err = tb.Send(context.TODO(), request{comment: c, parent: cp})
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
assert.NoError(t, err)
tb, err = NewTelegram("non-json-resp", "remark_test", 2*time.Second, ts.URL+"/")
assert.NotNil(t, err, "should failed")
err = tb.Send(context.TODO(), request{comment: c, parent: cp})
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
require.NotNil(t, err)
assert.Contains(t, err.Error(), "unexpected telegram status code 404", "send on broken tg")
+1 -1
View File
@@ -109,7 +109,7 @@ func (s *private) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
Scopes(comment.Locator.URL, lastCommentsScope, comment.User.ID, comment.Locator.SiteID))
if s.notifyService != nil {
s.notifyService.Submit(finalComment)
s.notifyService.Submit(notify.Request{Comment: finalComment})
}
log.Printf("[DEBUG] created commend %+v", finalComment)