Files
parasocial/internal/irc/client_test.go
ruinivist 7acb293dea test: add benchmarks for various components
Adds benchmarking tests to ensure the application remains low on resources.
Covers payload parsing, auth verification, IRC chat line formatting, and more.
2026-05-31 23:13:18 +00:00

240 lines
5.0 KiB
Go

package irc
import (
"bufio"
"context"
"io"
"net"
"reflect"
"strings"
"testing"
"time"
)
func TestRunOnceSendsAuthJoinAndRespondsToPing(t *testing.T) {
t.Parallel()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
lines := make(chan string, 10)
serverErr := make(chan error, 1)
go func() {
conn, err := listener.Accept()
if err != nil {
serverErr <- err
return
}
defer conn.Close()
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
serverErr <- nil
return
}
serverErr <- err
return
}
line = strings.TrimRight(line, "\r\n")
lines <- line
switch line {
case "NICK viewer":
_, _ = conn.Write([]byte(":tmi.twitch.tv 001 viewer :Welcome\r\n"))
case "JOIN #streamer":
_, _ = conn.Write([]byte("PING :tmi.twitch.tv\r\n"))
case "PONG :tmi.twitch.tv":
_, _ = conn.Write([]byte(":viewer!viewer@viewer.tmi.twitch.tv JOIN #streamer\r\n"))
case "QUIT :parasocial --once complete":
serverErr <- nil
return
}
}
}()
client := &Client{
Addr: listener.Addr().String(),
Login: "viewer",
Token: "token",
Streamer: "streamer",
Once: true,
Out: io.Discard,
}
if err := client.Run(context.Background()); err != nil {
t.Fatal(err)
}
want := []string{
"PASS oauth:token",
"NICK viewer",
"JOIN #streamer",
"PONG :tmi.twitch.tv",
"QUIT :parasocial --once complete",
}
for _, expected := range want {
select {
case got := <-lines:
if got != expected {
t.Fatalf("line = %q, want %q", got, expected)
}
case <-time.After(time.Second):
t.Fatalf("timed out waiting for %q", expected)
}
}
select {
case err := <-serverErr:
if err != nil {
t.Fatal(err)
}
case <-time.After(time.Second):
t.Fatal("server did not finish")
}
}
func TestRunEmitsJoinAndPostJoinEvents(t *testing.T) {
t.Parallel()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
events := make(chan Event, 4)
serverErr := make(chan error, 1)
go func() {
conn, err := listener.Accept()
if err != nil {
serverErr <- err
return
}
defer conn.Close()
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil {
serverErr <- err
return
}
line = strings.TrimRight(line, "\r\n")
switch line {
case "NICK viewer":
_, _ = conn.Write([]byte(":tmi.twitch.tv 001 viewer :Welcome\r\n"))
case "JOIN #streamer":
_, _ = conn.Write([]byte(":viewer!viewer@viewer.tmi.twitch.tv JOIN #streamer\r\n"))
_, _ = conn.Write([]byte(":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello\r\n"))
_, _ = conn.Write([]byte("PING :tmi.twitch.tv\r\n"))
case "PONG :tmi.twitch.tv":
serverErr <- nil
return
}
}
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client := &Client{
Addr: listener.Addr().String(),
Login: "viewer",
Token: "token",
Streamer: "streamer",
Events: func(event Event) {
events <- event
},
Out: io.Discard,
}
done := make(chan error, 1)
go func() {
done <- client.Run(ctx)
}()
var got []Event
for len(got) < 2 {
select {
case event := <-events:
got = append(got, event)
if event.Line == ":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello" {
cancel()
}
case <-time.After(time.Second):
t.Fatal("timed out waiting for IRC events")
}
}
if err := <-done; err != nil {
t.Fatal(err)
}
if err := <-serverErr; err != nil && err != io.EOF {
t.Fatal(err)
}
want := []Event{
{Streamer: "streamer", State: StateJoined, Line: "Joined #streamer as viewer"},
{Streamer: "streamer", Line: ":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello"},
}
if !reflect.DeepEqual(got[:2], want) {
t.Fatalf("events = %#v, want %#v", got[:2], want)
}
}
func TestAuthFailureReturnsError(t *testing.T) {
t.Parallel()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
go func() {
conn, err := listener.Accept()
if err != nil {
return
}
defer conn.Close()
_, _ = conn.Write([]byte(":tmi.twitch.tv NOTICE * :Login authentication failed\r\n"))
}()
client := &Client{
Addr: listener.Addr().String(),
Login: "viewer",
Token: "bad-token",
Streamer: "streamer",
Out: io.Discard,
}
err = client.Run(context.Background())
if err == nil {
t.Fatal("expected auth failure")
}
if !strings.Contains(err.Error(), "authentication failure") {
t.Fatalf("err = %v", err)
}
}
func BenchmarkPingPayload(b *testing.B) {
line := "PING :tmi.twitch.tv"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = pingPayload(line)
}
}
func BenchmarkIsJoinConfirmation(b *testing.B) {
line := ":jules!jules@jules.tmi.twitch.tv JOIN #parasocial"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = isJoinConfirmation(line, "jules", "parasocial")
}
}