Test the Node build module's fetch, forge files, manifest and guard (issues 1489, 1484, 1488), part 19 of 19

This commit is contained in:
2026-10-20 21:05:55 +00:00
parent 1d6c905407
commit bda49fb945
4 changed files with 887 additions and 0 deletions
@@ -0,0 +1,239 @@
package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
const commitSHA = "0123456789abcdef0123456789abcdef01234567"
var fetchArgs = map[string]any{"repository": "novox/mesh-catalog", "commit": commitSHA}
// fetchMachine is a machine whose git checks out the given files into the tree it is asked to fetch into (the fake
// answers the commands of FetchCommit and `git ls-files`), and whose npm ci writes the packages the test names.
func fetchMachine(t *testing.T, files map[string]string, installs map[string]string) (*Module, *fakeMachine) {
t.Helper()
m, f, _ := newMachine(t)
f.answer["git rev-parse"] = forgekey.Ran{Stdout: commitSHA + "\n"}
var manifests []string
for p := range files {
if strings.HasSuffix(p, "module.json") {
manifests = append(manifests, p)
}
}
f.answer["git ls-files"] = forgekey.Ran{Stdout: strings.Join(manifests, "\n") + "\n"}
f.effect = func(c call) {
switch {
case c.name == "git" && slices_contains(c.args, "checkout"):
for i, a := range c.args {
if a == "-C" {
for p, body := range files {
write(t, filepath.Join(c.args[i+1], filepath.FromSlash(p)), body, 0o644)
}
}
}
case c.name == "npm":
for i, a := range c.args {
if a == "--prefix" {
for p, body := range installs {
write(t, filepath.Join(c.args[i+1], "node_modules", filepath.FromSlash(p)), body, 0o644)
}
}
}
}
}
return m, f
}
func slices_contains(l []string, s string) bool {
for _, x := range l {
if x == s {
return true
}
}
return false
}
func typeScriptModule(pkg string) map[string]string {
return map[string]string{
"modules/m/module.json": `{"module":"m","build":{"artifacts":[{"name":"tools","kind":"bundle","language":"typescript","entrypoints":["tools/index.js"]}]}}`,
"modules/m/package.json": pkg,
}
}
func npmCalls(f *fakeMachine) []call {
var out []call
for _, c := range f.calls {
if c.name == "npm" {
out = append(out, c)
}
}
return out
}
// 1489/AC1: a module whose package.json names only @novox/mesh-sdk and @types/node (and typescript) installs nothing
// and the step passes.
func TestAModuleNamingOnlyTheSdkAndTypesInstallsNothing(t *testing.T) {
m, f := fetchMachine(t, typeScriptModule(`{"dependencies":{"@novox/mesh-sdk":"^0.1.6"},"devDependencies":{"@types/node":"^22","typescript":"^5"}}`), nil)
out, err := m.fetch(context.Background(), fetchArgs)
if err != nil {
t.Fatal(err)
}
r := out.(FetchResult)
if len(npmCalls(f)) != 0 || len(r.Installed) != 0 || len(r.Modules) != 1 || r.Modules[0] != "modules/m" || r.Commit != commitSHA {
t.Errorf("result = %+v, npm calls %d, want the module found and nothing installed", r, len(npmCalls(f)))
}
if _, err := os.Stat(filepath.Join(m.Work, r.Workspace, "tree", "modules", "m", "module.json")); err != nil {
t.Errorf("the commit is not checked out in the workspace: %v", err)
}
}
// 1489/AC2: a module naming lodash with a lock gets npm ci --omit=dev --ignore-scripts (fake runner), its install put
// at its node_modules without the SDK and without any copy of it; without a lock the step fails naming the directory.
func TestAModuleNamingLodashWithALockIsInstalledWithoutTheSdkAndWithoutALockFails(t *testing.T) {
files := typeScriptModule(`{"dependencies":{"@novox/mesh-sdk":"^0.1.6","lodash":"^4"},"devDependencies":{"x":"1"},"scripts":{"test":"y"}}`)
files["modules/m/package-lock.json"] = `{"lockfileVersion":3}`
m, f := fetchMachine(t, files, map[string]string{"lodash/index.js": "l", "@novox/mesh-sdk/index.js": "sdk", "a/node_modules/@novox/mesh-sdk/i.js": "nested"})
out, err := m.fetch(context.Background(), fetchArgs)
if err != nil {
t.Fatal(err)
}
r := out.(FetchResult)
calls := npmCalls(f)
if len(calls) != 1 {
t.Fatalf("npm ran %d times, want once", len(calls))
}
line := strings.Join(calls[0].args, " ")
if !strings.HasPrefix(line, "ci --omit=dev --omit=peer --ignore-scripts --no-bin-links") {
t.Errorf("ran npm %s", line)
}
nm := filepath.Join(m.Work, r.Workspace, "tree", "modules", "m", "node_modules")
if _, err := os.Stat(filepath.Join(nm, "lodash", "index.js")); err != nil {
t.Errorf("lodash is not at the module's node_modules: %v", err)
}
for _, gone := range []string{"@novox/mesh-sdk", "a/node_modules/@novox/mesh-sdk"} {
if _, err := os.Stat(filepath.Join(nm, gone)); err == nil {
t.Errorf("a copy of the SDK is left at %s", gone)
}
}
if got := strings.Join(r.Installed, ","); got != "modules/m" {
t.Errorf("installed = %q", got)
}
// the module's own package.json is untouched: the install worked on a scratch copy.
if raw, _ := os.ReadFile(filepath.Join(m.Work, r.Workspace, "tree", "modules", "m", "package.json")); !strings.Contains(string(raw), "scripts") {
t.Errorf("the module's package.json was rewritten: %s", raw)
}
var scratch []string
for _, e := range mustReadDir(t, filepath.Join(m.Work, r.Workspace)) {
scratch = append(scratch, e)
}
if strings.Contains(strings.Join(scratch, ","), "npm-") {
t.Errorf("the scratch install is left in the workspace: %v", scratch)
}
delete(files, "modules/m/package-lock.json")
m, _ = fetchMachine(t, files, nil)
if _, err := m.fetch(context.Background(), fetchArgs); err == nil || !strings.Contains(err.Error(), "modules/m") || !strings.Contains(err.Error(), "package-lock.json") {
t.Errorf("without a lock: %v, want the failure naming modules/m", err)
}
}
func mustReadDir(t *testing.T, dir string) []string {
t.Helper()
es, err := os.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
var out []string
for _, e := range es {
out = append(out, e.Name())
}
return out
}
// 1489/AC3: given a process environment holding a token, no command of the step sees it. The commands the step makes
// are given PATH, HOME and the locale and their own words; and the real clean runner, run on `env` here, prints only
// those.
func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) {
t.Setenv("MESH_BUS_CREDENTIAL", "hunter2")
t.Setenv("NPM_TOKEN", "hunter3")
files := typeScriptModule(`{"dependencies":{"lodash":"^4"}}`)
files["modules/m/package-lock.json"] = `{}`
m, f := fetchMachine(t, files, map[string]string{"lodash/index.js": "l"})
if _, err := m.fetch(context.Background(), fetchArgs); err != nil {
t.Fatal(err)
}
if len(f.calls) < 6 {
t.Fatalf("only %d commands ran", len(f.calls))
}
for _, c := range f.calls {
for _, e := range c.env {
if strings.Contains(e, "hunter") || strings.HasPrefix(e, "MESH_") || strings.HasPrefix(e, "NPM_TOKEN") {
t.Errorf("%s %v was given %s", c.name, c.args[:min(len(c.args), 3)], e)
}
}
if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !strings.HasPrefix(c.env[1], "HOME=") || c.env[2] != "LC_ALL=C" {
t.Errorf("%s was given %v, want PATH, HOME and the locale first", c.name, c.env)
}
}
// The runner main wires: what it hands a command is exactly what the step gave.
ran := forgekey.ExecRunnerClean(10*time.Second)(context.Background(), append(m.baseEnv(), "ONLY=this"), "env")
if ran.Status != 0 || strings.Contains(ran.Stdout, "hunter") || !strings.Contains(ran.Stdout, "ONLY=this") {
t.Errorf("the clean runner printed %q", ran.Stdout)
}
}
func TestFetchRefusesWhatIsNotARepositoryBeforeAnythingRuns(t *testing.T) {
m, f := fetchMachine(t, nil, nil)
for _, repo := range []string{"", "x", "../etc/passwd", "a/b/c", "a/..", "o/r;rm"} {
if _, err := m.fetch(context.Background(), map[string]any{"repository": repo, "commit": commitSHA}); err == nil {
t.Errorf("%q was accepted", repo)
}
}
if len(f.calls) != 0 {
t.Errorf("%d commands ran", len(f.calls))
}
}
func TestFetchAsksGitForTheSshRemote(t *testing.T) {
m, f := fetchMachine(t, typeScriptModule(`{}`), nil)
if _, err := m.fetch(context.Background(), fetchArgs); err != nil {
t.Fatal(err)
}
var all []string
for _, c := range f.calls {
all = append(all, c.name+" "+strings.Join(c.args, " "))
}
lines := strings.Join(all, "\n")
for _, want := range []string{"remote add origin ssh://mesh-forge/novox/mesh-catalog.git", "fetch --no-tags --depth 1 origin " + commitSHA, "checkout -q --detach " + commitSHA} {
if !strings.Contains(lines, want) {
t.Errorf("git was not asked %q:\n%s", want, lines)
}
}
}
func TestWorkspacesOlderThanADayAreRemoved(t *testing.T) {
m, _, work := newMachine(t)
old, fresh := filepath.Join(work, "aaaa"), filepath.Join(work, "bbbb")
for _, d := range []string{old, fresh, filepath.Join(work, "Not-A-Workspace")} {
os.MkdirAll(d, 0o755)
}
os.Chtimes(old, time.Now().Add(-25*time.Hour), time.Now().Add(-25*time.Hour))
os.Chtimes(filepath.Join(work, "Not-A-Workspace"), time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
m.CleanWorkspaces(time.Now())
if _, err := os.Stat(old); err == nil {
t.Errorf("the old workspace is kept")
}
if _, err := os.Stat(fresh); err != nil {
t.Errorf("the fresh workspace was removed")
}
if _, err := os.Stat(filepath.Join(work, "Not-A-Workspace")); err != nil {
t.Errorf("a directory that is no workspace was removed")
}
}