The fetch takes `group-packages` (a member's repository, commit, ref and the package's directory there) in place of the single `package` and its tagged form; it packs each package a module of the tree takes, publishing nothing, and places it in every module whose package.json names it, the SDK in every module, in place of the registry's whatever the lock names. Builds: design 47 G A-284, A-285 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
186 lines
8.9 KiB
Go
186 lines
8.9 KiB
Go
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
|
|
}
|
|
|
|
// ADR 0573 §2 (unpinned, the lead's decision L10's lock version gone): a member is built against the latest release the
|
|
// registry holds, whatever its lock names or when it names none, checked against the lock's integrity only when the lock
|
|
// names that version; a pre-release is never the latest; a registry holding no release is a wait, `waits for:`.
|
|
func TestAMemberGetsTheLatestPublishedSDKWhateverItsLockNames(t *testing.T) {
|
|
m, _, _ := newMachine(t)
|
|
held, older, pre := tgzOf(t, sdkPackage, "0.1.15"), tgzOf(t, sdkPackage, "0.1.9"), tgzOf(t, sdkPackage, "0.1.16-feat-x.1")
|
|
fakeRegistry(t, m, map[string][]byte{"0.1.15": held, "0.1.9": older, "0.1.16-feat-x.1": pre})
|
|
for _, c := range []struct {
|
|
name, locks, integrity, fails string
|
|
}{
|
|
{name: "a lock naming the latest", locks: "0.1.15", integrity: integrityOf(held)},
|
|
{name: "a lock naming an older version (the pin)", locks: "0.1.9", integrity: integrityOf(older)},
|
|
{name: "a lock naming a version the registry lacks", locks: "0.1.16", integrity: "sha512-x"},
|
|
{name: "a lock naming none", locks: ""},
|
|
{name: "a lock naming the latest with another integrity", locks: "0.1.15", integrity: "sha512-another", fails: "sha512-another"},
|
|
} {
|
|
dir := memberLocking(t, c.locks, c.integrity)
|
|
err := m.placeSDK(context.Background(), dir, "modules/m", nil)
|
|
switch {
|
|
case c.fails == "" && (err != nil || placedVersion(dir) != "0.1.15"):
|
|
t.Errorf("%s: %v, placed %q, want the latest release 0.1.15", 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)
|
|
}
|
|
}
|
|
fakeRegistry(t, m, map[string][]byte{"0.1.16-feat-x.1": pre})
|
|
if err := m.placeSDK(context.Background(), memberLocking(t, "0.1.15", ""), "modules/m", nil); err == nil || err.Error() != builds.WaitsFor+"@novox/mesh-sdk" {
|
|
t.Errorf("a registry with no release answered %v, want %s@novox/mesh-sdk", err, builds.WaitsFor)
|
|
}
|
|
}
|
|
|
|
// design 47 G A-284, A-285 (ADR 0500 §5): the group's packed SDK is placed in place of the registry's whatever the
|
|
// member's lock names, its version, its integrity or none, and the registry is not asked.
|
|
func TestTheGroupsSDKIsPlacedInPlaceOfTheRegistrys(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}
|
|
for _, lock := range [][2]string{{"0.1.15", group.Integrity}, {"0.1.15", "sha512-other"}, {"0.1.14", integrityOf(tgzOf(t, sdkPackage, "0.1.14"))}, {"0.1.13", "sha512-x"}, {"", ""}} {
|
|
dir := memberLocking(t, lock[0], lock[1])
|
|
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "0.1.15" {
|
|
t.Errorf("a member locking %q %q: %v, placed %q, want the group's 0.1.15", lock[0], lock[1], err, placedVersion(dir))
|
|
}
|
|
}
|
|
if asked.Load() != 0 {
|
|
t.Errorf("the registry was asked %d times", asked.Load())
|
|
}
|
|
}
|
|
|
|
// design 47 G A-285: each of the group's packages is placed by its package.json name in a module that names it, in
|
|
// place of what the install put there; one it does not name, the module itself and the SDK (placeSDK's) are not.
|
|
func TestTheGroupsPackagesArePlacedInEveryModuleThatNamesThem(t *testing.T) {
|
|
served := map[string]*packed{}
|
|
for _, name := range []string{sdkPackage, "@novox/mesh-delivery-contracts-git", "@novox/mesh-delivery-contracts-mesh-catalog", "@novox/mesh-contracts-x", "@novox/module-m"} {
|
|
served[name] = &packed{Name: name, Version: "0.2.0", tgz: tgzOf(t, name, "0.2.0")}
|
|
}
|
|
dir := t.TempDir()
|
|
write(t, filepath.Join(dir, "package.json"), `{"name":"@novox/module-m","version":"0.1.0","dependencies":{"@novox/mesh-delivery-contracts-git":"^0.1.0","@novox/mesh-sdk":"^0.1.19","@novox/module-m":"0.1.0"},"devDependencies":{"@novox/mesh-delivery-contracts-mesh-catalog":"^0.1.0"}}`, 0o644)
|
|
write(t, filepath.Join(dir, "node_modules", "@novox", "mesh-delivery-contracts-git", "package.json"), `{"name":"@novox/mesh-delivery-contracts-git","version":"0.1.0"}`, 0o644)
|
|
if err := placeGroup(dir, served); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, want := range map[string]string{"@novox/mesh-delivery-contracts-git": "0.2.0", "@novox/mesh-delivery-contracts-mesh-catalog": "0.2.0",
|
|
"@novox/mesh-contracts-x": "", sdkPackage: "", "@novox/module-m": ""} {
|
|
p, _ := readPackageJSON(filepath.Join(dir, "node_modules", filepath.FromSlash(name)))
|
|
if p.Version != want {
|
|
t.Errorf("%s placed at %q, want %q", name, p.Version, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The fetch's `group-packages` is a list of a repository and a full commit, with a pull request's head at most and a
|
|
// directory inside the member; anything else is refused before anything is fetched.
|
|
func TestTheGroupPackagesArgumentIsRefusedWhenItIsNotOne(t *testing.T) {
|
|
full := strings.Repeat("a", 40)
|
|
for _, bad := range []any{"novox/mesh-sdk", map[string]any{"repository": "novox/mesh-sdk", "commit": full},
|
|
[]any{map[string]any{"repository": "novox/mesh-sdk", "commit": "main"}},
|
|
[]any{map[string]any{"repository": "../x", "commit": full}},
|
|
[]any{map[string]any{"repository": "novox/mesh-sdk", "commit": full, "ref": "refs/heads/main"}},
|
|
[]any{map[string]any{"repository": "novox/mesh-delivery", "commit": full, "path": "../x"}},
|
|
[]any{map[string]any{"repository": "novox/mesh-delivery", "commit": full, "path": "/abs"}},
|
|
[]any{map[string]any{"name": sdkPackage, "version": "0.1.16-feat-x.1"}}} {
|
|
if _, err := groupPackagesArg(bad); err == nil {
|
|
t.Errorf("%v was taken", bad)
|
|
}
|
|
}
|
|
gs, err := groupPackagesArg([]any{map[string]any{"repository": "novox/mesh-sdk", "commit": full, "ref": "refs/pull/9/head"},
|
|
map[string]any{"repository": "novox/mesh-delivery", "commit": full, "path": "modules/mesh-delivery-contracts/ts/seats/git/"}})
|
|
if err != nil || len(gs) != 2 || gs[0].Ref != "refs/pull/9/head" || gs[0].Path != "" || gs[1].Path != "modules/mesh-delivery-contracts/ts/seats/git" {
|
|
t.Errorf("a member's root and a member's directory: %+v %v", gs, err)
|
|
}
|
|
}
|