Files
egress-proxies-operator/internal/provider/proxmox/state_test.go
T
kacerrandClaude Opus 5.5 6e996ae2c7 proxmox: config block, IDs, marks, state mapping, classify, registration
Phase 2 of the proxmox provider plan. Adds the proxmox: config block with
its validation, registers the type in the config allow-list and the
constructor map, and starts internal/provider/proxmox: the api seam and
its data types, providerIDs (vms/<vmid>/<name>) with the UID-derived VMID,
the description mark, state mapping, and error classification.

Tests read the responses captured from pv1-1 in Phase 0 rather than
repeating Proxmox's wording, and every matcher is run against every
captured error so none fires on the wrong message. The contract methods
are Transient stubs until Phases 5 to 7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 09:42:46 +00:00

115 lines
4.4 KiB
Go

package proxmox
import (
"testing"
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
)
func TestMapState_table(t *testing.T) {
t.Parallel()
tests := []struct {
status, qmp, ip string
want provider.InstanceState
}{
{"running", "running", "192.168.4.147", provider.StateRunning},
{"running", "running", "", provider.StateProvisioning},
{"running", "", "192.168.4.147", provider.StateRunning},
{"running", "", "", provider.StateProvisioning},
{"running", "prelaunch", "", provider.StateProvisioning},
{"running", "paused", "192.168.4.147", provider.StateStopped},
{"running", "suspended", "", provider.StateStopped},
{"running", "io-error", "", provider.StateStopped},
{"running", "something-new", "", provider.StateStopped},
{"stopped", "stopped", "", provider.StateStopped},
{"stopped", "", "", provider.StateStopped},
{"unknown", "", "", provider.StateStopped},
{"", "", "", provider.StateStopped},
}
for _, tc := range tests {
t.Run(tc.status+"/"+tc.qmp+"/"+tc.ip, func(t *testing.T) {
t.Parallel()
if got := mapState(tc.status, tc.qmp, tc.ip); got != tc.want {
t.Errorf("mapState(%q, %q, %q) = %q, want %q", tc.status, tc.qmp, tc.ip, got, tc.want)
}
})
}
}
// TestMapState_fromCaptures feeds status/current as Proxmox reported it for
// a running, a stopped and a paused VM.
func TestMapState_fromCaptures(t *testing.T) {
t.Parallel()
tests := []struct {
capture string
ip string
want provider.InstanceState
}{
{"status_current_running", "192.168.0.150", provider.StateRunning},
{"status_current_running", "", provider.StateProvisioning},
{"status_current_stopped", "", provider.StateStopped},
{"status_current_paused", "192.168.0.150", provider.StateStopped},
}
for _, tc := range tests {
t.Run(tc.capture+"/"+tc.ip, func(t *testing.T) {
t.Parallel()
s := decodeData[vmStatus](t, tc.capture)
if got := mapState(s.Status, s.QMPStatus, tc.ip); got != tc.want {
t.Errorf("mapState(%q, %q, %q) = %q, want %q", s.Status, s.QMPStatus, tc.ip, got, tc.want)
}
})
}
}
func TestFirstIPv4_filters(t *testing.T) {
t.Parallel()
ip := func(addr, typ string) agentIP { return agentIP{Address: addr, Type: typ} }
tests := []struct {
name string
ifaces []agentInterface
want string
}{
{name: "none", want: ""},
{name: "loopback interface only", ifaces: []agentInterface{{Name: "lo", IPAddresses: []agentIP{ip("127.0.0.1", "ipv4")}}}, want: ""},
{name: "ipv6 only", ifaces: []agentInterface{{Name: "ens18", IPAddresses: []agentIP{ip("fe80::1", "ipv6"), ip("2001:db8::5", "ipv6")}}}, want: ""},
{name: "link-local skipped", ifaces: []agentInterface{{Name: "ens18", IPAddresses: []agentIP{ip("169.254.3.4", "ipv4"), ip("192.168.4.20", "ipv4")}}}, want: "192.168.4.20"},
{name: "unspecified skipped", ifaces: []agentInterface{{Name: "ens18", IPAddresses: []agentIP{ip("0.0.0.0", "ipv4")}}}, want: ""},
{name: "loopback address on another interface skipped", ifaces: []agentInterface{{Name: "dummy0", IPAddresses: []agentIP{ip("127.0.0.2", "ipv4")}}}, want: ""},
{name: "type and literal disagree", ifaces: []agentInterface{{Name: "ens18", IPAddresses: []agentIP{ip("fe80::1", "ipv4"), ip("10.0.0.9", "ipv6")}}}, want: ""},
{name: "unparsable skipped", ifaces: []agentInterface{{Name: "ens18", IPAddresses: []agentIP{ip("not-an-ip", "ipv4"), ip("10.0.0.9", "ipv4")}}}, want: "10.0.0.9"},
{name: "agent order decides", ifaces: []agentInterface{
{Name: "ens18", IPAddresses: []agentIP{ip("192.168.4.20", "ipv4")}},
{Name: "ens19", IPAddresses: []agentIP{ip("10.0.0.9", "ipv4")}},
}, want: "192.168.4.20"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := firstIPv4(tc.ifaces); got != tc.want {
t.Errorf("firstIPv4() = %q, want %q", got, tc.want)
}
})
}
}
// TestFirstIPv4_fromCaptures reads the guest agent's answer from the two
// Phase 0 runs, untagged and on VLAN 4.
func TestFirstIPv4_fromCaptures(t *testing.T) {
t.Parallel()
tests := map[string]string{
"agent_network_get_interfaces": "192.168.0.150",
"agent_network_get_interfaces_vlan": "192.168.4.147",
}
for capture, want := range tests {
t.Run(capture, func(t *testing.T) {
t.Parallel()
res := decodeData[struct {
Result []agentInterface `json:"result"`
}](t, capture)
if got := firstIPv4(res.Result); got != want {
t.Errorf("firstIPv4(%s) = %q, want %q", capture, got, want)
}
})
}
}