//go:build integration package main import ( "context" "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" "git.novox.be/novox/mesh-sdk/go/forgekey" ) // sdkFixture is a package named as the SDK at version 9.9.9 with no dependency, whose build script writes dist/: what // the group's package member is to its TypeScript members. var sdkFixture = map[string]string{ "module.json": `{"module":"mesh-sdk","version":"1","build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript"}]}}`, "package.json": `{"name":"@novox/mesh-sdk","version":"9.9.9","type":"module","files":["dist"],"scripts":{"build":"node build.mjs"}}`, "package-lock.json": `{"name":"@novox/mesh-sdk","version":"9.9.9","lockfileVersion":3,"requires":true,"packages":{"":{"name":"@novox/mesh-sdk","version":"9.9.9"}}}`, "build.mjs": "import fs from 'node:fs';\nfs.mkdirSync('dist', {recursive: true});\nfs.writeFileSync('dist/index.js', 'export const v = \"9.9.9\";\\n');\n", } // forgeRepo makes a bare repository o/ under root/forge holding files at one commit, and answers the commit. func forgeRepo(t *testing.T, root, name string, files map[string]string) string { t.Helper() bare, work := filepath.Join(root, "forge", "o", name+".git"), filepath.Join(root, "work-"+name) os.MkdirAll(bare, 0o755) os.MkdirAll(work, 0o755) git(t, bare, "init", "-q", "--bare") git(t, work, "init", "-q", "-b", "main") for n, c := range files { write(t, filepath.Join(work, filepath.FromSlash(n)), c, 0o644) } git(t, work, "add", ".") git(t, work, "commit", "-q", "-m", "c") git(t, work, "push", "-q", bare, "HEAD:refs/heads/main") return git(t, work, "rev-parse", "HEAD") } // ADR 0500 §5 with the lead's decision L10, on real git, npm and bubblewrap: the fetch packs the group's package member // (its build script and npm pack shut off, nothing published), and a member whose lock names that version with that // integrity gets it in its node_modules; a member whose lock names it with another integrity fails naming both. func TestTheFetchPacksTheGroupsPackageAndServesItToAMember(t *testing.T) { needBubblewrap(t) for _, tool := range []string{"node", "npm", "git"} { if _, err := exec.LookPath(tool); err != nil { t.Skipf("NOT RUN: no %s where this check runs; every build node has it", tool) } } root := t.TempDir() sdkCommit := forgeRepo(t, root, "sdk", sdkFixture) m, _, _ := newMachine(t) m.Run = forgekey.ExecRunner m.RemoteBase = "file://" + filepath.Join(root, "forge") + "/" m.FetchOptions = []buildstep.Option{buildstep.AllowFileRemote()} fakeRegistry(t, m, nil) // the registry holds no 9.9.9: only the group can serve it // What the group's member packs to, as the member's lock would name it: packed once here the way the fetch packs it. probe, err := m.fetch(context.Background(), map[string]any{"repository": "o/sdk", "commit": sdkCommit, "package": map[string]any{"repository": "o/sdk", "commit": sdkCommit}}) if err != nil { t.Fatal(err) } group := strings.Fields(probe.(FetchResult).Group) if len(group) != 2 || group[0] != "@novox/mesh-sdk@9.9.9" || !strings.HasPrefix(group[1], "sha512-") { t.Fatalf("the fetch answered the group %q", probe.(FetchResult).Group) } member := func(integrity string) string { lock, _ := json.Marshal(map[string]any{"name": "m", "lockfileVersion": 3, "requires": true, "packages": map[string]any{ "": map[string]any{"name": "m", "dependencies": map[string]any{"@novox/mesh-sdk": "9.9.9"}}, "node_modules/@novox/mesh-sdk": map[string]any{"version": "9.9.9", "integrity": integrity}}}) files := typeScriptModule(`{"name":"m","dependencies":{"@novox/mesh-sdk":"9.9.9"}}`) files["modules/m/package-lock.json"] = string(lock) return forgeRepo(t, root, "m-"+integrity[len(integrity)-6:len(integrity)-2], files) } commit := member(group[1]) name := "m-" + group[1][len(group[1])-6:len(group[1])-2] out, err := m.fetch(context.Background(), map[string]any{"repository": "o/" + name, "commit": commit, "package": map[string]any{"repository": "o/sdk", "commit": sdkCommit}}) if err != nil { t.Fatalf("a member locking the group's version: %v", err) } placed := filepath.Join(m.Work, out.(FetchResult).Workspace, "tree", "modules", "m", "node_modules", "@novox", "mesh-sdk") if raw, err := os.ReadFile(filepath.Join(placed, "dist", "index.js")); err != nil || !strings.Contains(string(raw), "9.9.9") { t.Errorf("the member's SDK is not the group's built package: %q %v", raw, err) } other := member("sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") _, err = m.fetch(context.Background(), map[string]any{"repository": "o/m-AAAA", "commit": other, "package": map[string]any{"repository": "o/sdk", "commit": sdkCommit}}) if err == nil || !strings.Contains(err.Error(), group[1]) { t.Errorf("a member locking another integrity answered %v", err) } } // ADR 0499 §4 (issue 1758): a fetch asked with the module's directory and its named paths leaves only those, the // manifest and the lock files; another module of the commit is not listed or installed. func TestAFetchWithNamedPathsNarrowsTheWorkspace(t *testing.T) { root := t.TempDir() files := typeScriptModule(`{"name":"m"}`) files["modules/m/tools/index.ts"] = "export {}\n" files["modules/m/docs/a.md"] = "not named\n" files["modules/other/module.json"] = files["modules/m/module.json"] files["README.md"] = "the repository\n" commit := forgeRepo(t, root, "narrow", files) m, _, _ := newMachine(t) m.Run = forgekey.ExecRunner m.RemoteBase = "file://" + filepath.Join(root, "forge") + "/" m.FetchOptions = []buildstep.Option{buildstep.AllowFileRemote()} out, err := m.fetch(context.Background(), map[string]any{"repository": "o/narrow", "commit": commit, "dir": "modules/m", "paths": []any{"tools"}}) if err != nil { t.Fatal(err) } r := out.(FetchResult) tree := filepath.Join(m.Work, r.Workspace, "tree") for _, gone := range []string{"README.md", "modules/m/docs/a.md", "modules/other/module.json"} { if _, err := os.Stat(filepath.Join(tree, gone)); err == nil { t.Errorf("%s is still in the workspace", gone) } } if _, err := os.Stat(filepath.Join(tree, "modules/m/tools/index.ts")); err != nil || strings.Join(r.Modules, ",") != "modules/m" { t.Errorf("the named path or the module went: %v, modules %v", err, r.Modules) } }