package main import ( "archive/tar" "bytes" "compress/gzip" "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "sync/atomic" "testing" builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" ) // tgzOf is a packed npm package holding package/package.json of name@version and one more file. func tgzOf(t *testing.T, name, version string) []byte { t.Helper() var buf bytes.Buffer zw := gzip.NewWriter(&buf) tw := tar.NewWriter(zw) for _, f := range [][2]string{{"package/package.json", `{"name":"` + name + `","version":"` + version + `"}`}, {"package/dist/index.js", "export const v = '" + version + "';\n"}} { n, body := f[0], f[1] tw.WriteHeader(&tar.Header{Name: n, Mode: 0o644, Size: int64(len(body)), Typeflag: tar.TypeReg}) tw.Write([]byte(body)) } tw.Close() zw.Close() return buf.Bytes() } // fakeRegistry serves the packument and the packed files of the versions it holds; asked counts its answers. func fakeRegistry(t *testing.T, m *Module, held map[string][]byte) *atomic.Int32 { t.Helper() var asked atomic.Int32 var srv *httptest.Server srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { asked.Add(1) switch { case r.URL.Path == "/npm/@novox%2fmesh-sdk" || r.URL.Path == "/npm/@novox/mesh-sdk": if len(held) == 0 { http.NotFound(w, r) return } versions := map[string]any{} for v, tgz := range held { versions[v] = map[string]any{"dist": map[string]any{"tarball": srv.URL + "/files/" + v + ".tgz", "integrity": integrityOf(tgz)}} } json.NewEncoder(w).Encode(map[string]any{"versions": versions}) case strings.HasPrefix(r.URL.Path, "/files/"): v := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/files/"), ".tgz") if tgz, ok := held[v]; ok { w.Write(tgz) return } http.NotFound(w, r) default: http.NotFound(w, r) } })) t.Cleanup(srv.Close) m.Toolchain.Registry = func() (Registry, error) { return Registry{URL: srv.URL + "/npm/"}, nil } m.HTTP = srv.Client() return &asked } // memberLocking is a member's directory whose lock names @novox/mesh-sdk@version with integrity (none when version is ""). func memberLocking(t *testing.T, version, integrity string) string { t.Helper() dir := t.TempDir() lock := map[string]any{"lockfileVersion": 3, "packages": map[string]any{"": map[string]any{"name": "m"}}} if version != "" { lock["packages"].(map[string]any)["node_modules/@novox/mesh-sdk"] = map[string]any{"version": version, "integrity": integrity} } raw, _ := json.Marshal(lock) write(t, filepath.Join(dir, "package-lock.json"), string(raw), 0o644) return dir } func placedVersion(dir string) string { raw, err := os.ReadFile(filepath.Join(dir, "node_modules", "@novox", "mesh-sdk", "package.json")) if err != nil { return "" } var p packageJSON json.Unmarshal(raw, &p) return p.Version } // The lead's decision L10 (ADR 0500 §6): a member is built against the SDK version its own lock names, downloaded from // the registry and checked against the lock's integrity; a version the registry does not hold is a wait, `waits for:`. func TestAMemberGetsTheSDKVersionItsLockNamesOrWaitsForIt(t *testing.T) { m, _, _ := newMachine(t) held := tgzOf(t, sdkPackage, "0.1.15") fakeRegistry(t, m, map[string][]byte{"0.1.15": held}) dir := memberLocking(t, "0.1.15", integrityOf(held)) if err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil); err != nil || placedVersion(dir) != "0.1.15" { t.Errorf("the registry's 0.1.15: %v, placed %q", err, placedVersion(dir)) } dir = memberLocking(t, "0.1.16", "sha512-x") err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil) if err == nil || err.Error() != builds.WaitsFor+"@novox/mesh-sdk@0.1.16" { t.Errorf("a version the registry lacks answered %v, want %s@novox/mesh-sdk@0.1.16", err, builds.WaitsFor) } dir = memberLocking(t, "0.1.15", "sha512-another") if err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil); err == nil || !strings.Contains(err.Error(), "sha512-another") { t.Errorf("a registry file of another integrity than the lock's answered %v", err) } // ADR 0607 §3, §5 (design 51 *Tagged versions*): given the group's tagged version, the fetch places it from the // registry whatever the lock names, against the lock's integrity when the lock names it, else the registry's; one the // registry lacks fails naming it, no wait (the check waited for its published). tagged := tgzOf(t, sdkPackage, "0.1.16-feat-x.1") fakeRegistry(t, m, map[string][]byte{"0.1.15": held, "0.1.16-feat-x.1": tagged}) group := &groupPackage{Name: sdkPackage, Version: "0.1.16-feat-x.1"} for _, c := range []struct { name, locks, integrity, fails string }{ {name: "a lock naming another version", locks: "0.1.15", integrity: integrityOf(held)}, {name: "a lock naming the tagged version", locks: "0.1.16-feat-x.1", integrity: integrityOf(tagged)}, {name: "a lock naming it with another integrity", locks: "0.1.16-feat-x.1", integrity: "sha512-other", fails: "sha512-other"}, } { dir = memberLocking(t, c.locks, c.integrity) err := m.placeSDK(context.Background(), dir, "modules/m", nil, group) switch { case c.fails == "" && (err != nil || placedVersion(dir) != "0.1.16-feat-x.1"): t.Errorf("%s: %v, placed %q, want the group's tagged version", c.name, err, placedVersion(dir)) case c.fails != "" && (err == nil || !strings.Contains(err.Error(), c.fails)): t.Errorf("%s: %v, want it to fail naming %s", c.name, err, c.fails) } } dir = memberLocking(t, "0.1.15", integrityOf(held)) missing := &groupPackage{Name: sdkPackage, Version: "0.1.16-feat-x.9"} if err := m.placeSDK(context.Background(), dir, "modules/m", nil, missing); err == nil || !strings.Contains(err.Error(), "0.1.16-feat-x.9") || strings.HasPrefix(err.Error(), builds.WaitsFor) { t.Errorf("a tagged version the registry lacks answered %v, want it named and no wait", err) } } // ADR 0500 §5 (S3, S4): the group's packed package is served in place of the registry's when the member's lock names its // version with its integrity; another integrity, or a version neither the group nor the registry holds, fails naming both. // A member whose lock names no SDK keeps the toolchain's and asks the registry nothing. func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) { m, _, _ := newMachine(t) asked := fakeRegistry(t, m, map[string][]byte{"0.1.14": tgzOf(t, sdkPackage, "0.1.14")}) tgz := tgzOf(t, sdkPackage, "0.1.15") group := &packed{Name: sdkPackage, Version: "0.1.15", Integrity: integrityOf(tgz), Digest: digestOf(tgz), tgz: tgz} dir := memberLocking(t, "0.1.15", group.Integrity) if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "0.1.15" || asked.Load() != 0 { t.Errorf("the group's own version: %v, placed %q, the registry asked %d times", err, placedVersion(dir), asked.Load()) } dir = memberLocking(t, "0.1.15", "sha512-other") if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err == nil || !strings.Contains(err.Error(), "sha512-other") || !strings.Contains(err.Error(), group.Integrity) { t.Errorf("another integrity answered %v, want both named", err) } dir = memberLocking(t, "0.1.14", integrityOf(tgzOf(t, sdkPackage, "0.1.14"))) if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "0.1.14" { t.Errorf("a member locking the registry's 0.1.14 in a group: %v, placed %q", err, placedVersion(dir)) } dir = memberLocking(t, "0.1.13", "sha512-x") if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err == nil || !strings.Contains(err.Error(), "0.1.13") || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), builds.WaitsFor) { t.Errorf("a version neither holds answered %v, want both named and no wait", err) } before := asked.Load() dir = memberLocking(t, "", "") if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "" || asked.Load() != before { t.Errorf("a lock naming no SDK: %v, placed %q, the registry asked", err, placedVersion(dir)) } } // The fetch's `package` argument is a repository and a full commit, with a pull request's head at most; anything else is // refused before anything is fetched. func TestTheGroupPackageArgumentIsRefusedWhenItIsNotOne(t *testing.T) { for _, bad := range []any{"novox/mesh-sdk", map[string]any{"repository": "novox/mesh-sdk", "commit": "main"}, map[string]any{"repository": "../x", "commit": strings.Repeat("a", 40)}, map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/heads/main"}, map[string]any{"name": "left-pad", "version": "1.0.0"}, map[string]any{"name": sdkPackage, "version": "latest"}} { if _, err := groupPackageArg(bad); err == nil { t.Errorf("%v was taken", bad) } } g, err := groupPackageArg(map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/pull/9/head"}) if err != nil || g.Ref != "refs/pull/9/head" { t.Errorf("a pull request's head: %+v %v", g, err) } if g, err := groupPackageArg(map[string]any{"name": sdkPackage, "version": "0.1.16-feat-x.1"}); err != nil || !g.tagged() { t.Errorf("the group's tagged version: %+v %v", g, err) } }