Phase3
This commit is contained in:
@@ -137,12 +137,76 @@ type ServerStopPayload struct {
|
||||
OperationMeta
|
||||
}
|
||||
|
||||
// ServerCommandPayload instructs the agent to run a console command via RCON.
|
||||
type ServerCommandPayload struct {
|
||||
OperationMeta
|
||||
Command string `json:"command"`
|
||||
}
|
||||
|
||||
// ServerLogsSubscribePayload instructs the agent to stream container logs.
|
||||
type ServerLogsSubscribePayload struct {
|
||||
OperationMeta
|
||||
TailLines int `json:"tailLines"`
|
||||
Follow bool `json:"follow"`
|
||||
}
|
||||
|
||||
// ServerLogPayload carries a single log line from a game server.
|
||||
type ServerLogPayload struct {
|
||||
OperationMeta
|
||||
Line string `json:"line"`
|
||||
Stream string `json:"stream"`
|
||||
}
|
||||
|
||||
// ServerFilesListPayload lists files in a server data directory.
|
||||
type ServerFilesListPayload struct {
|
||||
OperationMeta
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// ServerFilesReadPayload reads a file from the server data directory.
|
||||
type ServerFilesReadPayload struct {
|
||||
OperationMeta
|
||||
Path string `json:"path"`
|
||||
MaxBytes int `json:"maxBytes"`
|
||||
}
|
||||
|
||||
// ServerFilesWritePayload writes content to a file in the server data directory.
|
||||
type ServerFilesWritePayload struct {
|
||||
OperationMeta
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
Create bool `json:"create"`
|
||||
}
|
||||
|
||||
// FileEntry describes a file or directory in a list response.
|
||||
type FileEntry struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
IsDir bool `json:"isDir"`
|
||||
Size int64 `json:"size"`
|
||||
ModifiedAt string `json:"modifiedAt"`
|
||||
}
|
||||
|
||||
// ServerFilesListResultPayload is the result of a files.list operation.
|
||||
type ServerFilesListResultPayload struct {
|
||||
Entries []FileEntry `json:"entries"`
|
||||
}
|
||||
|
||||
// ServerFilesReadResultPayload is the result of a files.read operation.
|
||||
type ServerFilesReadResultPayload struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
Truncated bool `json:"truncated"`
|
||||
}
|
||||
|
||||
// ServerOperationResultPayload reports the outcome of a lifecycle operation.
|
||||
type ServerOperationResultPayload struct {
|
||||
OperationMeta
|
||||
ResultCode string `json:"resultCode"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
||||
ResultCode string `json:"resultCode"`
|
||||
Operation string `json:"operation,omitempty"`
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
||||
}
|
||||
|
||||
// ServerStatePayload reports the observed server state.
|
||||
|
||||
@@ -212,3 +212,168 @@ func TestNewEnvelope(t *testing.T) {
|
||||
t.Fatalf("protocol version %d", env.ProtocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhase3Payloads_JSONRoundtrip(t *testing.T) {
|
||||
meta := OperationMeta{
|
||||
ServerID: "srv-01",
|
||||
Generation: 3,
|
||||
}
|
||||
|
||||
command := ServerCommandPayload{
|
||||
OperationMeta: meta,
|
||||
Command: "say hello",
|
||||
}
|
||||
commandRaw, err := json.Marshal(command)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decodedCommand ServerCommandPayload
|
||||
if err := json.Unmarshal(commandRaw, &decodedCommand); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decodedCommand.Command != command.Command {
|
||||
t.Fatalf("command: got %q want %q", decodedCommand.Command, command.Command)
|
||||
}
|
||||
|
||||
logsSub := ServerLogsSubscribePayload{
|
||||
OperationMeta: meta,
|
||||
TailLines: 100,
|
||||
Follow: true,
|
||||
}
|
||||
logsSubRaw, err := json.Marshal(logsSub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decodedLogsSub ServerLogsSubscribePayload
|
||||
if err := json.Unmarshal(logsSubRaw, &decodedLogsSub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decodedLogsSub.TailLines != 100 || !decodedLogsSub.Follow {
|
||||
t.Fatalf("logs subscribe mismatch: %+v", decodedLogsSub)
|
||||
}
|
||||
|
||||
filesList := ServerFilesListPayload{
|
||||
OperationMeta: meta,
|
||||
Path: "world",
|
||||
}
|
||||
filesListRaw, err := json.Marshal(filesList)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env := &Envelope{
|
||||
ProtocolVersion: CurrentProtocolVersion,
|
||||
MessageID: "msg-files-list",
|
||||
Type: TypeServerFilesList,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Payload: filesListRaw,
|
||||
}
|
||||
if err := env.Validate(); err != nil {
|
||||
t.Fatalf("files list envelope: %v", err)
|
||||
}
|
||||
|
||||
filesRead := ServerFilesReadPayload{
|
||||
OperationMeta: meta,
|
||||
Path: "server.properties",
|
||||
MaxBytes: 4096,
|
||||
}
|
||||
filesReadRaw, err := json.Marshal(filesRead)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env = &Envelope{
|
||||
ProtocolVersion: CurrentProtocolVersion,
|
||||
MessageID: "msg-files-read",
|
||||
Type: TypeServerFilesRead,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Payload: filesReadRaw,
|
||||
}
|
||||
if err := env.Validate(); err != nil {
|
||||
t.Fatalf("files read envelope: %v", err)
|
||||
}
|
||||
|
||||
filesWrite := ServerFilesWritePayload{
|
||||
OperationMeta: meta,
|
||||
Path: "ops.txt",
|
||||
Content: "test",
|
||||
Create: true,
|
||||
}
|
||||
filesWriteRaw, err := json.Marshal(filesWrite)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env = &Envelope{
|
||||
ProtocolVersion: CurrentProtocolVersion,
|
||||
MessageID: "msg-files-write",
|
||||
Type: TypeServerFilesWrite,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Payload: filesWriteRaw,
|
||||
}
|
||||
if err := env.Validate(); err != nil {
|
||||
t.Fatalf("files write envelope: %v", err)
|
||||
}
|
||||
|
||||
logLine := ServerLogPayload{
|
||||
OperationMeta: meta,
|
||||
Line: "[Server] Done",
|
||||
Stream: "stdout",
|
||||
}
|
||||
logLineRaw, err := json.Marshal(logLine)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env = &Envelope{
|
||||
ProtocolVersion: CurrentProtocolVersion,
|
||||
MessageID: "msg-log",
|
||||
Type: TypeServerLog,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Payload: logLineRaw,
|
||||
}
|
||||
if err := env.Validate(); err != nil {
|
||||
t.Fatalf("server log envelope: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerOperationResultPayload_WithResult(t *testing.T) {
|
||||
listResult, err := json.Marshal(ServerFilesListResultPayload{
|
||||
Entries: []FileEntry{{
|
||||
Name: "server.properties",
|
||||
Path: "server.properties",
|
||||
IsDir: false,
|
||||
Size: 128,
|
||||
ModifiedAt: "2026-06-26T12:00:00Z",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := ServerOperationResultPayload{
|
||||
OperationMeta: OperationMeta{ServerID: "srv-01", Generation: 1},
|
||||
ResultCode: "ok",
|
||||
Operation: "files.list",
|
||||
Result: listResult,
|
||||
}
|
||||
raw, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var decoded ServerOperationResultPayload
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decoded.Operation != "files.list" {
|
||||
t.Fatalf("operation: got %q", decoded.Operation)
|
||||
}
|
||||
if len(decoded.Result) == 0 {
|
||||
t.Fatal("expected result payload")
|
||||
}
|
||||
|
||||
var result ServerFilesListResultPayload
|
||||
if err := json.Unmarshal(decoded.Result, &result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(result.Entries) != 1 || result.Entries[0].Name != "server.properties" {
|
||||
t.Fatalf("unexpected list result: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user