add: test cases for rcon
This commit is contained in:
5
go.mod
5
go.mod
@@ -2,4 +2,7 @@ module tipsy.codes/charles/mc-god/v2
|
|||||||
|
|
||||||
go 1.25.6
|
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
2
go.sum
@@ -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 h1:pYwZ8Rhcgfh/LhdPBncecuEo5thoFvPIuMSWovz1FME=
|
||||||
github.com/gorcon/rcon v1.4.0/go.mod h1:M6v6sNmr/NET9YIf+2rq+cIjTBridoy62uzQ58WgC1I=
|
github.com/gorcon/rcon v1.4.0/go.mod h1:M6v6sNmr/NET9YIf+2rq+cIjTBridoy62uzQ58WgC1I=
|
||||||
|
|||||||
@@ -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_ADDRESS` - The address of the Minecraft server (e.g., "localhost:25575")
|
||||||
- `RCON_PASSWORD` - The RCON password for authentication
|
- `RCON_PASSWORD` - The RCON password for authentication
|
||||||
|
|
||||||
## Methods
|
## Testing
|
||||||
|
|
||||||
- `New(address, password)` - Create a new RCON client
|
`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.
|
||||||
- `Execute(command)` - Execute a command on the server
|
|
||||||
- `SetWeather(weather)` - Set the weather (clear, rain, thunder)
|
The protocol is described at https://developer.valvesoftware.com/wiki/Source_RCON_Protocol.
|
||||||
- `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
|
|
||||||
|
|||||||
109
internal/pkg/rcon/mockrcon.go
Normal file
109
internal/pkg/rcon/mockrcon.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -56,61 +56,17 @@ func (c *Client) Ping() error {
|
|||||||
|
|
||||||
// SetWeather sets the weather in the Minecraft world
|
// SetWeather sets the weather in the Minecraft world
|
||||||
func (c *Client) SetWeather(weather string) error {
|
func (c *Client) SetWeather(weather string) error {
|
||||||
var command string
|
return fmt.Errorf("not implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTime sets the time in the Minecraft world
|
// SetTime sets the time in the Minecraft world
|
||||||
func (c *Client) SetTime(timeValue string) error {
|
func (c *Client) SetTime(timeValue string) error {
|
||||||
var command string
|
return fmt.Errorf("not implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDifficulty sets the difficulty level
|
// SetDifficulty sets the difficulty level
|
||||||
func (c *Client) SetDifficulty(difficulty string) error {
|
func (c *Client) SetDifficulty(difficulty string) error {
|
||||||
var command string
|
return fmt.Errorf("not implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServerInfo returns basic server information
|
// GetServerInfo returns basic server information
|
||||||
@@ -118,14 +74,6 @@ func (c *Client) GetServerInfo() (string, error) {
|
|||||||
return c.Execute("version")
|
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
|
// ConnectWithTimeout attempts to connect with a timeout
|
||||||
func ConnectWithTimeout(address, password string, timeout time.Duration) (*Client, error) {
|
func ConnectWithTimeout(address, password string, timeout time.Duration) (*Client, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
|||||||
@@ -2,13 +2,34 @@ package rcon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
// This is a placeholder test since we can't actually connect to a server
|
serv := MockRCONServer{}
|
||||||
// in a test environment without a real Minecraft server
|
done, err := serv.Start()
|
||||||
// The actual functionality will be tested with integration tests
|
if err != nil {
|
||||||
t.Log("RCON package tests - placeholder for actual tests")
|
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) {
|
func TestGetEnvCredentials(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user