feat(protocols): conversations - thread_id and in_reply_to written at delivery, list and read APIs
A reply now joins the conversation of the message it answers (Message-ID,
In-Reply-To, References), falling back to the subject for replies with no
headers. /mail/threads lists a folder as conversations and /mail/thread/{id}
returns one. Replying from the webmail flags the answered message.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff89e9dc44
commit
67d0c8aa5a
17 files changed
+1025
-9
No files matched your search
@@ -1,6 +1,10 @@
|
||||
package usecase
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractMessageHeaders_ReadsSubjectSenderAndSnippet(t *testing.T) {
|
||||
msg := "From: Alice Example <alice@remote.test>\r\n" +
|
||||
@@ -142,3 +146,37 @@ func TestExtractMessageHeaders_AttachmentDetection(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeadersCarryWhatAMessageAnswers(t *testing.T) {
|
||||
raw := "Message-ID: <c@x.test>\r\nIn-Reply-To: <b@x.test>\r\nReferences: <a@x.test>\r\n <b@x.test>\r\nSubject: Re: Plan\r\n\r\nbody"
|
||||
h := ExtractMessageHeaders([]byte(raw))
|
||||
if h.InReplyTo != "b@x.test" {
|
||||
t.Errorf("InReplyTo = %q, want b@x.test", h.InReplyTo)
|
||||
}
|
||||
if len(h.References) != 2 || h.References[0] != "a@x.test" || h.References[1] != "b@x.test" {
|
||||
t.Errorf("References = %v, want [a@x.test b@x.test]", h.References)
|
||||
}
|
||||
if h.MessageID != "c@x.test" {
|
||||
t.Errorf("MessageID = %q", h.MessageID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMessageThatAnswersNothingHasNoReferences(t *testing.T) {
|
||||
h := ExtractMessageHeaders([]byte("Subject: Hi\r\n\r\nbody"))
|
||||
if h.InReplyTo != "" || len(h.References) != 0 {
|
||||
t.Errorf("a first message claims to answer %q %v", h.InReplyTo, h.References)
|
||||
}
|
||||
}
|
||||
|
||||
// A chain can run to hundreds of ids. The ones nearest the end name the parent.
|
||||
func TestAVeryLongChainKeepsTheNewestIDs(t *testing.T) {
|
||||
var b strings.Builder
|
||||
b.WriteString("References:")
|
||||
for i := 0; i < 50; i++ {
|
||||
fmt.Fprintf(&b, " <m%d@x.test>", i)
|
||||
}
|
||||
h := ExtractMessageHeaders([]byte(b.String() + "\r\nSubject: x\r\n\r\nbody"))
|
||||
if len(h.References) != maxReferences || h.References[len(h.References)-1] != "m49@x.test" {
|
||||
t.Errorf("kept %d ids ending %q", len(h.References), h.References[len(h.References)-1])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user