package daemon import ( "context" "errors" "os" "path/filepath" "strings" "testing" "banger/internal/imagecat" "banger/internal/model" "banger/internal/paths" "banger/internal/system" ) func TestFindOrAutoPullImageReturnsLocalWithoutPulling(t *testing.T) { d := &Daemon{ layout: paths.Layout{ImagesDir: t.TempDir()}, store: openDaemonStore(t), runner: system.NewRunner(), } d.img = &ImageService{ layout: d.layout, store: d.store, runner: d.runner, bundleFetch: func(context.Context, string, imagecat.CatEntry) (imagecat.Manifest, error) { t.Fatal("bundleFetch should not be called when image is local") return imagecat.Manifest{}, nil }, } wireServices(d) id, _ := model.NewID() if err := d.store.UpsertImage(context.Background(), model.Image{ ID: id, Name: "my-local-image", CreatedAt: model.Now(), UpdatedAt: model.Now(), }); err != nil { t.Fatal(err) } image, err := d.vm.findOrAutoPullImage(context.Background(), "my-local-image") if err != nil { t.Fatalf("findOrAutoPullImage: %v", err) } if image.Name != "my-local-image" { t.Fatalf("Name = %q, want my-local-image", image.Name) } } func TestFindOrAutoPullImagePullsFromCatalog(t *testing.T) { imagesDir := t.TempDir() kernelsDir := t.TempDir() seedKernel(t, kernelsDir, "generic-6.12") pullCalls := 0 d := &Daemon{ layout: paths.Layout{ImagesDir: imagesDir, KernelsDir: kernelsDir}, store: openDaemonStore(t), runner: system.NewRunner(), } d.img = &ImageService{ layout: d.layout, store: d.store, runner: d.runner, bundleFetch: func(ctx context.Context, destDir string, entry imagecat.CatEntry) (imagecat.Manifest, error) { pullCalls++ return stubBundleFetch(imagecat.Manifest{KernelRef: "generic-6.12"})(ctx, destDir, entry) }, workSeedBuilder: stubWorkSeedBuilder, } wireServices(d) // "debian-bookworm" is in the embedded imagecat catalog. image, err := d.vm.findOrAutoPullImage(context.Background(), "debian-bookworm") if err != nil { t.Fatalf("findOrAutoPullImage: %v", err) } if image.Name != "debian-bookworm" { t.Fatalf("Name = %q, want debian-bookworm", image.Name) } if pullCalls != 1 { t.Fatalf("bundleFetch calls = %d, want 1", pullCalls) } } func TestFindOrAutoPullImageReturnsOriginalErrorWhenNotInCatalog(t *testing.T) { d := &Daemon{ layout: paths.Layout{ImagesDir: t.TempDir()}, store: openDaemonStore(t), runner: system.NewRunner(), } wireServices(d) _, err := d.vm.findOrAutoPullImage(context.Background(), "not-in-catalog-or-store") if err == nil || !strings.Contains(err.Error(), "not found") { t.Fatalf("err = %v, want not-found", err) } } func TestReadOrAutoPullKernelReturnsLocalWithoutPulling(t *testing.T) { kernelsDir := t.TempDir() seedKernel(t, kernelsDir, "generic-6.12") d := &Daemon{layout: paths.Layout{KernelsDir: kernelsDir}} wireServices(d) entry, err := d.img.readOrAutoPullKernel(context.Background(), "generic-6.12") if err != nil { t.Fatalf("readOrAutoPullKernel: %v", err) } if entry.Name != "generic-6.12" { t.Fatalf("Name = %q", entry.Name) } } func TestReadOrAutoPullKernelErrorsWhenNotInCatalog(t *testing.T) { d := &Daemon{layout: paths.Layout{KernelsDir: t.TempDir()}} wireServices(d) _, err := d.img.readOrAutoPullKernel(context.Background(), "nonexistent-kernel") if err == nil || !strings.Contains(err.Error(), "not found") { t.Fatalf("err = %v, want not-found", err) } } // TestReadOrAutoPullKernelSurfacesNonNotExistError covers the path where // kernelcat.ReadLocal fails for a reason other than missing entry (e.g. // corrupt manifest); the autopull logic should NOT try to fetch in that // case since the entry clearly exists in some broken form. func TestReadOrAutoPullKernelSurfacesNonNotExistError(t *testing.T) { kernelsDir := t.TempDir() // Seed a manifest that doesn't match the entry's own Name field — // kernelcat.ReadLocal returns an error, not os.ErrNotExist. dir := filepath.Join(kernelsDir, "broken-kernel") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"name":"different-name"}`), 0o644); err != nil { t.Fatal(err) } d := &Daemon{layout: paths.Layout{KernelsDir: kernelsDir}} wireServices(d) _, err := d.img.readOrAutoPullKernel(context.Background(), "broken-kernel") if err == nil { t.Fatal("want error") } // Must not be wrapped in an "auto-pull" message — the corrupt-manifest // failure should surface as the primary cause. if strings.Contains(err.Error(), "not found in catalog") { t.Fatalf("err = %v, should not claim 'not in catalog'", err) } // Sanity: ensure it's not os.ErrNotExist-compatible. if errors.Is(err, os.ErrNotExist) { t.Fatalf("err = %v, should not be os.ErrNotExist", err) } }