Files
mesh-catalog/modules/mesh-build-node/cmd/mesh-build-node/toolchain_test.go
T

176 lines
6.3 KiB
Go

package main
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
type npmCall struct {
name string
args []string
env []string
}
type fakeNpm struct {
mu sync.Mutex
calls []npmCall
ran forgekey.Ran
}
func (f *fakeNpm) run(_ context.Context, env []string, name string, args ...string) forgekey.Ran {
f.mu.Lock()
defer f.mu.Unlock()
f.calls = append(f.calls, npmCall{name, args, env})
if f.ran.Status == 0 && f.ran.Err == "" {
// npm ci writes node_modules into --prefix.
for i, a := range args {
if a == "--prefix" {
os.MkdirAll(filepath.Join(args[i+1], "node_modules"), 0o700)
}
}
}
return f.ran
}
func toolchainIn(t *testing.T, fake *fakeNpm) Toolchain {
t.Helper()
home, cache := t.TempDir(), t.TempDir()
if err := os.MkdirAll(filepath.Join(home, "toolchain"), 0o700); err != nil {
t.Fatal(err)
}
os.WriteFile(filepath.Join(home, "toolchain", "package.json"), []byte(`{"name":"x"}`), 0o600)
os.WriteFile(filepath.Join(home, "toolchain", "package-lock.json"), []byte(`{"lock":1}`), 0o600)
return Toolchain{Paths: Paths{Home: home, Cache: cache}, Run: fake.run,
Registry: func() (Registry, error) {
return Registry{URL: "https://git.novox.be/api/packages/novox/npm/", Username: "mesh-build-node", Password: "pw"}, nil
}}
}
// 1488/AC4: given a missing toolchain directory, npm ci --ignore-scripts runs once in <cache>/toolchain/<recipe
// version> with the module's lock; asked again with the directory present, nothing runs.
func TestAMissingToolchainDirectoryIsInstalledOnceFromTheLock(t *testing.T) {
fake := &fakeNpm{}
tc := toolchainIn(t, fake)
version := Recipe{Steps: "1", Lock: "x"}.Version()
dir := filepath.Join(tc.Paths.Cache, "toolchain", version)
if tc.Present(version) {
t.Fatal("present before any install")
}
if err := tc.Ensure(context.Background(), version); err != nil {
t.Fatal(err)
}
if len(fake.calls) != 1 {
t.Fatalf("npm ran %d times, want once", len(fake.calls))
}
c := fake.calls[0]
joined := strings.Join(c.args, " ")
if c.name != "npm" || c.args[0] != "ci" || !strings.Contains(joined, "--ignore-scripts") || !strings.Contains(joined, "--no-bin-links") || !strings.Contains(joined, "--prefix "+dir) {
t.Errorf("ran %s %v, want npm ci --ignore-scripts --prefix %s", c.name, c.args, dir)
}
if lock, _ := os.ReadFile(filepath.Join(dir, "package-lock.json")); string(lock) != `{"lock":1}` {
t.Errorf("the directory holds lock %q, want the module's", lock)
}
if _, err := os.Stat(filepath.Join(dir, ".npmrc-install")); err == nil {
t.Errorf("the registry credential file was left in the toolchain directory")
}
if !tc.Present(version) {
t.Errorf("not present after the install")
}
if err := tc.Ensure(context.Background(), version); err != nil || len(fake.calls) != 1 {
t.Errorf("a present directory ran npm again: %d calls, %v", len(fake.calls), err)
}
}
// 1488/AC4: the install is told the mesh's registry for @novox with the account's credential, and only through a
// config file of its own, never on the command line.
func TestTheInstallNamesTheRegistryInAConfigFileNotOnTheCommandLine(t *testing.T) {
var npmrc string
fake := &fakeNpm{}
tc := toolchainIn(t, fake)
tc.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
for i, a := range args {
if a == "--userconfig" {
raw, _ := os.ReadFile(args[i+1])
npmrc = string(raw)
}
}
return fake.run(ctx, env, name, args...)
}
if err := tc.Ensure(context.Background(), "v1"); err != nil {
t.Fatal(err)
}
if !strings.Contains(npmrc, "@novox:registry=https://git.novox.be/api/packages/novox/npm/\n") ||
!strings.Contains(npmrc, "//git.novox.be/api/packages/novox/npm/:_auth=") {
t.Errorf("npmrc = %q", npmrc)
}
if strings.Contains(strings.Join(fake.calls[0].args, " "), "pw") {
t.Errorf("the credential is on the command line: %v", fake.calls[0].args)
}
}
// 1488/AC4: an install that fails leaves no directory taken for a toolchain, says why, and the next call tries again.
func TestAFailedInstallIsNotAToolchain(t *testing.T) {
fake := &fakeNpm{ran: forgekey.Ran{Status: 1, Stderr: "E401 unauthorized"}}
tc := toolchainIn(t, fake)
err := tc.Ensure(context.Background(), "v1")
if err == nil || !strings.Contains(err.Error(), "E401") {
t.Fatalf("err = %v, want the npm failure named", err)
}
if tc.Present("v1") {
t.Errorf("a failed install is present")
}
fake.ran = forgekey.Ran{}
if err := tc.Ensure(context.Background(), "v1"); err != nil || len(fake.calls) != 2 || !tc.Present("v1") {
t.Errorf("the retry: %v, %d calls", err, len(fake.calls))
}
}
// 1488/AC4: a module's steps run the install behind the guard: a refusing node installs nothing, an accepting one
// installs once however many steps follow.
func TestPrepareInstallsOnlyBehindAPassingGuard(t *testing.T) {
fake := &fakeNpm{}
tc := toolchainIn(t, fake)
states := newStates()
m := moduleWith(states, "")
m.Probe = func(context.Context) (string, error) { return "", errors.New("no node") }
m.Toolchain = &tc
if err := m.Prepare(context.Background()); err == nil || len(fake.calls) != 0 {
t.Fatalf("a refusing node: %v, %d npm calls", err, len(fake.calls))
}
m = moduleWith(states, "v26.10.0")
m.Toolchain = &tc
for i := 0; i < 3; i++ {
if err := m.Prepare(context.Background()); err != nil {
t.Fatal(err)
}
}
if len(fake.calls) != 1 {
t.Errorf("npm ran %d times for three steps, want once", len(fake.calls))
}
}
func TestRegistryFromTheBinding(t *testing.T) {
dir := t.TempDir()
b, s := filepath.Join(dir, "b.json"), filepath.Join(dir, "s")
os.WriteFile(s, []byte("secret\n"), 0o600)
os.WriteFile(b, []byte(`{"provision":"npm-package-registry","at":"10.42.0.1","as":"mesh-build-node-ace","serves":{"scheme":"http","port":3000,"npm-path":"/api/packages/novox/npm/"}}`), 0o600)
r, err := ReadRegistry(b, s)
if err != nil || r.URL != "http://10.42.0.1:3000/api/packages/novox/npm/" || r.Username != "mesh-build-node-ace" || r.Password != "secret" {
t.Fatalf("= %+v, %v", r, err)
}
os.WriteFile(b, []byte(`{"at":"h","as":"a","serves":{"port":1,"npm-path":"/p/","public-root":"https://git.novox.be/p"}}`), 0o600)
if r, _ := ReadRegistry(b, s); r.URL != "https://git.novox.be/p/" {
t.Errorf("the public root is not preferred: %q", r.URL)
}
if _, err := ReadRegistry(filepath.Join(dir, "none"), s); err == nil {
t.Errorf("no binding was accepted")
}
}