package daemon import ( "context" "os" "path/filepath" "testing" "time" "banger/internal/model" "banger/internal/store" ) func TestEnsureDefaultImageUsesConfiguredDefaultRootfs(t *testing.T) { dir := t.TempDir() rootfs, kernel, _, _, _ := writeDefaultImageArtifacts(t, dir) db := openDefaultImageStore(t, dir) d := &Daemon{ config: model.DaemonConfig{ DefaultImageName: "default", DefaultRootfs: rootfs, DefaultKernel: kernel, }, store: db, } if err := d.ensureDefaultImage(context.Background()); err != nil { t.Fatalf("ensureDefaultImage: %v", err) } image, err := db.GetImageByName(context.Background(), "default") if err != nil { t.Fatalf("GetImageByName: %v", err) } if image.RootfsPath != rootfs { t.Fatalf("RootfsPath = %q, want %q", image.RootfsPath, rootfs) } if image.KernelPath != kernel { t.Fatalf("KernelPath = %q, want %q", image.KernelPath, kernel) } if image.Managed { t.Fatal("default image should be unmanaged") } } func TestEnsureDefaultImageLeavesCurrentUnmanagedDefaultUntouched(t *testing.T) { dir := t.TempDir() rootfs, kernel, initrd, modulesDir, packages := writeDefaultImageArtifacts(t, dir) db := openDefaultImageStore(t, dir) now := time.Date(2026, time.March, 16, 12, 0, 0, 0, time.UTC) image := model.Image{ ID: "default-id", Name: "default", Managed: false, RootfsPath: rootfs, KernelPath: kernel, InitrdPath: initrd, ModulesDir: modulesDir, PackagesPath: packages, Docker: true, CreatedAt: now, UpdatedAt: now, } if err := db.UpsertImage(context.Background(), image); err != nil { t.Fatalf("UpsertImage: %v", err) } d := &Daemon{ config: model.DaemonConfig{ DefaultImageName: "default", DefaultRootfs: rootfs, DefaultKernel: kernel, DefaultInitrd: initrd, DefaultModulesDir: modulesDir, DefaultPackagesFile: packages, }, store: db, } if err := d.ensureDefaultImage(context.Background()); err != nil { t.Fatalf("ensureDefaultImage: %v", err) } got, err := db.GetImageByName(context.Background(), "default") if err != nil { t.Fatalf("GetImageByName: %v", err) } if got.ID != image.ID { t.Fatalf("ID = %q, want %q", got.ID, image.ID) } if !got.UpdatedAt.Equal(image.UpdatedAt) { t.Fatalf("UpdatedAt = %s, want unchanged %s", got.UpdatedAt, image.UpdatedAt) } } func TestEnsureDefaultImageReconcilesStaleUnmanagedDefaultInPlace(t *testing.T) { dir := t.TempDir() rootfs, kernel, initrd, modulesDir, packages := writeDefaultImageArtifacts(t, dir) db := openDefaultImageStore(t, dir) now := time.Date(2026, time.March, 16, 12, 0, 0, 0, time.UTC) stale := model.Image{ ID: "default-id", Name: "default", Managed: false, RootfsPath: "/home/thales/projects/personal/banger/rootfs-docker.ext4", KernelPath: "/home/thales/projects/personal/banger/wtf/root/boot/vmlinux-6.8.0-94-generic", InitrdPath: "/home/thales/projects/personal/banger/wtf/root/boot/initrd.img-6.8.0-94-generic", ModulesDir: "/home/thales/projects/personal/banger/wtf/root/lib/modules/6.8.0-94-generic", PackagesPath: "/home/thales/projects/personal/banger/packages.apt", Docker: true, CreatedAt: now, UpdatedAt: now, } if err := db.UpsertImage(context.Background(), stale); err != nil { t.Fatalf("UpsertImage: %v", err) } vm := testVM("uses-default", stale.ID, "172.16.0.25") if err := db.UpsertVM(context.Background(), vm); err != nil { t.Fatalf("UpsertVM: %v", err) } d := &Daemon{ config: model.DaemonConfig{ DefaultImageName: "default", DefaultRootfs: rootfs, DefaultKernel: kernel, DefaultInitrd: initrd, DefaultModulesDir: modulesDir, DefaultPackagesFile: packages, }, store: db, } if err := d.ensureDefaultImage(context.Background()); err != nil { t.Fatalf("ensureDefaultImage: %v", err) } got, err := db.GetImageByName(context.Background(), "default") if err != nil { t.Fatalf("GetImageByName: %v", err) } if got.ID != stale.ID { t.Fatalf("ID = %q, want preserved %q", got.ID, stale.ID) } if !got.CreatedAt.Equal(stale.CreatedAt) { t.Fatalf("CreatedAt = %s, want preserved %s", got.CreatedAt, stale.CreatedAt) } if got.RootfsPath != rootfs || got.KernelPath != kernel || got.InitrdPath != initrd || got.ModulesDir != modulesDir || got.PackagesPath != packages { t.Fatalf("stale default not reconciled: %+v", got) } if !got.UpdatedAt.After(stale.UpdatedAt) { t.Fatalf("UpdatedAt = %s, want newer than %s", got.UpdatedAt, stale.UpdatedAt) } gotVM, err := db.GetVMByID(context.Background(), vm.ID) if err != nil { t.Fatalf("GetVMByID: %v", err) } if gotVM.ImageID != stale.ID { t.Fatalf("VM image ID = %q, want preserved %q", gotVM.ImageID, stale.ID) } } func TestEnsureDefaultImageLeavesManagedDefaultUntouched(t *testing.T) { dir := t.TempDir() rootfs, kernel, _, _, _ := writeDefaultImageArtifacts(t, dir) db := openDefaultImageStore(t, dir) now := time.Date(2026, time.March, 16, 12, 0, 0, 0, time.UTC) managed := model.Image{ ID: "managed-default", Name: "default", Managed: true, RootfsPath: "/managed/rootfs.ext4", KernelPath: "/managed/vmlinux", CreatedAt: now, UpdatedAt: now, } if err := db.UpsertImage(context.Background(), managed); err != nil { t.Fatalf("UpsertImage: %v", err) } d := &Daemon{ config: model.DaemonConfig{ DefaultImageName: "default", DefaultRootfs: rootfs, DefaultKernel: kernel, }, store: db, } if err := d.ensureDefaultImage(context.Background()); err != nil { t.Fatalf("ensureDefaultImage: %v", err) } got, err := db.GetImageByName(context.Background(), "default") if err != nil { t.Fatalf("GetImageByName: %v", err) } if got.RootfsPath != managed.RootfsPath || got.KernelPath != managed.KernelPath { t.Fatalf("managed default was rewritten: %+v", got) } } func TestEnsureDefaultImageSkipsRewriteWhenCurrentArtifactsMissing(t *testing.T) { dir := t.TempDir() db := openDefaultImageStore(t, dir) now := time.Date(2026, time.March, 16, 12, 0, 0, 0, time.UTC) stale := model.Image{ ID: "default-id", Name: "default", Managed: false, RootfsPath: "/old/rootfs.ext4", KernelPath: "/old/vmlinux", CreatedAt: now, UpdatedAt: now, } if err := db.UpsertImage(context.Background(), stale); err != nil { t.Fatalf("UpsertImage: %v", err) } d := &Daemon{ config: model.DaemonConfig{ DefaultImageName: "default", DefaultRootfs: filepath.Join(dir, "missing-rootfs.ext4"), DefaultKernel: filepath.Join(dir, "missing-vmlinux"), }, store: db, } if err := d.ensureDefaultImage(context.Background()); err != nil { t.Fatalf("ensureDefaultImage: %v", err) } got, err := db.GetImageByName(context.Background(), "default") if err != nil { t.Fatalf("GetImageByName: %v", err) } if got.RootfsPath != stale.RootfsPath || got.KernelPath != stale.KernelPath { t.Fatalf("default image should have stayed stale when no current artifacts exist: %+v", got) } } func openDefaultImageStore(t *testing.T, dir string) *store.Store { t.Helper() db, err := store.Open(filepath.Join(dir, "state.db")) if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { _ = db.Close() }) return db } func writeDefaultImageArtifacts(t *testing.T, dir string) (rootfs, kernel, initrd, modulesDir, packages string) { t.Helper() rootfs = filepath.Join(dir, "rootfs-docker.ext4") kernel = filepath.Join(dir, "vmlinux") initrd = filepath.Join(dir, "initrd.img") modulesDir = filepath.Join(dir, "modules") packages = filepath.Join(dir, "packages.apt") files := []string{ rootfs, kernel, initrd, packages, filepath.Join(modulesDir, "modules.dep"), } for _, path := range files { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte("test"), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } return rootfs, kernel, initrd, modulesDir, packages } func TestSetDNSUsesConfiguredMapDNSDataFile(t *testing.T) { t.Parallel() dataFile := filepath.Join(t.TempDir(), "mapdns", "records.json") runner := &scriptedRunner{ t: t, steps: []runnerStep{ { call: runnerCall{ name: "custom-mapdns", args: []string{"set", "--data-file", dataFile, "devbox.vm", "172.16.0.8"}, }, }, }, } d := &Daemon{ runner: runner, config: model.DaemonConfig{ MapDNSBin: "custom-mapdns", MapDNSDataFile: dataFile, }, } if err := d.setDNS(context.Background(), "devbox", "172.16.0.8"); err != nil { t.Fatalf("setDNS: %v", err) } runner.assertExhausted() } func TestSetDNSUsesMapDNSDefaultsWhenDataFileUnset(t *testing.T) { t.Parallel() runner := &scriptedRunner{ t: t, steps: []runnerStep{ { call: runnerCall{ name: "mapdns", args: []string{"set", "devbox.vm", "172.16.0.8"}, }, }, }, } d := &Daemon{ runner: runner, config: model.DaemonConfig{}, } if err := d.setDNS(context.Background(), "devbox", "172.16.0.8"); err != nil { t.Fatalf("setDNS: %v", err) } runner.assertExhausted() }