package rcon import ( "bufio" "context" "fmt" "net" "strings" "testing" "time" ) // fakeTelnetServer speaks enough of the 7DTD protocol to exercise dial+auth+exec: // - sends "Please enter password:" on accept // - waits for client password (any line), then sends "Logon successful.\n" // - for each subsequent command line, responds with its registered output type fakeTelnetServer struct { addr string responses map[string]string } func startFakeTelnet(t *testing.T, responses map[string]string) *fakeTelnetServer { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } t.Cleanup(func() { _ = ln.Close() }) fs := &fakeTelnetServer{addr: ln.Addr().String(), responses: responses} go func() { for { conn, err := ln.Accept() if err != nil { return } go fs.handle(conn) } }() return fs } func (fs *fakeTelnetServer) handle(conn net.Conn) { defer conn.Close() _, _ = conn.Write([]byte("*** Connected with 7DTD server.\r\nPlease enter password:")) br := bufio.NewReader(conn) if _, err := br.ReadString('\n'); err != nil { return } _, _ = conn.Write([]byte("\nLogon successful.\n")) for { _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) line, err := br.ReadString('\n') if err != nil { return } cmd := strings.TrimSpace(line) resp, ok := fs.responses[cmd] if !ok { resp = fmt.Sprintf("(unknown command: %s)\n", cmd) } _, _ = conn.Write([]byte(resp)) } } func TestTelnetDialAndExec(t *testing.T) { srv := startFakeTelnet(t, map[string]string{ "lp": "Total of 3 in the game\n\n1. id=1, Alice, pos=(0,0,0)\n2. id=2, Bob, pos=(1,0,0)\n3. id=3, Carol, pos=(2,0,0)\n\n", "say \"hello\"": "*Server: hello\n", }) host, port := splitAddr(t, srv.addr) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() client, err := TelnetDialer{}.Dial(ctx, DialOptions{ Host: host, Port: port, Password: "test", ConnectTimeout: 2 * time.Second, ReadIdle: 150 * time.Millisecond, }) if err != nil { t.Fatalf("Dial: %v", err) } defer client.Close() out, err := client.Exec(ctx, "lp") if err != nil { t.Fatalf("Exec lp: %v", err) } if !strings.Contains(out, "Total of 3 in the game") { t.Errorf("lp output missing expected substring, got: %q", out) } out, err = client.Exec(ctx, `say "hello"`) if err != nil { t.Fatalf(`Exec say: %v`, err) } if !strings.Contains(out, "*Server: hello") { t.Errorf("say output missing, got: %q", out) } } func TestDialerForUnknown(t *testing.T) { if _, err := DialerFor("source_rcon"); err == nil { t.Error("expected error for unknown adapter") } if d, err := DialerFor("telnet"); err != nil || d.Name() != "telnet" { t.Errorf("DialerFor(telnet): d=%v err=%v", d, err) } } func splitAddr(t *testing.T, addr string) (string, int) { t.Helper() host, portStr, err := net.SplitHostPort(addr) if err != nil { t.Fatalf("split %q: %v", addr, err) } var port int if _, err := fmt.Sscanf(portStr, "%d", &port); err != nil { t.Fatalf("port parse: %v", err) } return host, port }