package api
import (
"context"
"net/http"
"sync"
"testing"
"time"
)
// A reply is written as the account with no session behind it, so a
// shut, deleted or never-claimed account must not still write through
// the notifications it was sent before. Refused for good, and recorded.
func TestInboundReplyFromAClosedAccountIsRefused(t *testing.T) {
for _, tc := range []struct {
name string
open func(string) (bool, error)
}{
{"disabled", func(string) (bool, error) { return false, nil }},
{"unknown", func(n string) (bool, error) { return n == "someone-else", nil }},
} {
h, store, log := newReplyRig(t)
h.Open = tc.open
w := deliver(h, "s3cret", 1_800_000_000, replyEvent("forum-reply:abc123:lou", "lou", "still here"))
if w.Code != http.StatusForbidden {
t.Errorf("FAIL: %s account answered %d: %s", tc.name, w.Code, w.Body)
}
if len(*log) != 0 {
t.Errorf("FAIL: a %s account posted %d replies", tc.name, len(*log))
}
if _, seen := store.seen["<[email protected]>"]; !seen {
t.Errorf("FAIL: the %s refusal was not recorded, so notify would retry it", tc.name)
}
}
t.Log("ok: only an open account writes by mail")
}
// A reply forum.reply stages for moderation was written, into the
// queue. It is acknowledged and recorded, so a redelivery is a
// duplicate rather than another staged copy.
func TestInboundReplyHeldForModerationIsFinal(t *testing.T) {
h, store, _ := newReplyRig(t)
calls := 0
h.Post = func(context.Context, forumReplyIn) (string, error) { calls++; return "", errHeld }
ev := replyEvent("forum-reply:abc123:lou", "lou", "hello")
if w := deliver(h, "s3cret", 1_800_000_000, ev); w.Code != http.StatusAccepted {
t.Fatalf("FAIL: a held reply answered %d: %s", w.Code, w.Body)
}
if store.seen["<[email protected]>"] != "held for moderation" {
t.Errorf("FAIL: recorded %q", store.seen["<[email protected]>"])
}
if w := deliver(h, "s3cret", 1_800_000_000, ev); w.Code != http.StatusOK || calls != 1 {
t.Errorf("FAIL: the redelivery answered %d and staged %d copies", w.Code, calls)
}
t.Log("ok: one held copy, however often notify asks")
}
// Two copies of one mail arriving together post once.
func TestInboundReplyConcurrentCopiesPostOnce(t *testing.T) {
h, _, _ := newReplyRig(t)
var mu sync.Mutex
calls := 0
h.Post = func(context.Context, forumReplyIn) (string, error) {
mu.Lock()
calls++
mu.Unlock()
time.Sleep(20 * time.Millisecond) // long enough for the other copy to arrive mid-write
return "newhash", nil
}
ev := replyEvent("forum-reply:abc123:lou", "lou", "twice")
var wg sync.WaitGroup
codes := make([]int, 2)
for i := range codes {
wg.Add(1)
go func(i int) { defer wg.Done(); codes[i] = deliver(h, "s3cret", 1_800_000_000, ev).Code }(i)
}
wg.Wait()
if calls != 1 {
t.Errorf("FAIL: two concurrent copies posted %d replies (answers %v)", calls, codes)
}
t.Log("ok: one reply from two simultaneous deliveries")
}