check / check (push) Waiting to run
The shared files are fetched from sneak/prompts dd4027b, with this repository's own entries after the shared content in .gitignore, .editorconfig and .dockerignore. Lint and tests are Dockerfile phases (golangci-lint v2.14.0, Debian Go 1.24.13) that the build stage depends on, and the Makefile targets call the script/ entrypoints. make fmt also formats Markdown with prettier. Fixes for the new lint findings: the auth cookie is always Secure, an IRC connection's relay goroutine stops when the connection closes, and repeated strings are constants. neoirc-cli treats a plain-HTTP server on localhost or a loopback address as secure, so local use keeps its session. Whether the 60-second test cap covers building the test phase is open on sneak/prompts issue 113. Model: opus-5-5
341 lines
6.5 KiB
Go
341 lines
6.5 KiB
Go
package ircserver_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"sneak.berlin/go/neoirc/internal/ircserver"
|
|
"sneak.berlin/go/neoirc/pkg/irc"
|
|
)
|
|
|
|
//nolint:funlen // table-driven test
|
|
func TestParseMessage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
nick = "alice"
|
|
channel = "#general"
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want *ircserver.Message
|
|
wantNil bool
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: "",
|
|
want: nil,
|
|
wantNil: true,
|
|
},
|
|
{
|
|
name: "simple command",
|
|
input: irc.CmdPing,
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdPing,
|
|
Params: nil,
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "command with one param",
|
|
input: "NICK alice",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "NICK",
|
|
Params: []string{nick},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "command case insensitive",
|
|
input: "nick Alice",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "NICK",
|
|
Params: []string{"Alice"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "privmsg with trailing",
|
|
input: "PRIVMSG #general :hello world",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdPrivmsg,
|
|
Params: []string{channel, "hello world"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "with prefix",
|
|
input: ":server.example.com 001 alice :Welcome to IRC",
|
|
want: &ircserver.Message{
|
|
Prefix: "server.example.com",
|
|
Command: "001",
|
|
Params: []string{nick, "Welcome to IRC"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "user command",
|
|
input: "USER alice 0 * :Alice Smith",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "USER",
|
|
Params: []string{
|
|
nick, "0", "*", "Alice Smith",
|
|
},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "join channel",
|
|
input: "JOIN #general",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "JOIN",
|
|
Params: []string{channel},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "quit with trailing",
|
|
input: "QUIT :leaving now",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdQuit,
|
|
Params: []string{"leaving now"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "quit without reason",
|
|
input: irc.CmdQuit,
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdQuit,
|
|
Params: nil,
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "mode query",
|
|
input: "MODE #general",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "MODE",
|
|
Params: []string{channel},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "kick with reason",
|
|
input: "KICK #general bob :misbehaving",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "KICK",
|
|
Params: []string{
|
|
channel, "bob", "misbehaving",
|
|
},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "empty trailing",
|
|
input: "PRIVMSG #general :",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdPrivmsg,
|
|
Params: []string{channel, ""},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "pass command",
|
|
input: "PASS mysecret",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "PASS",
|
|
Params: []string{"mysecret"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "ping with server",
|
|
input: "PING :irc.example.com",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: irc.CmdPing,
|
|
Params: []string{"irc.example.com"},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
{
|
|
name: "topic with trailing spaces",
|
|
input: "TOPIC #general :Welcome to the channel!",
|
|
want: &ircserver.Message{
|
|
Prefix: "",
|
|
Command: "TOPIC",
|
|
Params: []string{
|
|
channel,
|
|
"Welcome to the channel!",
|
|
},
|
|
},
|
|
wantNil: false,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := ircserver.ParseMessage(testCase.input)
|
|
if testCase.wantNil {
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %+v", got)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if got == nil {
|
|
t.Fatal("expected non-nil message")
|
|
}
|
|
|
|
if got.Prefix != testCase.want.Prefix {
|
|
t.Errorf(
|
|
"prefix: got %q, want %q",
|
|
got.Prefix, testCase.want.Prefix,
|
|
)
|
|
}
|
|
|
|
if got.Command != testCase.want.Command {
|
|
t.Errorf(
|
|
"command: got %q, want %q",
|
|
got.Command, testCase.want.Command,
|
|
)
|
|
}
|
|
|
|
if len(got.Params) != len(testCase.want.Params) {
|
|
t.Fatalf(
|
|
"params length: got %d, want %d (%v vs %v)",
|
|
len(got.Params),
|
|
len(testCase.want.Params),
|
|
got.Params,
|
|
testCase.want.Params,
|
|
)
|
|
}
|
|
|
|
for i, p := range got.Params {
|
|
if p != testCase.want.Params[i] {
|
|
t.Errorf(
|
|
"param[%d]: got %q, want %q",
|
|
i, p, testCase.want.Params[i],
|
|
)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFormatMessage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
nick = "alice"
|
|
channel = "#general"
|
|
serverName = "server"
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
prefix string
|
|
command string
|
|
params []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "simple command",
|
|
prefix: "",
|
|
command: irc.CmdPing,
|
|
params: nil,
|
|
want: irc.CmdPing,
|
|
},
|
|
{
|
|
name: "with prefix",
|
|
prefix: serverName,
|
|
command: "PONG",
|
|
params: []string{serverName},
|
|
want: ":server PONG server",
|
|
},
|
|
{
|
|
name: "privmsg with trailing",
|
|
prefix: "alice!alice@host",
|
|
command: irc.CmdPrivmsg,
|
|
params: []string{channel, "hello world"},
|
|
want: ":alice!alice@host PRIVMSG #general :hello world",
|
|
},
|
|
{
|
|
name: "numeric reply",
|
|
prefix: serverName,
|
|
command: "001",
|
|
params: []string{nick, "Welcome to IRC"},
|
|
want: ":server 001 alice :Welcome to IRC",
|
|
},
|
|
{
|
|
name: "empty trailing",
|
|
prefix: serverName,
|
|
command: irc.CmdPrivmsg,
|
|
params: []string{"#chan", ""},
|
|
want: ":server PRIVMSG #chan :",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := ircserver.FormatMessage(
|
|
testCase.prefix, testCase.command, testCase.params...,
|
|
)
|
|
if got != testCase.want {
|
|
t.Errorf("got %q, want %q", got, testCase.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseFormatRoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Round-trip only works for lines where the last
|
|
// parameter either contains a space (gets ':' prefix
|
|
// on format) or is a non-trailing single token.
|
|
lines := []string{
|
|
irc.CmdPing,
|
|
"NICK alice",
|
|
"PRIVMSG #general :hello world",
|
|
"JOIN #general",
|
|
"MODE #general",
|
|
}
|
|
|
|
for _, line := range lines {
|
|
msg := ircserver.ParseMessage(line)
|
|
if msg == nil {
|
|
t.Fatalf("failed to parse: %q", line)
|
|
}
|
|
|
|
formatted := ircserver.FormatMessage(
|
|
msg.Prefix, msg.Command, msg.Params...,
|
|
)
|
|
if formatted != line {
|
|
t.Errorf(
|
|
"round-trip failed: input %q, got %q",
|
|
line, formatted,
|
|
)
|
|
}
|
|
}
|
|
}
|