internal/transport/contract_test.go
Ref: Size: 4.4 KiB History
package transport
import (
"bytes"
"testing"
"github.com/a73x/eitri/internal/pb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/proto"
)
// The wire contract (internal/pb + internal/transport) is the only code shared
// across the control/data-plane boundary (invariant R3). These tests pin its
// observable behavior: every field of the two top-level envelopes must survive
// a WriteMsg/ReadMsg round-trip unchanged. If a field stops being framed, or
// the framing is altered incompatibly, proto.Equal fails here.
func TestAgentMessageReportRoundTrip(t *testing.T) {
in := &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.Report{
Vms: []*pb.VMStatus{
{VmId: "vm-1", PowerState: "on", Phase: "running", Ip: "10.0.0.5", LastError: ""},
{VmId: "vm-2", PowerState: "off", Phase: "stopped"},
{VmId: "vm-3", PowerState: "off", Phase: "creating", StatusDetail: "downloading image 1.2/3.7 GiB"},
},
Exposures: []*pb.ExposureStatus{{
Id: "e-1", State: "active",
Sessions: &pb.ExposureSessions{Active: 7, Refused: 12, Dropped: 3},
}},
Destroyed: []string{"vm-old"},
Quarantined: []*pb.QuarantinedVM{{VmId: "vm-q", Name: "q", VmspecJson: []byte(`{"k":1}`), DestroyAtUnix: 1750000000}},
Capacity: &pb.Capacity{Vcpus: 16, MemMb: 32768, DiskGb: 500},
FenceViolation: true,
LastSeenEpoch: 42,
}}}
out := &pb.AgentMessage{}
roundTrip(t, in, out)
assert.True(t, proto.Equal(in, out), "report did not survive round-trip:\n in=%v\nout=%v", in, out)
}
func TestServerMessageSnapshotRoundTrip(t *testing.T) {
in := &pb.ServerMessage{Msg: &pb.ServerMessage_Snapshot{Snapshot: &pb.Snapshot{
Epoch: 7,
Vms: []*pb.VMSpec{{
VmId: "vm-1", Name: "web", ImageUrl: "https://img/x.qcow2", ImageSha256: "abc",
CloudInit: "#cloud-config", Vcpus: 4, MemMb: 8192, DiskGb: 40,
Persistent: true, PowerState: "on", Tombstoned: false, SshAuthorizedKey: "ssh-ed25519 AAAA",
SshUserCaAuthorizedKeys: []string{"ssh-ed25519 CAAAAA eitri-user-ca"},
}},
}}}
out := &pb.ServerMessage{}
roundTrip(t, in, out)
assert.True(t, proto.Equal(in, out), "snapshot did not survive round-trip:\n in=%v\nout=%v", in, out)
}
// TestAReportFromANewerAgentDecodesOnAnOlderServer pins the rule the field
// numbering exists to serve. A mixed fleet is the normal state during a
// rollout, so a peer that has never heard of a field must skip past it and read
// everything else — not reject the frame, and not misread the field it does
// know. Field 99 is nothing in this schema and stands in for whatever a later
// release adds beside status_detail.
func TestAReportFromANewerAgentDecodesOnAnOlderServer(t *testing.T) {
raw, err := proto.Marshal(&pb.VMStatus{
VmId: "vm-1", PowerState: "off", Phase: "creating",
StatusDetail: "downloading image 1.2/3.7 GiB",
})
require.NoError(t, err)
raw = protowire.AppendTag(raw, 99, protowire.BytesType)
raw = protowire.AppendString(raw, "a field from a release this peer predates")
var got pb.VMStatus
require.NoError(t, proto.Unmarshal(raw, &got), "an unknown field must not fail the frame")
assert.Equal(t, "vm-1", got.GetVmId())
assert.Equal(t, "creating", got.GetPhase())
assert.Equal(t, "downloading image 1.2/3.7 GiB", got.GetStatusDetail())
}
// TestAReportFromAnOlderAgentReadsAsSilence is the same rule from the other
// side, and the one the console's display rules rest on: an agent that predates
// these fields sends neither, and neither may arrive as a value. An absent
// status_detail is empty (nothing to add) and absent counters are NIL — not a
// zeroed struct claiming this port has turned nobody away.
func TestAReportFromAnOlderAgentReadsAsSilence(t *testing.T) {
raw, err := proto.Marshal(&pb.Report{
Vms: []*pb.VMStatus{{VmId: "vm-1", PowerState: "off", Phase: "creating"}},
Exposures: []*pb.ExposureStatus{{Id: "e-1", State: "active"}},
})
require.NoError(t, err)
var got pb.Report
require.NoError(t, proto.Unmarshal(raw, &got))
assert.Empty(t, got.GetVms()[0].GetStatusDetail())
assert.Nil(t, got.GetExposures()[0].GetSessions(), "an uncounted port must not decode as zeros")
}
// roundTrip frames in and reads it back into out.
func roundTrip(t *testing.T, in, out proto.Message) {
t.Helper()
var buf bytes.Buffer
require.NoError(t, WriteMsg(&buf, in))
require.NoError(t, ReadMsg(&buf, out, DefaultMaxFrame))
}