mesh/merge-gate head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: builds messenger, telegram → novox; no bus step; every machine composes with the c…
mesh/repo-check head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
Issue 372's fix with issue 639's one line edited an answered ask down to 'You chose X at HH:MM · headline', so the operator could not read back what they approved. The headline stays the title, the summary, points, link and Details stay the body, and how it ended and who acts on it become the note. Telegram cuts the body from its end past 4096 characters, never the title or the note.
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@g14; 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())
|
|
}
|
|
}
|