package main import ( "context" "os" "path/filepath" "slices" "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) } } // 1309: the HOME of a run step is its own, inside its /tmp, never the account's. if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !slices.Contains(c.env, "HOME=/tmp/home") || !slices.Contains(c.env, "LC_ALL=C") { t.Errorf("%s env = %v, want PATH, the step's own 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) } }