Files
mesh-catalog/modules/telegram/cmd/telegram/render_test.go
T

104 lines
5.5 KiB
Go

package main
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// adr0343 is the ask for ADR 0343 as the router now sends it to this channel (novox/hq issue 639): the knowledge
// base's brief as its title and body, the rest in its note. On 2026-10-13 the same ask arrived as about thirty plain
// lines — every one of its thirteen clauses on a line of its own — and the operator would not read it.
var adr0343 = asks.Message{Handle: "h-343", To: "42",
Title: "Your agents' memory follows you to every machine",
Body: "What an agent learns in a project's memory is copied to every machine that runs the agent, so a session " +
"anywhere starts with it. A file changed on two machines keeps both versions and warns you; secrets in memory files " +
"are screened, though not perfectly.\n" +
"• Only memory folders travel: links and files over 128 KiB stay where they are\n" +
"• A conflict reaches you as a warning that needs you\n" +
"• A deleted file is marked, and a newer change elsewhere outlives the deletion",
Note: "Asked by mesh-knowledge at 16:05. Unanswered by tomorrow 15:55: the record stays proposed; nothing of it is in force.\n" +
"Approve: the record is accepted and its clauses are in force. Decline: the record stays proposed; nothing of it is in force.\n" +
"ADR 0343, proposed by fix-367@node-b; the knowledge base's own review approved it. Fingerprint a59f 361b f60e cb5e d237 a837.\n" +
"Read its 13 clauses with the knowledge base's get, number 343.",
Buttons: []asks.Button{{Label: "Approve", Ticket: "t-approve"}, {Label: "Decline", Ticket: "t-decline"}}}
// The ADR 0343 ask fits a phone's glance: a bold headline, the summary and its points, then the rest apart and
// collapsed — at most twelve lines in all, and never a clause.
func TestTheADR0343AskFitsAGlance(t *testing.T) {
c, f, _, _ := newChannel(t, goodToken)
subject, body := work(asks.Show, adr0343)
if err := c.Take(subject, body); err != nil {
t.Fatal(err)
}
sent := f.of("sendMessage")
if len(sent) != 1 {
t.Fatalf("sent %d", len(sent))
}
html := sent[0].Body["text"].(string)
t.Logf("as sent:\n%s", html)
lines := strings.Split(html, "\n")
if len(lines) > 12 {
t.Errorf("the ask is %d lines, more than 12:\n%s", len(lines), html)
}
if lines[0] != "<b>Your agents&#39; memory follows you to every machine</b>" && lines[0] != "<b>Your agents' memory follows you to every machine</b>" {
t.Errorf("the headline is not first and bold: %q", lines[0])
}
if !strings.HasPrefix(lines[1], "What an agent learns") || strings.Count(html, "\n• ") != 3 {
t.Errorf("the summary and its three points do not follow the headline:\n%s", html)
}
if !strings.Contains(html, "\n\n<blockquote expandable>Asked by mesh-knowledge at 16:05.") || !strings.HasSuffix(html, "number 343.</blockquote>") {
t.Errorf("the note is not apart and collapsed:\n%s", html)
}
for _, lead := range []string{"Only the memory folders are carried", "How it is checked", "13 clause(s)", "**"} {
if strings.Contains(html, lead) {
t.Errorf("the ask carries %q", lead)
}
}
if sent[0].Body["parse_mode"] != "HTML" {
t.Errorf("not sent as HTML: %v", sent[0].Body["parse_mode"])
}
}
// The note is kept whole when the message is too long: the body is cut first and the cut said; only a note that
// cannot fit beside the title is cut, and that said too (the review of mesh-catalog #278).
func TestALongBodyIsCutBeforeTheNote(t *testing.T) {
m := asks.Message{Title: "t", Body: strings.Repeat("b", 5000), Note: "+ shares: media=/storage/media\nFingerprint 1234."}
s := partsOf(m)
if s.note != m.Note || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
t.Errorf("note %q, body ends %q, %d characters", s.note, s.body[len(s.body)-20:], len([]rune(s.plain())))
}
m.Body, m.Note = "the summary", strings.Repeat("n", 5000)
s = partsOf(m)
if !strings.HasSuffix(s.note, cutNote) || !strings.Contains(s.body, "cut here") || len([]rune(s.plain())) != MaxLength {
t.Errorf("body %q, note ends %q, %d characters", s.body, s.note[len(s.note)-20:], len([]rune(s.plain())))
}
}
// A '<', '>' or '&' in the note is escaped like any other part: it neither breaks the blockquote nor injects.
func TestTheNoteIsEscaped(t *testing.T) {
got := partsOf(asks.Message{Title: "t", Note: "Asked by <b>x</b> & y </blockquote><i>"}).html()
want := "<b>t</b>\n\n<blockquote expandable>Asked by &lt;b&gt;x&lt;/b&gt; &amp; y &lt;/blockquote&gt;&lt;i&gt;</blockquote>"
if got != want {
t.Errorf("%q", got)
}
}
// novox/hq issue 714: an answered ask keeps its question whole, with how it ended below it. Over Telegram's bound,
// the end of the body (the asker's Details) is cut; the headline and the answer are never.
func TestAnAnsweredAskOverTheBoundKeepsItsHeadlineAndItsAnswer(t *testing.T) {
answer := "You chose Approve at 20:03 on Telegram.\nTold to mesh-knowledge, which acts on it itself."
m := asks.Message{Handle: "h-714", To: "42", Title: "Fixes reach every machine at once, unchecked",
Body: "A fix goes to every machine in one step.\n• No machine goes first\n" + strings.Repeat("Details line. ", 400),
Note: answer}
s := partsOf(m)
if n := len([]rune(s.plain())); n > MaxLength {
t.Fatalf("the answered ask is %d characters, over %d", n, MaxLength)
}
if s.title != m.Title || s.note != answer || !strings.HasPrefix(s.body, "A fix goes to every machine in one step.\n• No machine goes first") ||
!strings.HasSuffix(s.body, cutNote) {
t.Errorf("the headline, the summary or the answer was cut:\n%s", s.plain())
}
}