104 lines
5.5 KiB
Go
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' 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 <b>x</b> & y </blockquote><i></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())
|
|
}
|
|
}
|