1 Commits
Author SHA1 Message Date
sneak ed00694211 Bind history to the recipient session and channel, not the name (closes #148)
check / check (push) Canceled after 0s
History was selected by msg_to, so whoever next held a released nick, or
created a deleted channel again under its old name, could read the
earlier holder's messages. Each message row now records the session a
DM or INVITE was sent to (to_session_id) and the channel a channel
message was sent to (to_channel_id), and GET /api/v1/history selects by
the one that applies, using its index. A session's DM history now also
follows it across nick changes. A DM or channel message whose recipient
is deleted before it is stored gets the unknown nick or channel error.

Model: opus-5-5
2026-10-20 17:22:32 +00:00
+131 -38
View File
@@ -1551,6 +1551,60 @@ func (tserver *testServer) getHistory(
return resp.StatusCode, msgs
}
// getAllHistory pages back through target's history
// pageSize messages at a time and returns the message texts,
// oldest first, and the number of pages that held any. It
// stops after maxPages pages, so a before that is ignored
// cannot loop forever.
func (tserver *testServer) getAllHistory(
token, target string,
pageSize, maxPages int,
) ([]string, int) {
tserver.t.Helper()
var (
got []string
pages int
)
// The first page has no before; each later one passes the
// id of the oldest message on the page fetched before it.
before := ""
for range maxPages {
status, page := tserver.getHistory(
token, target, before, pageSize,
)
if status != http.StatusOK {
tserver.t.Fatalf("expected 200, got %d", status)
}
if len(page) == 0 {
break
}
pages++
texts := make([]string, 0, len(page))
for _, msg := range page {
var body []string
err := json.Unmarshal(msg.Body, &body)
if err != nil {
tserver.t.Fatalf("decode body: %v", err)
}
texts = append(texts, body...)
}
got = slices.Insert(got, 0, texts...)
before = page[0].ID
}
return got, pages
}
func TestHistoryPaging(t *testing.T) {
const (
channel = "#paging"
@@ -1579,46 +1633,10 @@ func TestHistoryPaging(t *testing.T) {
})
}
var (
got []string
pages int
got, pages := tserver.getAllHistory(
token, channel, pageSize, msgCount,
)
// The first page has no before; each later one passes the
// id of the oldest message on the page fetched before it.
before := ""
for range msgCount {
status, page := tserver.getHistory(
token, channel, before, pageSize,
)
if status != http.StatusOK {
t.Fatalf("expected 200, got %d", status)
}
if len(page) == 0 {
break
}
pages++
texts := make([]string, 0, len(page))
for _, msg := range page {
var body []string
err := json.Unmarshal(msg.Body, &body)
if err != nil {
t.Fatalf("decode body: %v", err)
}
texts = append(texts, body...)
}
got = slices.Insert(got, 0, texts...)
before = page[0].ID
}
if pages != wantPages {
t.Fatalf(
"expected %d pages, got %d", wantPages, pages,
@@ -1630,6 +1648,81 @@ func TestHistoryPaging(t *testing.T) {
}
}
// TestHistoryDMPaging pages through a nick's DM history,
// which holds the DMs its session got under an earlier nick
// and none sent to another session, and checks that the id
// of a DM sent to another session is not accepted as before.
func TestHistoryDMPaging(t *testing.T) {
const (
firstNick = "readerFirst"
laterNick = "readerLater"
otherNick = "otherReader"
// DMs sent to firstNick before the nick change.
sentEarly = 2
pageSize = 2
wantPages = 3
)
tserver := newTestServer(t)
senderToken := tserver.createSession("dmPager")
readerToken := tserver.createSession(firstNick)
otherToken := tserver.createSession(otherNick)
sendDM := func(to, text string) {
tserver.sendCommand(senderToken, map[string]any{
commandKey: privmsgCmd,
toKey: to,
bodyKey: []string{text},
})
}
want := []string{"dm 1", "dm 2", "dm 3", "dm 4", "dm 5"}
for _, text := range want[:sentEarly] {
sendDM(firstNick, text)
}
_, result := tserver.sendCommand(readerToken, map[string]any{
commandKey: irc.CmdNick,
bodyKey: []string{laterNick},
})
if result[nickKey] != laterNick {
t.Fatalf("expected nick %s, got %v", laterNick, result)
}
sendDM(otherNick, "not for the reader")
for _, text := range want[sentEarly:] {
sendDM(laterNick, text)
}
got, pages := tserver.getAllHistory(
readerToken, laterNick, pageSize, len(want),
)
if pages != wantPages {
t.Fatalf(
"expected %d pages, got %d", wantPages, pages,
)
}
if !slices.Equal(got, want) {
t.Fatalf("expected %q, got %q", want, got)
}
_, otherDMs := tserver.getHistory(otherToken, otherNick, "", 1)
if len(otherDMs) != 1 {
t.Fatalf("expected 1 DM, got %d", len(otherDMs))
}
status, _ := tserver.getHistory(
readerToken, laterNick, otherDMs[0].ID, pageSize,
)
if status != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", status)
}
}
func TestHistoryUnknownBefore(t *testing.T) {
tserver := newTestServer(t)
token := tserver.createSession("lost")