banger/internal/daemon/vm_create_test.go
Thales Maciel 16702bd5e1
daemon split (6/n): extract wireServices + drop lazy service getters
Factor the service + capability wiring out of Daemon.Open() into
wireServices(d), an idempotent helper that constructs HostNetwork,
ImageService, WorkspaceService, and VMService from whatever
infrastructure (runner, store, config, layout, logger, closing) is
already set on d. Open() calls it once after filling the composition
root; tests that build &Daemon{...} literals call it to get a working
service graph, preinstalling stubs on the fields they want to fake.

Drops the four lazy-init getters on *Daemon — d.hostNet(),
d.imageSvc(), d.workspaceSvc(), d.vmSvc() — whose sole purpose was
keeping test literals working. Every production call site now reads
d.net / d.img / d.ws / d.vm directly; the services are guaranteed
non-nil once Open returns. No behavior change.

Mechanical: all existing `d.xxxSvc()` calls (production + tests)
rewritten to field access; each `d := &Daemon{...}` in tests gets a
trailing wireServices(d) so the literal + wiring are side-by-side.
Tests that override a pre-built service (e.g. d.img = &ImageService{
bundleFetch: stub}) now set the override before wireServices so the
replacement propagates into VMService's peer pointer.

Also nil-guards HostNetwork.stopVMDNS and d.store in Close() so
partially-initialised daemons (pre-reconcile open failure) still
tear down cleanly — same contract the old lazy getters provided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:55:28 -03:00

88 lines
3.1 KiB
Go

package daemon
import (
"context"
"path/filepath"
"strings"
"testing"
"banger/internal/model"
"banger/internal/paths"
)
// TestReserveVMAllowsNameThatPrefixesExistingVM is a regression for a
// correctness bug in the name-uniqueness check: reserveVM used to
// route through FindVM, which falls back to prefix-matching on both
// ids and names. That meant a perfectly valid new name like "beta"
// could be rejected simply because an existing VM's id or name
// started with "beta". Exact-name lookup via store.GetVMByName fixes
// it. The test seeds a VM whose id and name are long strings, then
// tries to reserve a new VM with a name that's a prefix of each —
// both must succeed.
func TestReserveVMAllowsNameThatPrefixesExistingVM(t *testing.T) {
ctx := context.Background()
tmp := t.TempDir()
d := &Daemon{
store: openDaemonStore(t),
layout: paths.Layout{VMsDir: filepath.Join(tmp, "vms"), RuntimeDir: filepath.Join(tmp, "runtime")},
config: model.DaemonConfig{BridgeIP: model.DefaultBridgeIP},
}
wireServices(d)
existing := testVM("longname-sandbox-foobar", "image-x", "172.16.0.50")
upsertDaemonVM(t, ctx, d.store, existing)
image := testImage("image-x")
image.ID = "image-x"
image.Name = "image-x"
if err := d.store.UpsertImage(ctx, image); err != nil {
t.Fatalf("UpsertImage: %v", err)
}
// New VM name is a prefix of the existing id (which is
// "longname-sandbox-foobar-id" per testVM). Old FindVM-based check
// would reject this.
if vm, err := d.vm.reserveVM(ctx, "longname", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128}); err != nil {
t.Fatalf("reserveVM(prefix of id): %v", err)
} else if vm.Name != "longname" {
t.Fatalf("reserveVM returned name=%q, want longname", vm.Name)
}
// Prefix of the existing name ("longname-sandbox") must also work.
if vm, err := d.vm.reserveVM(ctx, "longname-sandbox", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128}); err != nil {
t.Fatalf("reserveVM(prefix of name): %v", err)
} else if vm.Name != "longname-sandbox" {
t.Fatalf("reserveVM returned name=%q, want longname-sandbox", vm.Name)
}
}
// TestReserveVMRejectsExactDuplicateName confirms the uniqueness
// check still catches actual collisions after the FindVM → GetVMByName
// switch.
func TestReserveVMRejectsExactDuplicateName(t *testing.T) {
ctx := context.Background()
tmp := t.TempDir()
d := &Daemon{
store: openDaemonStore(t),
layout: paths.Layout{VMsDir: filepath.Join(tmp, "vms"), RuntimeDir: filepath.Join(tmp, "runtime")},
config: model.DaemonConfig{BridgeIP: model.DefaultBridgeIP},
}
wireServices(d)
existing := testVM("sandbox", "image-x", "172.16.0.51")
upsertDaemonVM(t, ctx, d.store, existing)
image := testImage("image-x")
image.ID = "image-x"
image.Name = "image-x"
if err := d.store.UpsertImage(ctx, image); err != nil {
t.Fatalf("UpsertImage: %v", err)
}
_, err := d.vm.reserveVM(ctx, "sandbox", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128})
if err == nil {
t.Fatal("reserveVM with duplicate name should have failed")
}
if !strings.Contains(err.Error(), "already exists") {
t.Fatalf("err = %v, want 'already exists'", err)
}
}