//nolint:paralleltest // global viper, as in api_test.go package handlers_test import ( "net/http" "strings" "testing" "sneak.berlin/go/neoirc/pkg/irc" ) // Each test starts one server and reuses it for all its // checks, and makes as few requests as it can: servers, // sessions and requests are what makes this package's tests // slow. // sendAndPoll sends cmd from the session token and returns // the messages after lastID that the session has been sent, // and the ID of the last of them. func sendAndPoll( tserver *testServer, token string, lastID int64, cmd map[string]any, ) ([]map[string]any, int64) { tserver.t.Helper() tserver.sendCommand(token, cmd) return tserver.pollMessages(token, lastID) } // newOperSession creates a session for nick, makes it a // server operator, and returns its token and the ID of the // last message it has been sent. func newOperSession( tserver *testServer, nick string, ) (string, int64) { tserver.t.Helper() token := tserver.createSession(nick) _, lastID := sendAndPoll(tserver, token, 0, map[string]any{ commandKey: irc.CmdOper, bodyKey: []string{testOperName, testOperPassword}, }) return token, lastID } // numericBody returns the first body line of the message in // msgs with the given numeric, and fails the test if there // is none. func numericBody( t *testing.T, msgs []map[string]any, numeric string, ) string { t.Helper() msg := findNumericWithParams(msgs, numeric) if msg == nil { t.Fatalf("expected numeric %s, got %v", numeric, msgs) } lines, _ := msg[bodyKey].([]any) if len(lines) == 0 { return "" } line, _ := lines[0].(string) return line } func TestUserhost(t *testing.T) { tserver := newTestServer(t) token := tserver.createSession("alice") tserver.createSession("bob") _, lastID := tserver.pollMessages(token, 0) msgs, lastID := sendAndPoll(tserver, token, lastID, map[string]any{ commandKey: irc.CmdUserhost, bodyKey: []string{"alice", "bob"}, }) body := numericBody(t, msgs, "302") if !strings.HasPrefix(body, "alice=+alice@") || !strings.Contains(body, " bob=+bob@") { t.Errorf("expected alice and bob, got %q", body) } msgs, _ = sendAndPoll(tserver, token, lastID, map[string]any{ commandKey: irc.CmdUserhost, }) if !findNumeric(msgs, "461") { t.Errorf("expected ERR_NEEDMOREPARAMS (461), got %v", msgs) } } func TestVersionAdminInfoTime(t *testing.T) { tserver := newTestServer(t) token := tserver.createSession("frank") _, lastID := tserver.pollMessages(token, 0) msgs, lastID := sendAndPoll(tserver, token, lastID, map[string]any{ commandKey: irc.CmdVersion, }) params := getNumericParams(findNumericWithParams(msgs, "351")) if len(params) == 0 || params[0] != "neoirc-test-test." { t.Errorf("expected RPL_VERSION neoirc-test-test., got %v", msgs) } for _, check := range []struct { command string numerics []string }{ {irc.CmdAdmin, []string{"256", "257", "258", "259"}}, {irc.CmdInfo, []string{"371", "374"}}, {irc.CmdTime, []string{"391"}}, } { msgs, lastID = sendAndPoll(tserver, token, lastID, map[string]any{ commandKey: check.command, }) for _, numeric := range check.numerics { if !findNumeric(msgs, numeric) { t.Errorf( "%s: expected %s, got %v", check.command, numeric, msgs, ) } } } } func TestKill(t *testing.T) { tserver := newTestServerWithOper(t) victimToken := tserver.createSession("victim") observerToken := tserver.createSession("observer") notOperToken := tserver.createSession("notoper") operToken, operLastID := newOperSession(tserver, "killer") for _, token := range []string{victimToken, observerToken} { tserver.sendCommand(token, map[string]any{ commandKey: joinCmd, toKey: "#killtest", }) } _, notOperLastID := tserver.pollMessages(notOperToken, 0) msgs, _ := sendAndPoll(tserver, notOperToken, notOperLastID, map[string]any{ commandKey: irc.CmdKill, bodyKey: []string{"victim"}, }) if !findNumeric(msgs, "481") { t.Errorf("expected ERR_NOPRIVILEGES (481), got %v", msgs) } for _, check := range []struct { numeric string body []string }{ {"461", []string{}}, {"401", []string{"ghost"}}, {"483", []string{"killer"}}, } { msgs, operLastID = sendAndPoll(tserver, operToken, operLastID, map[string]any{ commandKey: irc.CmdKill, bodyKey: check.body, }) if !findNumeric(msgs, check.numeric) { t.Errorf( "KILL %v: expected %s, got %v", check.body, check.numeric, msgs, ) } } _, observerLastID := tserver.pollMessages(observerToken, 0) status, result := tserver.sendCommand(operToken, map[string]any{ commandKey: irc.CmdKill, bodyKey: []string{"victim", "go away"}, }) if status != http.StatusOK { t.Fatalf("expected 200, got %d: %v", status, result) } msgs, _ = tserver.pollMessages(observerToken, observerLastID) if !findMessage(msgs, irc.CmdQuit, "victim") { t.Errorf("expected the observer to see QUIT, got %v", msgs) } status, _ = tserver.getState(victimToken) if status != http.StatusUnauthorized { t.Errorf("expected the victim's session gone, got %d", status) } } func TestWallops(t *testing.T) { tserver := newTestServerWithOper(t) receiverToken := tserver.createSession("receiver") plainToken := tserver.createSession("plain") operToken, operLastID := newOperSession(tserver, "walloper") _, receiverLastID := sendAndPoll(tserver, receiverToken, 0, map[string]any{ commandKey: modeCmd, toKey: "receiver", bodyKey: []string{"+w"}, }) _, plainLastID := tserver.pollMessages(plainToken, 0) msgs, plainLastID := sendAndPoll(tserver, plainToken, plainLastID, map[string]any{ commandKey: irc.CmdWallops, bodyKey: []string{"not allowed"}, }) if !findNumeric(msgs, "481") { t.Errorf("expected ERR_NOPRIVILEGES (481), got %v", msgs) } msgs, _ = sendAndPoll(tserver, operToken, operLastID, map[string]any{ commandKey: irc.CmdWallops, }) if !findNumeric(msgs, "461") { t.Errorf("expected ERR_NEEDMOREPARAMS (461), got %v", msgs) } tserver.sendCommand(operToken, map[string]any{ commandKey: irc.CmdWallops, bodyKey: []string{"server going down"}, }) msgs, _ = tserver.pollMessages(receiverToken, receiverLastID) if !findMessage(msgs, irc.CmdWallops, "walloper") { t.Errorf("expected WALLOPS for the +w user, got %v", msgs) } msgs, _ = tserver.pollMessages(plainToken, plainLastID) if findMessage(msgs, irc.CmdWallops, "walloper") { t.Errorf("WALLOPS reached a user without +w: %v", msgs) } } func TestUserMode(t *testing.T) { const ( nick = "alice" other = "other" ) tserver := newTestServerWithOper(t) token := tserver.createSession(nick) otherToken := tserver.createSession(other) operToken, operLastID := newOperSession(tserver, "deoper") // mode sends MODE for target from token, with modeStr // unless it is empty, and returns the session's next // messages. mode := func( token string, lastID int64, target, modeStr string, ) ([]map[string]any, int64) { cmd := map[string]any{commandKey: modeCmd, toKey: target} if modeStr != "" { cmd[bodyKey] = []string{modeStr} } return sendAndPoll(tserver, token, lastID, cmd) } _, otherLastID := mode(otherToken, 0, other, "+w") _, lastID := tserver.pollMessages(token, 0) for _, check := range []struct { target, modeStr, numeric, modes string }{ {nick, "+w", "221", "+w"}, {nick, "", "221", "+w"}, {strings.ToUpper(nick), "-w", "221", "+"}, {nick, "+z", "501", ""}, {other, "", "502", ""}, {other, "-w", "502", ""}, } { var msgs []map[string]any msgs, lastID = mode(token, lastID, check.target, check.modeStr) if check.numeric != "221" { if !findNumeric(msgs, check.numeric) || findNumeric(msgs, "221") { t.Errorf( "MODE %s %s: expected only %s, got %v", check.target, check.modeStr, check.numeric, msgs, ) } continue } got := numericBody(t, msgs, "221") if got != check.modes { t.Errorf( "MODE %s %s: expected %q, got %q", check.target, check.modeStr, check.modes, got, ) } } msgs, _ := mode(otherToken, otherLastID, other, "") if got := numericBody(t, msgs, "221"); got != "+w" { t.Errorf("%s's modes changed to %q", other, got) } msgs, _ = mode(operToken, operLastID, "deoper", "-o") if got := numericBody(t, msgs, "221"); got != "+" { t.Errorf("after -o: expected +, got %q", got) } }