2 Commits
Author SHA1 Message Date
clawbot 65ae37d97a Fix IRC listener relay of bodies and modes, and JOIN names (closes #127)
check / check (push) Waiting to run
The IRC listener relayed only the first line of a PRIVMSG or NOTICE
body; it now sends one line per body line. A relayed MODE change now
carries its key, limit or mask, each value its own parameter, also in
the echo to the user who made it. CR, LF and NUL are removed from each
parameter and from each whole line sent to an IRC listener connection,
so no stored value can start a line of its own. The channel name check
moved into service.JoinChannel, so the IRC listener's JOIN refuses the
same names with 403.

Model: opus-5-5
2026-10-20 11:24:55 +00:00
clawbot 4ab320ab2d Give each handler and service test its own database (closes #131)
check / check (push) Waiting to run
Test databases in internal/handlers and internal/service were named
after the address of the running test. A later test could get the
address of a finished one, and with it a database that test's server
might still hold open. Each test database now gets a random name, as
in internal/ircserver.

A new test in each package opens two test databases at once and checks
that they do not share one; both new tests fail with the old names.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-20 13:04:40 +02:00
3 changed files with 94 additions and 4 deletions
+29 -2
View File
@@ -7,6 +7,7 @@ package handlers_test
import (
"bytes"
"context"
"crypto/rand"
"database/sql"
"encoding/json"
"errors"
@@ -152,9 +153,12 @@ func newTestServerWith(
) *testServer {
t.Helper()
// A random name, so that no other test can share it, not
// even one that has finished but still holds its database
// open.
dbURL := fmt.Sprintf(
"file:test_%p?mode=memory&cache=shared",
t,
"file:%s?mode=memory&cache=shared",
rand.Text(),
)
copyTemplateDB(t, dbURL)
@@ -667,6 +671,29 @@ func TestCreateSessionDuplicate(t *testing.T) {
}
}
// TestEachTestServerHasItsOwnDatabase checks that starting a
// second test server leaves the first one's database alone and
// starts on an empty one of its own.
func TestEachTestServerHasItsOwnDatabase(t *testing.T) {
first := newTestServer(t)
first.createSession("samenick")
second := newTestServer(t)
resp := postSession(t, first, "samenick")
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusConflict {
t.Fatalf(
"first server: expected 409, got %d",
resp.StatusCode,
)
}
second.createSession("samenick")
}
func TestCreateSessionEmpty(t *testing.T) {
tserver := newTestServer(t)
+46
View File
@@ -5,6 +5,7 @@ import (
"log/slog"
"net"
"os"
"slices"
"strings"
"testing"
"time"
@@ -100,3 +101,48 @@ func TestDisconnectNotifiesAndClosesVictim(t *testing.T) {
t.Errorf("missing ERROR line, got: %q", joined)
}
}
// TestDisconnectReasonCannotStartLine checks that CR, LF and
// NUL in a KILL reason are removed from both the KILL line
// and the ERROR line, so the victim gets exactly those two
// lines. The ERROR line is not built with FormatMessage, so
// only the removal in sendWithin protects it.
func TestDisconnectReasonCannotStartLine(t *testing.T) {
t.Parallel()
victim, clientSide := newKillVictim(t)
received := make(chan []string, 1)
go func() {
var lines []string
scanner := bufio.NewScanner(clientSide)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
received <- lines
}()
victim.Disconnect("bye\r\nPRIVMSG victim :forged\x00")
var lines []string
select {
case lines = <-received:
case <-time.After(testTimeout):
t.Fatal("the victim's connection was not closed")
}
// The test connection has no host name, hence the two
// spaces after "Closing Link:".
want := []string{
":" + testServerName +
" KILL victim :byePRIVMSG victim :forged",
"ERROR :Closing Link: (byePRIVMSG victim :forged)",
}
if !slices.Equal(lines, want) {
t.Errorf("got lines %q, want %q", lines, want)
}
}
+19 -2
View File
@@ -6,6 +6,7 @@ package service_test
import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
@@ -43,9 +44,12 @@ type testEnv struct {
func newTestEnv(t *testing.T) *testEnv {
t.Helper()
// A random name, so that no other test can share it, not
// even one that has finished but still holds its database
// open.
dbURL := fmt.Sprintf(
"file:svc_test_%p?mode=memory&cache=shared",
t,
"file:%s?mode=memory&cache=shared",
rand.Text(),
)
var (
@@ -137,6 +141,19 @@ func createSession(
return sessionID
}
// TestEachTestEnvHasItsOwnDatabase checks that a test
// environment starts on an empty database while another one
// is still open.
func TestEachTestEnvHasItsOwnDatabase(t *testing.T) {
ctx := t.Context()
first := newTestEnv(t)
createSession(ctx, t, first.db, "samenick")
second := newTestEnv(t)
createSession(ctx, t, second.db, "samenick")
}
func TestFanOut(t *testing.T) {
env := newTestEnv(t)
ctx := t.Context()