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

200 lines
8.8 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// tscTypeError is what tsc 5.9.3 printed for `export const n: number = "text";` in tools/bad.ts, run on this machine
// on 2026-10-20 with the flags of the type-check below (status 2).
const tscTypeError = "tools/bad.ts(1,14): error TS2322: Type 'string' is not assignable to type 'number'.\n"
func testArgs(level string) map[string]any {
return map[string]any{"workspace": "w1", "module": ".", "level": level}
}
// 1491/AC1: a package with a type error fails the unit level, naming the file.
func TestATypeErrorFailsTheUnitLevelNamingTheFile(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"tools/bad.ts": `export const n: number = "text";`, "package.json": `{"name":"x"}`})
f.answer["tsc"] = forgekey.Ran{Status: 2, Stdout: tscTypeError}
out, err := m.test(context.Background(), testArgs("unit"))
if err != nil {
t.Fatalf("a type error ended the step %v, want it finished with the verdict in its result", err)
}
r := out.(*TestResult)
if r.Passed || len(r.Failed) != 1 || !strings.Contains(r.Why, "tools/bad.ts") || !strings.Contains(r.Failed[0].Output, "TS2322") {
t.Fatalf("result = %+v, want passed false, naming tools/bad.ts", r)
}
if c := f.calls[0]; !strings.Contains(joined(c), "--noEmit") || !strings.Contains(joined(c), "typescript/bin/tsc") ||
!strings.Contains(joined(c), "--moduleResolution NodeNext --target ES2022") || !strings.HasSuffix(joined(c), "tools/bad.ts") {
t.Errorf("the type-check ran %s", joined(c))
}
}
// 1491/AC2: a package with a passing test script passes the unit level, which reports its time; over its bound it
// says so and still passes.
func TestAPassingTestScriptPassesTheUnitLevelAndReportsItsTime(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"index.ts": `export const a = 1;`, "package.json": `{"scripts":{"test":"node --test"}}`})
f.cost["tsc"], f.cost["npm"] = 400*time.Millisecond, 600*time.Millisecond
out, err := m.test(context.Background(), testArgs("unit"))
if err != nil {
t.Fatal(err)
}
r := out.(*TestResult)
if !r.Passed || r.NoTests || r.Elapsed != 1 || r.OverBound || r.Bound != 5 || strings.Join(r.Ran, ",") != "type-check,test" {
t.Errorf("result = %+v, want the type-check and the test script in 1 s within the bound", r)
}
last := f.calls[len(f.calls)-1]
if last.name != "npm" || strings.Join(last.args, " ") != "test" {
t.Errorf("ran %s, want npm test", joined(last))
}
f.cost["npm"] = 6 * time.Second
out, err = m.test(context.Background(), testArgs("unit"))
if r := out.(*TestResult); err != nil || !r.OverBound {
t.Errorf("a slow unit level = %+v, %v, want passed and over_bound", r, err)
}
}
// 1491/AC3: a package with neither script nor .ts files has no tests, and nothing runs.
func TestAPackageWithNeitherScriptNorTypeScriptHasNoTests(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"package.json": `{"name":"x"}`, "node_modules/p/x.ts": "ignored", "types.d.ts": "declare const a: 1;"})
for _, level := range []string{"unit", "integration"} {
out, err := m.test(context.Background(), testArgs(level))
r := out.(*TestResult)
if err != nil || !r.Passed || !r.NoTests || !strings.Contains(r.Why, "no tests") {
t.Errorf("%s = %+v, %v, want the result to say no tests", level, r, err)
}
}
if len(f.calls) != 0 {
t.Errorf("%d commands ran for a package with no tests", len(f.calls))
}
}
// 1491/AC4: with test:integration present the integration level runs it, and reports the setup (the type-check) and
// the running time apart.
func TestTheIntegrationLevelRunsItsScriptAndReportsSetupAndRunningApart(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"index.ts": `export const a = 1;`, "tsconfig.json": `{}`,
"package.json": `{"scripts":{"test":"x","test:integration":"node --test int"}}`})
f.cost["tsc"], f.cost["npm"] = 2*time.Second, 40*time.Second
out, err := m.test(context.Background(), testArgs("integration"))
if err != nil {
t.Fatal(err)
}
r := out.(*TestResult)
if r.Setup != 2 || r.Elapsed != 40 || r.SetupBound != 30 || r.Bound != 60 || !r.Passed || r.OverBound || r.Level != "integration" {
t.Errorf("result = %+v, want setup 2 s and running 40 s apart", r)
}
last := f.calls[len(f.calls)-1]
if strings.Join(last.args, " ") != "run test:integration" {
t.Errorf("ran %s, want npm run test:integration", joined(last))
}
if tsc := f.calls[0]; strings.Join(tsc.args[1:], " ") != "--noEmit -p ." {
t.Errorf("a package with a tsconfig was checked with %s", joined(tsc))
}
f.cost["npm"] = 61 * time.Second
out, _ = m.test(context.Background(), testArgs("integration"))
if !out.(*TestResult).OverBound {
t.Errorf("61 s of running is within the bound")
}
}
// The commands of a step get an environment built from scratch: no word of this process's.
func TestTheStepsCommandsGetAnEnvironmentOfTheirOwn(t *testing.T) {
t.Setenv("MESH_SECRET_TOKEN", "hunter2")
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"index.ts": `export const a = 1;`, "package.json": `{"scripts":{"test":"x"}}`})
if _, err := m.test(context.Background(), testArgs("unit")); err != nil {
t.Fatal(err)
}
for _, c := range f.calls {
for _, e := range c.env {
if strings.Contains(e, "hunter2") || strings.HasPrefix(e, "MESH_") {
t.Errorf("%s was given %s", joined(c), e)
}
}
if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !strings.HasPrefix(c.env[1], "HOME=") {
t.Errorf("%s env = %v, want PATH, HOME and the locale", c.name, c.env)
}
}
}
// 1491: the step finishes with the verdict when the tests fail, and fails only when they could not be run: a failing
// test script is a verdict, a program that cannot start is not.
func TestAFailingScriptIsAVerdictAndACommandThatCannotStartIsAFailedStep(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"package.json": `{"scripts":{"test":"x"}}`})
f.answer["npm"] = forgekey.Ran{Status: 1, Stdout: "not ok 1 - adds\n"}
out, err := m.test(context.Background(), testArgs("unit"))
r, _ := out.(*TestResult)
if err != nil || r == nil || r.Passed || len(r.Failed) != 1 || r.Failed[0].Test != "test" || !strings.Contains(r.Failed[0].Output, "not ok 1") {
t.Errorf("a failing script = %+v, %v, want passed false and its output", r, err)
}
f.answer["npm"] = forgekey.Ran{Status: 127, Err: "ENOENT"}
if out, err := m.test(context.Background(), testArgs("unit")); err == nil {
t.Errorf("npm not installed = %v, want the step to fail", out)
}
if _, err := m.test(context.Background(), map[string]any{"workspace": "nope", "level": "unit"}); err == nil {
t.Errorf("a workspace never fetched did not fail the step")
}
}
func TestAWorkspaceOutsideTheWorkDirectoryIsRefused(t *testing.T) {
m, _, _ := newMachine(t)
for _, a := range []map[string]any{
{"workspace": "../etc", "level": "unit"},
{"workspace": "w1", "module": "../x", "level": "unit"},
{"workspace": "w1", "module": "/etc", "level": "unit"},
{"workspace": "w1", "level": "smoke"},
} {
if _, err := m.test(context.Background(), a); err == nil {
t.Errorf("%v was accepted", a)
}
}
}
// 1491 (review 6): --no-bin-links leaves the toolchain no bin directory, so `npm run build` found no tsc or esbuild
// and exited 127, which read as a failed test. The workspace now has a bin directory of small shell scripts (files,
// not links) that call the toolchain's tsc and esbuild, first on the PATH of the test script.
func TestTheTestScriptFindsTscAndEsbuildOnItsPath(t *testing.T) {
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"package.json": `{"scripts":{"test":"tsc && esbuild"}}`})
if _, err := m.test(context.Background(), testArgs("unit")); err != nil {
t.Fatal(err)
}
bin := filepath.Join(work, "w1", "bin")
nm := filepath.Join(m.toolchainDir(), "node_modules")
for name, want := range map[string]string{
"tsc": "exec node '" + nm + "/typescript/bin/tsc' \"$@\"",
"esbuild": "exec '" + nm + "/esbuild/bin/esbuild' \"$@\"",
} {
info, err := os.Lstat(filepath.Join(bin, name))
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0o111 == 0 {
t.Fatalf("bin/%s: %v %v, want an executable file", name, info, err)
}
raw, _ := os.ReadFile(filepath.Join(bin, name))
if !strings.HasPrefix(string(raw), "#!/bin/sh\n") || !strings.Contains(string(raw), want) {
t.Errorf("bin/%s = %q, want a script calling the toolchain's program", name, raw)
}
}
npm := f.calls[len(f.calls)-1]
path := ""
for _, e := range npm.env {
if strings.HasPrefix(e, "PATH=") {
path = strings.TrimPrefix(e, "PATH=") // the last PATH wins
}
}
if npm.name != "npm" || !strings.HasPrefix(path, bin+":") {
t.Errorf("npm ran with PATH %q, want %s first", path, bin)
}
}