//go:build integration package builder import ( "archive/tar" "bytes" "context" "os/exec" "path/filepath" "sort" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" ) func TestAnArchivePinnedByURLIsFetchedCheckedAndPackedWithoutItsOwnDirectory(t *testing.T) { body := release(t) v := &vendor{files: map[string][]byte{"/release.tar.gz": body}} base := serveVendor(t, v) r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil) got, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } digest, _ := got.Manifest.Resources[0]["digest"].(string) if !strings.HasPrefix(digest, "sha256:") || r.archives["betterbird/archive"] != digest { t.Fatalf("the release was not published and pinned as the module's archive: %v, published %v", got.Manifest.Resources[0], r.archives) } packed, err := upstreamArchive(context.Background(), workspace, catalogue.Artifact{Name: "archive", Kind: "archive", URL: base + "/release.tar.gz", SHA256: sumHex(body), Version: "153.4.0esr-bb10", Within: "betterbird"}, func(string, string, ...any) {}) if err != nil { t.Fatal(err) } if "sha256:"+sumHex(packed) != digest { t.Fatalf("a second packing of one pin gave another digest: %s and %s", digest, sumHex(packed)) } entries := entriesOf(t, packed) want := map[string]string{ "application.ini": "[App]\nVersion=153.4.0\n -rw-r--r--", "betterbird": "#!/bin/sh\n -rwxr-xr-x", "defaults/pref/channel-prefs.js": "pref(\"app.update.channel\", \"default\");\n -rw-r--r--", } if len(entries) != len(want) { t.Fatalf("the archive holds %v, want %v", entries, want) } for name, w := range want { if entries[name] != w { t.Errorf("%s is %q in the archive, want %q", name, entries[name], w) } } } func TestAnUpstreamFileIsFetchedOnceAndKeptByItsDigest(t *testing.T) { // The vendor of an add-on serves only its newest version at one address: a pin that was fetched once // must still build after the address moved on. body := release(t) v := &vendor{files: map[string][]byte{"/release.tar.gz": body}} base := serveVendor(t, v) r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil) first, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } v.files["/release.tar.gz"] = []byte("the vendor's next release") second, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatalf("a pin fetched before failed to build once the address served something else: %v", err) } if v.asked != 1 { t.Fatalf("the vendor was asked %d times for one pin, want once", v.asked) } if first.Manifest.Resources[0]["digest"] != second.Manifest.Resources[0]["digest"] { t.Fatal("two builds of one pin gave two digests") } } func TestAnUpstreamFileWhoseDownloadDoesNotMatchItsPinFailsAndNamesBothDigests(t *testing.T) { pinned := release(t) served := gzipped(t, tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "not what was reviewed", mode: 0o755}})) v := &vendor{files: map[string][]byte{"/release.tar.gz": served}} base := serveVendor(t, v) r, workspace := aRepository(t, pinnedManifest(base, sumHex(pinned)), nil) _, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil) if err == nil { t.Fatal("a download that is not the pinned file was built") } for _, digest := range []string{sumHex(pinned), sumHex(served)} { if !strings.Contains(err.Error(), "sha256:"+digest) { t.Errorf("the refusal does not name sha256:%s: %v", digest, err) } } if len(r.archives) != 0 { t.Fatalf("something was published: %v", r.archives) } kept, _ := filepath.Glob(filepath.Join(upstreamDir(workspace), "*")) if len(kept) != 0 { t.Fatalf("the refused download was kept: %v", kept) } } func TestAnUpstreamArchiveCompressedWithXzIsRead(t *testing.T) { // Betterbird publishes its Linux release as .tar.xz; the build seat's image carries xz for it. if _, err := exec.LookPath("xz"); err != nil { t.Skip("NOT TESTED here: no xz on this machine (the build seat's image carries it)") } plain := tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "#!/bin/sh\n", mode: 0o755}}) cmd := exec.Command("xz", "-zc") cmd.Stdin = bytes.NewReader(plain) compressed, err := cmd.Output() if err != nil { t.Fatal(err) } v := &vendor{files: map[string][]byte{"/r.tar.xz": compressed}} base := serveVendor(t, v) packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "archive", Kind: "archive", URL: base + "/r.tar.xz", SHA256: sumHex(compressed), Version: "1", Within: "betterbird"}, func(string, string, ...any) {}) if err != nil { t.Fatal(err) } var names []string for n := range entriesOf(t, packed) { names = append(names, n) } sort.Strings(names) if strings.Join(names, ",") != "betterbird" { t.Fatalf("the xz release unpacked to %v", names) } } func TestAnUpstreamTarWithAGlobalHeaderNestedWithinAndTrailingDataIsRead(t *testing.T) { // git archive writes a pax global header; a release may unpack under a/b with a/ listed first; xz may be // followed by bytes after the tar's end, which must be read before xz is waited for. var raw bytes.Buffer w := tar.NewWriter(&raw) w.WriteHeader(&tar.Header{Typeflag: tar.TypeXGlobalHeader, Name: "pax_global_header", PAXRecords: map[string]string{"comment": "abc"}}) w.WriteHeader(&tar.Header{Name: "a/", Typeflag: tar.TypeDir, Mode: 0o755}) w.WriteHeader(&tar.Header{Name: "a/b/", Typeflag: tar.TypeDir, Mode: 0o755}) w.WriteHeader(&tar.Header{Name: "a/b/f", Typeflag: tar.TypeReg, Mode: 0o644, Size: 1}) w.Write([]byte("x")) w.Close() body := gzipped(t, raw.Bytes()) v := &vendor{files: map[string][]byte{"/r.tar.gz": body}} base := serveVendor(t, v) packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive", URL: base + "/r.tar.gz", SHA256: sumHex(body), Version: "1", Within: "a/b"}, func(string, string, ...any) {}) if err != nil { t.Fatal(err) } if e := entriesOf(t, packed); len(e) != 1 || e["f"] == "" { t.Fatalf("%v", e) } if _, err := exec.LookPath("xz"); err != nil { t.Skip("NOT TESTED here: no xz") } // A megabyte after the tar's end: more than a pipe holds. trailing := append(append([]byte{}, raw.Bytes()...), bytes.Repeat([]byte{0}, 1<<20)...) cmd := exec.Command("xz", "-zc") cmd.Stdin = bytes.NewReader(trailing) compressed, err := cmd.Output() if err != nil { t.Fatal(err) } v.files["/r.tar.xz"] = compressed ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if _, err := upstreamArchive(ctx, t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive", URL: base + "/r.tar.xz", SHA256: sumHex(compressed), Version: "1", Within: "a/b"}, func(string, string, ...any) {}); err != nil { t.Fatal(err) } }