Files
2026-06-11 11:29:29 +02:00

176 lines
4.2 KiB
Go

package vm
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"forgejo.digital-droplets.de/philschlo/proxui/platform/cluster"
"forgejo.digital-droplets.de/philschlo/proxui/platform/jobs"
)
func TestStartVMCallsProxmoxEnqueuesPollAndWritesAudit(t *testing.T) {
repository := &stubRepository{
getFound: true,
vm: VM{
ID: "vm-1",
TenantID: "tenant-1",
ClusterID: "cluster-1",
ProxmoxVMID: 100,
Node: "pve",
MembershipRole: "member",
},
}
clusters := &stubClusterRepository{
found: true,
cluster: cluster.Cluster{
ID: "cluster-1",
},
}
powerClient := &stubPowerClient{upid: "UPID:pve:1"}
tasks := &stubTaskEnqueuer{}
audit := &stubPowerAudit{}
handler := NewHandler(repository, WithPower(PowerDependencies{
Clusters: clusters,
ClientFactory: func(cluster.Cluster) (PowerClient, error) {
return powerClient, nil
},
Tasks: tasks,
Audit: audit,
}))
req := requestWithPrincipal(http.MethodPost, "/vms/vm-1/start")
req.SetPathValue("vmID", "vm-1")
rec := httptest.NewRecorder()
handler.StartVM(rec, req)
if rec.Code != http.StatusAccepted {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusAccepted)
}
if powerClient.action != "start" {
t.Fatalf("action = %q, want start", powerClient.action)
}
if powerClient.vmid != 100 {
t.Fatalf("vmid = %d, want 100", powerClient.vmid)
}
if tasks.payload.UPID != "UPID:pve:1" {
t.Fatalf("UPID = %q, want UPID:pve:1", tasks.payload.UPID)
}
if tasks.payload.SuccessStatus != "running" {
t.Fatalf("SuccessStatus = %q, want running", tasks.payload.SuccessStatus)
}
if audit.event.Action != "vm.power.start" {
t.Fatalf("audit action = %q, want vm.power.start", audit.event.Action)
}
}
func TestPowerVMRejectsViewer(t *testing.T) {
handler := NewHandler(&stubRepository{
getFound: true,
vm: VM{
ID: "vm-1",
MembershipRole: "viewer",
},
}, WithPower(validPowerDependencies()))
req := requestWithPrincipal(http.MethodPost, "/vms/vm-1/start")
req.SetPathValue("vmID", "vm-1")
rec := httptest.NewRecorder()
handler.StartVM(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
}
func TestPowerVMReturnsErrorWhenEnqueueFails(t *testing.T) {
deps := validPowerDependencies()
deps.Tasks = &stubTaskEnqueuer{err: errors.New("redis failed")}
handler := NewHandler(&stubRepository{
getFound: true,
vm: VM{
ID: "vm-1",
TenantID: "tenant-1",
ClusterID: "cluster-1",
ProxmoxVMID: 100,
Node: "pve",
MembershipRole: "member",
},
}, WithPower(deps))
req := requestWithPrincipal(http.MethodPost, "/vms/vm-1/start")
req.SetPathValue("vmID", "vm-1")
rec := httptest.NewRecorder()
handler.StartVM(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
}
func validPowerDependencies() PowerDependencies {
return PowerDependencies{
Clusters: &stubClusterRepository{
found: true,
cluster: cluster.Cluster{ID: "cluster-1"},
},
ClientFactory: func(cluster.Cluster) (PowerClient, error) {
return &stubPowerClient{upid: "UPID:pve:1"}, nil
},
Tasks: &stubTaskEnqueuer{},
Audit: &stubPowerAudit{},
}
}
type stubClusterRepository struct {
id string
cluster cluster.Cluster
found bool
err error
}
func (s *stubClusterRepository) GetCluster(_ context.Context, id string) (cluster.Cluster, bool, error) {
s.id = id
return s.cluster, s.found, s.err
}
type stubPowerClient struct {
node string
vmid int
action string
upid string
err error
}
func (s *stubPowerClient) PowerVM(_ context.Context, node string, vmid int, action string) (string, error) {
s.node = node
s.vmid = vmid
s.action = action
return s.upid, s.err
}
type stubTaskEnqueuer struct {
payload jobs.ProxmoxTaskPollPayload
err error
}
func (s *stubTaskEnqueuer) EnqueueProxmoxTaskPoll(_ context.Context, payload jobs.ProxmoxTaskPollPayload) error {
s.payload = payload
return s.err
}
type stubPowerAudit struct {
event VMPowerAuditEvent
err error
}
func (s *stubPowerAudit) WriteVMPowerAudit(_ context.Context, event VMPowerAuditEvent) error {
s.event = event
return s.err
}