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
This commit was merged in pull request #161.
This commit is contained in:
2026-10-20 19:48:35 +02:00
parent 5100b9a665
commit 164fc93a48
11 changed files with 569 additions and 135 deletions
+73 -1
View File
@@ -164,7 +164,7 @@ func TestFanOut(t *testing.T) {
body, _ := json.Marshal([]string{"hello"}) //nolint:errchkjson
dbID, uuid, err := env.svc.FanOut(
ctx, irc.CmdPrivmsg, "alice", "#test",
ctx, irc.CmdPrivmsg, "alice", "#test", 0, 0,
nil, body, nil,
[]int64{sid1, sid2},
)
@@ -339,6 +339,78 @@ func TestBroadcastQuit(t *testing.T) {
}
}
// TestSendDirectMessageTargetQuits checks that a DM whose
// target quits after its nick is looked up gets the same
// error as a DM to an unknown nick. The trigger deletes the
// target's session just before the DM is stored.
func TestSendDirectMessageTargetQuits(t *testing.T) {
env := newTestEnv(t)
ctx := t.Context()
sid := createSession(ctx, t, env.db, "alice")
createSession(ctx, t, env.db, "bob")
_, err := env.db.GetDB().ExecContext(ctx,
`CREATE TRIGGER delete_recipient
BEFORE INSERT ON messages
BEGIN
DELETE FROM sessions WHERE id = NEW.to_session_id;
END`)
if err != nil {
t.Fatalf("create trigger: %v", err)
}
body, _ := json.Marshal([]string{"hi"}) //nolint:errchkjson
_, err = env.svc.SendDirectMessage(
ctx, sid, "alice", irc.CmdPrivmsg, "bob", body, nil,
)
var ircErr *service.IRCError
if !errors.As(err, &ircErr) || ircErr.Code != irc.ErrNoSuchNick {
t.Fatalf("want ERR_NOSUCHNICK, got %v", err)
}
}
// TestSendChannelMessageChannelDeleted checks that a channel
// message whose channel is deleted after it is looked up gets
// the same error as a message to an unknown channel. The
// trigger deletes the channel just before the message is
// stored.
func TestSendChannelMessageChannelDeleted(t *testing.T) {
env := newTestEnv(t)
ctx := t.Context()
sid := createSession(ctx, t, env.db, "alice")
_, err := env.svc.JoinChannel(ctx, sid, "alice", "#gone", "")
if err != nil {
t.Fatalf("join alice: %v", err)
}
_, err = env.db.GetDB().ExecContext(ctx,
`CREATE TRIGGER delete_recipient
BEFORE INSERT ON messages
BEGIN
DELETE FROM channels WHERE id = NEW.to_channel_id;
END`)
if err != nil {
t.Fatalf("create trigger: %v", err)
}
body, _ := json.Marshal([]string{"hi"}) //nolint:errchkjson
_, _, err = env.svc.SendChannelMessage(
ctx, sid, "alice", irc.CmdPrivmsg, "#gone", body, nil,
)
var ircErr *service.IRCError
if !errors.As(err, &ircErr) ||
ircErr.Code != irc.ErrNoSuchChannel {
t.Fatalf("want ERR_NOSUCHCHANNEL, got %v", err)
}
}
func TestSendChannelMessage_Moderated(t *testing.T) {
env := newTestEnv(t)
ctx := t.Context()