Add the tier 3 utility IRC commands (closes #87)
check / check (push) Waiting to run

USERHOST, VERSION, ADMIN, INFO, TIME, KILL and WALLOPS work on the
HTTP API and on the IRC listener. The USERHOST reply, the VERSION and
INFO text, KILL and WALLOPS are each one service function that both
transports call. User mode +w selects who receives WALLOPS. MODE on
your own nick queries or changes your user modes through one parser
on both transports: the whole mode string is checked before anything
is stored, and both flags are stored in one transaction. KILL closes
the victim's IRC connection after sending it KILL and ERROR. The
sessions table gains is_wallops in 001_initial.sql, as the pre-1.0
rule in REPO_POLICIES.md asks.

Model: opus-5-5
This commit is contained in:
2026-10-08 01:13:36 +00:00
parent b119b094f5
commit a7faabb68f
19 changed files with 2498 additions and 127 deletions
+116
View File
@@ -1491,3 +1491,119 @@ func TestChannelUserLimit(t *testing.T) {
t.Fatalf("expected 0, got %d", limit)
}
}
// TestSetSessionUserModesIsAtomic makes the is_oper write
// fail after the is_wallops write has run, and checks that
// the wallops change was rolled back.
func TestSetSessionUserModesIsAtomic(t *testing.T) {
t.Parallel()
database := setupTestDB(t)
ctx := t.Context()
sessionID, _, _, err := database.CreateSession(
ctx, "alice", "", "", "",
)
if err != nil {
t.Fatal(err)
}
err = database.ExecForTest(ctx,
`CREATE TRIGGER reject_oper
BEFORE UPDATE OF is_oper ON sessions
BEGIN SELECT RAISE(ABORT, 'oper write rejected');
END`,
)
if err != nil {
t.Fatal(err)
}
wallops := true
oper := false
err = database.SetSessionUserModes(
ctx, sessionID, &wallops, &oper,
)
if err == nil {
t.Fatal("expected the rejected oper write to fail")
}
gotWallops, err := database.IsSessionWallops(
ctx, sessionID,
)
if err != nil {
t.Fatal(err)
}
if gotWallops {
t.Error("wallops was stored although the change failed")
}
}
// TestSetSessionUserModesAppliesBoth checks that both flags
// are written, and that a nil flag is left as it is.
func TestSetSessionUserModesAppliesBoth(t *testing.T) {
t.Parallel()
database := setupTestDB(t)
ctx := t.Context()
sessionID, _, _, err := database.CreateSession(
ctx, "alice", "", "", "",
)
if err != nil {
t.Fatal(err)
}
err = database.SetSessionOper(ctx, sessionID, true)
if err != nil {
t.Fatal(err)
}
wallops := true
oper := false
err = database.SetSessionUserModes(
ctx, sessionID, &wallops, &oper,
)
if err != nil {
t.Fatal(err)
}
gotWallops, err := database.IsSessionWallops(
ctx, sessionID,
)
if err != nil {
t.Fatal(err)
}
gotOper, err := database.IsSessionOper(ctx, sessionID)
if err != nil {
t.Fatal(err)
}
if !gotWallops || gotOper {
t.Errorf(
"want wallops=true oper=false, got %v/%v",
gotWallops, gotOper,
)
}
err = database.SetSessionUserModes(
ctx, sessionID, nil, nil,
)
if err != nil {
t.Fatal(err)
}
gotWallops, err = database.IsSessionWallops(
ctx, sessionID,
)
if err != nil {
t.Fatal(err)
}
if !gotWallops {
t.Error("a nil wallops flag cleared wallops")
}
}