Files
neoirc/internal/ircserver/conn_test.go
T
clawbot c0eef92d23
check / check (push) Waiting to run
Share the ADMIN text and KILL reason, and test the review's gaps
ADMIN's location and email lines come from service.AdminLines, and
KillUser applies the default reason, so both transports send the same
reply. The Disconnect test now allows a quarter of one write window,
so a Disconnect that writes before returning fails it. QueryUserMode
and UserhostReply are tested with a read that fails. The handler tests
again cover USERHOST for a nick with no session, MODE +o on your own
nick, and the status of a successful KILL. handleMode's comment says
what it does.

Model: opus-5-5
2026-10-08 03:01:13 +00:00

103 lines
2.3 KiB
Go

package ircserver_test
import (
"bufio"
"log/slog"
"net"
"os"
"strings"
"testing"
"time"
"sneak.berlin/go/neoirc/internal/config"
"sneak.berlin/go/neoirc/internal/ircserver"
)
// newKillVictim returns a Conn for the nick victim whose
// socket is one end of a net.Pipe, and the other end, from
// which the test reads what the victim is sent. A write to
// the pipe blocks until the other end reads.
func newKillVictim(t *testing.T) (*ircserver.Conn, net.Conn) {
t.Helper()
serverSide, clientSide := net.Pipe()
t.Cleanup(func() { _ = clientSide.Close() })
log := slog.New(slog.NewTextHandler(
os.Stderr,
&slog.HandlerOptions{Level: slog.LevelError},
))
cfg := &config.Config{ServerName: testServerName}
return ircserver.NewTestConn(
log, cfg, serverSide, "victim",
), clientSide
}
// TestDisconnectDoesNotBlockOnUnresponsiveVictim checks
// that Disconnect returns although the victim never reads,
// so a KILL cannot stall the operator who sent it. The
// limit is well below one KillWriteWindow, so a Disconnect
// that waits on any write to the victim fails.
func TestDisconnectDoesNotBlockOnUnresponsiveVictim(
t *testing.T,
) {
t.Parallel()
victim, _ := newKillVictim(t)
returned := make(chan struct{})
go func() {
victim.Disconnect("killed by oper")
close(returned)
}()
select {
case <-returned:
case <-time.After(ircserver.KillWriteWindow / 4):
t.Fatal("Disconnect waited for the victim to read")
}
}
// TestDisconnectNotifiesAndClosesVictim checks that a
// victim that reads is sent KILL and ERROR, and that its
// connection is then closed.
func TestDisconnectNotifiesAndClosesVictim(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("killed by oper")
var lines []string
select {
case lines = <-received:
case <-time.After(testTimeout):
t.Fatal("the victim's connection was not closed")
}
joined := strings.Join(lines, "\n")
if !strings.Contains(joined, "KILL victim :killed by oper") {
t.Errorf("missing KILL line, got: %q", joined)
}
if !strings.Contains(joined, "ERROR :Closing Link:") {
t.Errorf("missing ERROR line, got: %q", joined)
}
}