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:
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user