check / check (push) Waiting to run
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
103 lines
2.3 KiB
Go
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)
|
|
}
|
|
}
|