add: test cases for rcon

This commit is contained in:
2026-02-13 14:14:08 -08:00
parent 7c1697660f
commit db8304bebd
6 changed files with 147 additions and 73 deletions

5
go.mod
View File

@@ -2,4 +2,7 @@ module tipsy.codes/charles/mc-god/v2
go 1.25.6
require github.com/gorcon/rcon v1.4.0
require (
github.com/google/go-cmp v0.7.0
github.com/gorcon/rcon v1.4.0
)

2
go.sum
View File

@@ -1,2 +1,4 @@
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorcon/rcon v1.4.0 h1:pYwZ8Rhcgfh/LhdPBncecuEo5thoFvPIuMSWovz1FME=
github.com/gorcon/rcon v1.4.0/go.mod h1:M6v6sNmr/NET9YIf+2rq+cIjTBridoy62uzQ58WgC1I=

View File

@@ -44,17 +44,8 @@ The package expects these environment variables to be set:
- `RCON_ADDRESS` - The address of the Minecraft server (e.g., "localhost:25575")
- `RCON_PASSWORD` - The RCON password for authentication
## Methods
## Testing
- `New(address, password)` - Create a new RCON client
- `Execute(command)` - Execute a command on the server
- `SetWeather(weather)` - Set the weather (clear, rain, thunder)
- `SetTime(timeValue)` - Set the time (day, night, noon, midnight, or numeric)
- `SetDifficulty(difficulty)` - Set the difficulty level (peaceful, easy, normal, hard)
- `GetServerInfo()` - Get server version information
- `Close()` - Close the RCON connection
- `HealthCheck()` - Verify the connection is working
- `ExecuteWithTimeout()` - Execute command with timeout
- `ConnectWithTimeout()` - Connect with timeout
- `GetEnvCredentials()` - Get credentials from environment
- `NewFromEnv()` - Create client from environment variables
`rconmock.go` contains a mock rcon server that accepts and logs requests from the user. It can be configured to return errors, or success (empty body strings). It logs the recieved message bodies.
The protocol is described at https://developer.valvesoftware.com/wiki/Source_RCON_Protocol.

View File

@@ -0,0 +1,109 @@
package rcon
import (
"fmt"
"net"
"sync"
"github.com/gorcon/rcon"
)
// MockRCONServer simulates an RCON server for testing purposes
type MockRCONServer struct {
listener net.Listener
Commands []string
Errors []error
Messages []string
}
// Start starts the mock RCON server on a random available port
func (m *MockRCONServer) Start() (func() *RCONResults, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, fmt.Errorf("failed to start mock server: %w", err)
}
m.listener = listener
results := &RCONResults{}
done := sync.WaitGroup{}
done.Go(func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go m.handleConnection(conn, results)
}
})
return func() *RCONResults {
m.stop()
done.Wait()
return results
}, nil
}
// Stop stops the mock RCON server
func (m *MockRCONServer) stop() error {
if m.listener != nil {
return m.listener.Close()
}
return nil
}
// Address returns the address the mock server is listening on
func (m *MockRCONServer) Address() string {
if m.listener != nil {
return m.listener.Addr().String()
}
return ""
}
// handleConnection handles individual client connections
func (m *MockRCONServer) handleConnection(conn net.Conn, results *RCONResults) {
defer conn.Close()
for {
packet := &rcon.Packet{}
if _, err := packet.ReadFrom(conn); err != nil {
results.AppendError(err)
return
}
results.AppendMessage(packet.Body())
var respPacket *rcon.Packet
switch packet.Type {
case rcon.SERVERDATA_AUTH:
respPacket = rcon.NewPacket(rcon.SERVERDATA_AUTH_RESPONSE, packet.ID, "")
case rcon.SERVERDATA_EXECCOMMAND:
respPacket = rcon.NewPacket(rcon.SERVERDATA_RESPONSE_VALUE, packet.ID, "")
default:
results.AppendError(fmt.Errorf("unknown packet: %v", packet.Type))
return
}
if _, err := respPacket.WriteTo(conn); err != nil {
results.AppendError(err)
return
}
}
}
type RCONResults struct {
mu sync.Mutex
Messages []string
Errors []error
}
func (r *RCONResults) AppendError(err error) {
r.mu.Lock()
defer r.mu.Unlock()
r.Errors = append(r.Errors, err)
}
func (r *RCONResults) AppendMessage(msg string) {
r.mu.Lock()
defer r.mu.Unlock()
r.Messages = append(r.Messages, msg)
}

View File

@@ -56,61 +56,17 @@ func (c *Client) Ping() error {
// SetWeather sets the weather in the Minecraft world
func (c *Client) SetWeather(weather string) error {
var command string
switch weather {
case "clear":
command = "weather clear"
case "rain":
command = "weather rain"
case "thunder":
command = "weather thunder"
default:
return fmt.Errorf("invalid weather type: %s", weather)
}
_, err := c.Execute(command)
return err
return fmt.Errorf("not implemented")
}
// SetTime sets the time in the Minecraft world
func (c *Client) SetTime(timeValue string) error {
var command string
switch timeValue {
case "day":
command = "time set day"
case "night":
command = "time set night"
case "noon":
command = "time set noon"
case "midnight":
command = "time set midnight"
default:
// Assume it's a numeric value
command = fmt.Sprintf("time set %s", timeValue)
}
_, err := c.Execute(command)
return err
return fmt.Errorf("not implemented")
}
// SetDifficulty sets the difficulty level
func (c *Client) SetDifficulty(difficulty string) error {
var command string
switch difficulty {
case "peaceful":
command = "difficulty peaceful"
case "easy":
command = "difficulty easy"
case "normal":
command = "difficulty normal"
case "hard":
command = "difficulty hard"
default:
return fmt.Errorf("invalid difficulty level: %s", difficulty)
}
_, err := c.Execute(command)
return err
return fmt.Errorf("not implemented")
}
// GetServerInfo returns basic server information
@@ -118,14 +74,6 @@ func (c *Client) GetServerInfo() (string, error) {
return c.Execute("version")
}
// TailLogs starts tailing the server logs
func (c *Client) TailLogs(ctx context.Context, handler func(string)) error {
// This is a placeholder implementation
// In a real implementation, this would need to be connected to actual log tailing
// For now, we'll just return an error to indicate this is not implemented
return fmt.Errorf("log tailing not implemented in this version")
}
// ConnectWithTimeout attempts to connect with a timeout
func ConnectWithTimeout(address, password string, timeout time.Duration) (*Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)

View File

@@ -2,13 +2,34 @@ package rcon
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestNew(t *testing.T) {
// This is a placeholder test since we can't actually connect to a server
// in a test environment without a real Minecraft server
// The actual functionality will be tested with integration tests
t.Log("RCON package tests - placeholder for actual tests")
serv := MockRCONServer{}
done, err := serv.Start()
if err != nil {
t.Fatalf("failed to start server: %v", err)
}
password := "abc123"
client, err := New(serv.Address(), password)
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
if _, err = client.Execute("Hello world!"); err != nil {
t.Fatalf("failed to run command: %v", err)
}
results := done()
want := []string{
"abc123",
"Hello world!",
}
if diff := cmp.Diff(want, results.Messages); diff != "" {
t.Errorf("Got diff (-want +got):\n%s", diff)
}
}
func TestGetEnvCredentials(t *testing.T) {