package image import ( "archive/zip" "crypto/md5" "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "testing" ) func TestDownloadDecompressesXZFileURLs(t *testing.T) { if _, err := exec.LookPath("xz"); err != nil { t.Skip("xz not available") } dir := t.TempDir() raw := filepath.Join(dir, "base.img") if err := os.WriteFile(raw, []byte("metis-xz-test"), 0o644); err != nil { t.Fatal(err) } compressed := raw + ".xz" cmd := exec.Command("xz", "-zk", raw) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("xz: %v: %s", err, string(out)) } dest := filepath.Join(dir, "copy.img") if err := Download("file://"+compressed, dest); err != nil { t.Fatalf("Download: %v", err) } data, err := os.ReadFile(dest) if err != nil { t.Fatalf("ReadFile: %v", err) } if string(data) != "metis-xz-test" { t.Fatalf("unexpected decompressed content: %q", string(data)) } } func TestDownloadAndVerifyUsesArchiveChecksumForXZ(t *testing.T) { if _, err := exec.LookPath("xz"); err != nil { t.Skip("xz not available") } dir := t.TempDir() raw := filepath.Join(dir, "base.img") if err := os.WriteFile(raw, []byte("metis-xz-test"), 0o644); err != nil { t.Fatal(err) } compressed := raw + ".xz" cmd := exec.Command("xz", "-zk", raw) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("xz: %v: %s", err, string(out)) } archiveBytes, err := os.ReadFile(compressed) if err != nil { t.Fatalf("ReadFile archive: %v", err) } archiveSum := sha256.Sum256(archiveBytes) dest := filepath.Join(dir, "copy.img") localPath, err := DownloadAndVerify("file://"+compressed, dest, "sha256:"+hex.EncodeToString(archiveSum[:])) if err != nil { t.Fatalf("DownloadAndVerify: %v", err) } if localPath != dest { t.Fatalf("expected local path %s, got %s", dest, localPath) } data, err := os.ReadFile(dest) if err != nil { t.Fatalf("ReadFile dest: %v", err) } if string(data) != "metis-xz-test" { t.Fatalf("unexpected decompressed content: %q", string(data)) } } func TestDownloadAndVerifyReplacesStaleBadArchiveCache(t *testing.T) { if _, err := exec.LookPath("xz"); err != nil { t.Skip("xz not available") } dir := t.TempDir() raw := filepath.Join(dir, "base.img") if err := os.WriteFile(raw, []byte("metis-xz-test"), 0o644); err != nil { t.Fatal(err) } compressed := raw + ".xz" cmd := exec.Command("xz", "-zk", raw) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("xz: %v: %s", err, string(out)) } archiveBytes, err := os.ReadFile(compressed) if err != nil { t.Fatalf("ReadFile archive: %v", err) } archiveSum := sha256.Sum256(archiveBytes) dest := filepath.Join(dir, "copy.img") staleArchive := dest + ".xz" if err := os.WriteFile(staleArchive, []byte("bad-cache"), 0o644); err != nil { t.Fatalf("WriteFile stale archive: %v", err) } if _, err := DownloadAndVerify("file://"+compressed, dest, "sha256:"+hex.EncodeToString(archiveSum[:])); err != nil { t.Fatalf("DownloadAndVerify with stale archive: %v", err) } data, err := os.ReadFile(dest) if err != nil { t.Fatalf("ReadFile dest: %v", err) } if string(data) != "metis-xz-test" { t.Fatalf("unexpected decompressed content: %q", string(data)) } } func TestDownloadDecompressesZIPFileURLs(t *testing.T) { dir := t.TempDir() archive := filepath.Join(dir, "base.zip") if err := writeTestZIP(archive, map[string]string{ "nested/base.img": "metis-zip-test", }); err != nil { t.Fatalf("writeTestZIP: %v", err) } dest := filepath.Join(dir, "copy.img") if err := Download("file://"+archive, dest); err != nil { t.Fatalf("Download: %v", err) } data, err := os.ReadFile(dest) if err != nil { t.Fatalf("ReadFile: %v", err) } if string(data) != "metis-zip-test" { t.Fatalf("unexpected decompressed content: %q", string(data)) } } func TestDownloadAndVerifyUsesArchiveChecksumForZIP(t *testing.T) { dir := t.TempDir() archive := filepath.Join(dir, "base.zip") if err := writeTestZIP(archive, map[string]string{ "base.img": "metis-zip-test", }); err != nil { t.Fatalf("writeTestZIP: %v", err) } archiveBytes, err := os.ReadFile(archive) if err != nil { t.Fatalf("ReadFile archive: %v", err) } archiveSum := sha256.Sum256(archiveBytes) dest := filepath.Join(dir, "copy.img") localPath, err := DownloadAndVerify("file://"+archive, dest, "sha256:"+hex.EncodeToString(archiveSum[:])) if err != nil { t.Fatalf("DownloadAndVerify: %v", err) } if localPath != dest { t.Fatalf("expected local path %s, got %s", dest, localPath) } data, err := os.ReadFile(dest) if err != nil { t.Fatalf("ReadFile dest: %v", err) } if string(data) != "metis-zip-test" { t.Fatalf("unexpected decompressed content: %q", string(data)) } } func TestVerifyChecksumAcceptsMD5(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "sample.img") if err := os.WriteFile(path, []byte("metis-md5-test"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } sum := md5.Sum([]byte("metis-md5-test")) if err := VerifyChecksum(path, "md5:"+hex.EncodeToString(sum[:])); err != nil { t.Fatalf("VerifyChecksum md5: %v", err) } } func TestDownloadAndVerifyUsesHTTPAndCachedFile(t *testing.T) { body := []byte("metis-http-test") sum := sha256.Sum256(body) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/image.img" { http.NotFound(w, r) return } _, _ = w.Write(body) })) defer srv.Close() dir := t.TempDir() dest := filepath.Join(dir, "image.img") path, err := DownloadAndVerify(srv.URL+"/image.img", dest, "sha256:"+hex.EncodeToString(sum[:])) if err != nil { t.Fatalf("DownloadAndVerify: %v", err) } if path != dest { t.Fatalf("path = %q, want %q", path, dest) } if got, _ := os.ReadFile(dest); string(got) != string(body) { t.Fatalf("downloaded body = %q", string(got)) } if err := os.WriteFile(dest, body, 0o644); err != nil { t.Fatal(err) } if _, err := DownloadAndVerify(srv.URL+"/image.img", dest, "sha256:"+hex.EncodeToString(sum[:])); err != nil { t.Fatalf("cached DownloadAndVerify: %v", err) } } func TestDownloadAndVerifyRejectsBadChecksum(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "src.img") if err := os.WriteFile(src, []byte("bad"), 0o644); err != nil { t.Fatal(err) } if _, err := DownloadAndVerify("file://"+src, filepath.Join(dir, "dest.img"), "sha256:deadbeef"); err == nil { t.Fatal("expected checksum mismatch") } } func TestDownloadAndVerifyRawAndErrorBranches(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "src.img") if err := os.WriteFile(src, []byte("raw"), 0o644); err != nil { t.Fatal(err) } dest := filepath.Join(dir, "dest.img") if _, err := DownloadAndVerify("file://"+src, dest, ""); err != nil { t.Fatalf("DownloadAndVerify raw: %v", err) } if err := VerifyChecksum(dest, "bogus"); err == nil { t.Fatal("expected invalid checksum format error") } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "boom", http.StatusInternalServerError) })) defer srv.Close() if err := downloadRaw(srv.URL, filepath.Join(dir, "bad.img")); err == nil { t.Fatal("expected HTTP error from downloadRaw") } archive := filepath.Join(dir, "empty.zip") if err := writeTestZIP(archive, map[string]string{}); err != nil { t.Fatalf("writeTestZIP: %v", err) } if err := decompressZIP(archive, filepath.Join(dir, "out.img")); err == nil { t.Fatal("expected empty zip error") } } func writeTestZIP(path string, files map[string]string) error { out, err := os.Create(path) if err != nil { return err } defer out.Close() zw := zip.NewWriter(out) for name, content := range files { w, err := zw.Create(name) if err != nil { _ = zw.Close() return err } if _, err := w.Write([]byte(content)); err != nil { _ = zw.Close() return err } } if err := zw.Close(); err != nil { return err } return out.Sync() }