215 lines
5.5 KiB
Go
215 lines
5.5 KiB
Go
package protocol
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEnvelopeValidate_AgentHello(t *testing.T) {
|
|
payload := AgentHelloPayload{
|
|
NodeID: "node-01",
|
|
AgentVersion: "0.1.0",
|
|
ProtocolVersion: CurrentProtocolVersion,
|
|
}
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
env := &Envelope{
|
|
ProtocolVersion: CurrentProtocolVersion,
|
|
MessageID: "msg-001",
|
|
Type: TypeAgentHello,
|
|
Timestamp: time.Now().UTC(),
|
|
Payload: raw,
|
|
}
|
|
|
|
if err := env.Validate(); err != nil {
|
|
t.Fatalf("expected valid envelope, got %v", err)
|
|
}
|
|
if !env.Type.IsAgentToControl() {
|
|
t.Fatal("agent.hello should be agent-to-control")
|
|
}
|
|
}
|
|
|
|
func TestEnvelopeValidate_ServerStart(t *testing.T) {
|
|
payload := ServerStartPayload{
|
|
OperationMeta: OperationMeta{
|
|
ServerID: "srv-01",
|
|
Generation: 4,
|
|
},
|
|
DesiredGeneration: 4,
|
|
}
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
env := &Envelope{
|
|
ProtocolVersion: CurrentProtocolVersion,
|
|
MessageID: "msg-002",
|
|
CorrelationID: "corr-002",
|
|
Type: TypeServerStart,
|
|
Timestamp: time.Now().UTC(),
|
|
Payload: raw,
|
|
}
|
|
|
|
if err := env.Validate(); err != nil {
|
|
t.Fatalf("expected valid envelope, got %v", err)
|
|
}
|
|
if !env.Type.IsControlToAgent() {
|
|
t.Fatal("server.start should be control-to-agent")
|
|
}
|
|
}
|
|
|
|
func TestEnvelopeValidate_RejectsInvalid(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env Envelope
|
|
}{
|
|
{
|
|
name: "missing message id",
|
|
env: Envelope{
|
|
ProtocolVersion: 1,
|
|
Type: TypeAgentHeartbeat,
|
|
Timestamp: time.Now().UTC(),
|
|
Payload: json.RawMessage(`{"nodeId":"n1"}`),
|
|
},
|
|
},
|
|
{
|
|
name: "unknown type",
|
|
env: Envelope{
|
|
ProtocolVersion: 1,
|
|
MessageID: "m1",
|
|
Type: "agent.unknown",
|
|
Timestamp: time.Now().UTC(),
|
|
Payload: json.RawMessage(`{}`),
|
|
},
|
|
},
|
|
{
|
|
name: "non-utc timestamp",
|
|
env: Envelope{
|
|
ProtocolVersion: 1,
|
|
MessageID: "m1",
|
|
Type: TypeAgentHeartbeat,
|
|
Timestamp: time.Now().Local(),
|
|
Payload: json.RawMessage(`{"nodeId":"n1"}`),
|
|
},
|
|
},
|
|
{
|
|
name: "invalid payload json",
|
|
env: Envelope{
|
|
ProtocolVersion: 1,
|
|
MessageID: "m1",
|
|
Type: TypeAgentHeartbeat,
|
|
Timestamp: time.Now().UTC(),
|
|
Payload: json.RawMessage(`{not-json`),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if err := tc.env.Validate(); err == nil {
|
|
t.Fatal("expected validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeEnvelope(t *testing.T) {
|
|
data := []byte(`{
|
|
"protocolVersion": 1,
|
|
"messageId": "01HXYZ",
|
|
"type": "agent.heartbeat",
|
|
"timestamp": "2026-01-01T00:00:00Z",
|
|
"payload": {"nodeId":"node-01","runningServers":0}
|
|
}`)
|
|
|
|
env, err := DecodeEnvelope(data)
|
|
if err != nil {
|
|
t.Fatalf("decode failed: %v", err)
|
|
}
|
|
if env.Type != TypeAgentHeartbeat {
|
|
t.Fatalf("unexpected type %s", env.Type)
|
|
}
|
|
}
|
|
|
|
func TestServerProvisionPayload_JSONRoundtrip(t *testing.T) {
|
|
deadline := time.Date(2026, 6, 26, 12, 0, 0, 0, time.UTC)
|
|
original := ServerProvisionPayload{
|
|
OperationMeta: OperationMeta{
|
|
ServerID: "srv-abc123",
|
|
Generation: 2,
|
|
Deadline: &deadline,
|
|
IdempotencyKey: "idem-001",
|
|
},
|
|
Edition: "JAVA",
|
|
SoftwareFamily: "PAPER",
|
|
MinecraftVersion: "1.21.1",
|
|
HostPort: 25565,
|
|
RamMb: 2048,
|
|
DataPath: "/var/lib/hexahost-gamecloud/srv-abc123",
|
|
EulaAccepted: true,
|
|
}
|
|
|
|
data, err := json.Marshal(original)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded ServerProvisionPayload
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ServerID != original.ServerID {
|
|
t.Fatalf("serverId: got %q want %q", decoded.ServerID, original.ServerID)
|
|
}
|
|
if decoded.Generation != original.Generation {
|
|
t.Fatalf("generation: got %d want %d", decoded.Generation, original.Generation)
|
|
}
|
|
if decoded.Edition != original.Edition {
|
|
t.Fatalf("edition: got %q want %q", decoded.Edition, original.Edition)
|
|
}
|
|
if decoded.SoftwareFamily != original.SoftwareFamily {
|
|
t.Fatalf("softwareFamily: got %q want %q", decoded.SoftwareFamily, original.SoftwareFamily)
|
|
}
|
|
if decoded.MinecraftVersion != original.MinecraftVersion {
|
|
t.Fatalf("minecraftVersion: got %q want %q", decoded.MinecraftVersion, original.MinecraftVersion)
|
|
}
|
|
if decoded.HostPort != original.HostPort {
|
|
t.Fatalf("hostPort: got %d want %d", decoded.HostPort, original.HostPort)
|
|
}
|
|
if decoded.RamMb != original.RamMb {
|
|
t.Fatalf("ramMb: got %d want %d", decoded.RamMb, original.RamMb)
|
|
}
|
|
if decoded.DataPath != original.DataPath {
|
|
t.Fatalf("dataPath: got %q want %q", decoded.DataPath, original.DataPath)
|
|
}
|
|
if decoded.EulaAccepted != original.EulaAccepted {
|
|
t.Fatalf("eulaAccepted: got %v want %v", decoded.EulaAccepted, original.EulaAccepted)
|
|
}
|
|
if decoded.IdempotencyKey != original.IdempotencyKey {
|
|
t.Fatalf("idempotencyKey: got %q want %q", decoded.IdempotencyKey, original.IdempotencyKey)
|
|
}
|
|
if decoded.Deadline == nil || !decoded.Deadline.Equal(deadline) {
|
|
t.Fatalf("deadline mismatch: got %v want %v", decoded.Deadline, deadline)
|
|
}
|
|
}
|
|
|
|
func TestNewEnvelope(t *testing.T) {
|
|
env, err := NewEnvelope(TypeAgentHello, "msg-new", AgentHelloPayload{
|
|
NodeID: "n1",
|
|
AgentVersion: "0.1.0",
|
|
ProtocolVersion: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("new envelope: %v", err)
|
|
}
|
|
if env.ProtocolVersion != CurrentProtocolVersion {
|
|
t.Fatalf("protocol version %d", env.ProtocolVersion)
|
|
}
|
|
}
|