banger/internal/daemon/vm_create_test.go
Thales Maciel eba9a553bf
daemon: use exact-name lookup for VM-create uniqueness
reserveVM's duplicate-name guard routed through Daemon.FindVM, which
falls back to prefix-matching on both ids and names when no exact
match is found. That turns the uniqueness check into a correctness
bug: a brand-new VM name can be rejected because it happens to
prefix an existing VM's id, or an existing VM's name. So `vm create
--name beta` fails when `beta-sandbox` already exists.

Swap in a dedicated store.GetVMByName that does a literal `WHERE
name = ?` lookup, and use it from reserveVM. FindVM keeps its
prefix-matching behaviour for user-facing lookup paths (`vm ssh
<partial>`, `vm stop <partial>`) where "did you mean" semantics
are the feature.

Tests:
 - TestReserveVMAllowsNameThatPrefixesExistingVM — seeds a VM whose
   id + name both start with "longname", then reserves two new VMs
   named "longname" and "longname-sandbox". Both must succeed.
   Under the old FindVM-based check, both would fail.
 - TestReserveVMRejectsExactDuplicateName — actual collisions are
   still rejected after the swap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:00:33 -03:00

86 lines
3 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},
}
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.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.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},
}
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.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)
}
}