Skip to content

Commit eeed376

Browse files
anandgupta42claude
andauthored
test: add E2E tests for npm install pipeline (#50)
Add test suite exercising the three key install components (postinstall script, bin wrapper, publish output) by building fake npm layouts in temp dirs and running the real scripts as subprocesses. Catches the class of failure where an expired npm token causes optionalDependencies to fail silently, leaving the postinstall script to crash. 24 tests across 4 files, no build step or network access required. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5b92a38 commit eeed376

File tree

5 files changed

+519
-0
lines changed

5 files changed

+519
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { describe, test, expect, afterEach } from "bun:test"
2+
import path from "path"
3+
import fs from "fs"
4+
import {
5+
installTmpdir,
6+
createBinaryPackage,
7+
createDummyBinary,
8+
runBinWrapper,
9+
BIN_WRAPPER_SCRIPT,
10+
CURRENT_PLATFORM,
11+
CURRENT_ARCH,
12+
} from "./fixture"
13+
14+
let cleanup: (() => void) | undefined
15+
16+
afterEach(() => {
17+
cleanup?.()
18+
cleanup = undefined
19+
})
20+
21+
function copyBinWrapper(destDir: string): string {
22+
const binDir = path.join(destDir, "bin")
23+
fs.mkdirSync(binDir, { recursive: true })
24+
const wrapperPath = path.join(binDir, "altimate-code")
25+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
26+
return wrapperPath
27+
}
28+
29+
describe("bin/altimate-code wrapper", () => {
30+
test("uses ALTIMATE_CODE_BIN_PATH env var when set", () => {
31+
const { dir, cleanup: c } = installTmpdir()
32+
cleanup = c
33+
34+
const wrapperPath = copyBinWrapper(dir)
35+
const dummyBin = createDummyBinary(dir)
36+
37+
const result = runBinWrapper(wrapperPath, [], {
38+
ALTIMATE_CODE_BIN_PATH: dummyBin,
39+
})
40+
expect(result.exitCode).toBe(0)
41+
expect(result.stdout).toContain("altimate-code-test-ok")
42+
})
43+
44+
test("uses cached .altimate-code when present", () => {
45+
const { dir, cleanup: c } = installTmpdir()
46+
cleanup = c
47+
48+
const wrapperPath = copyBinWrapper(dir)
49+
const binDir = path.dirname(wrapperPath)
50+
createDummyBinary(binDir, ".altimate-code")
51+
52+
const result = runBinWrapper(wrapperPath)
53+
expect(result.exitCode).toBe(0)
54+
expect(result.stdout).toContain("altimate-code-test-ok")
55+
})
56+
57+
test("finds binary in sibling node_modules package", () => {
58+
const { dir, cleanup: c } = installTmpdir()
59+
cleanup = c
60+
61+
// Standard npm flat layout:
62+
// dir/node_modules/@altimateai/altimate-code/bin/altimate-code (wrapper)
63+
// dir/node_modules/@altimateai/altimate-code-{p}-{a}/bin/binary (binary)
64+
const wrapperPkgBin = path.join(dir, "node_modules", "@altimateai", "altimate-code", "bin")
65+
fs.mkdirSync(wrapperPkgBin, { recursive: true })
66+
const wrapperPath = path.join(wrapperPkgBin, "altimate-code")
67+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
68+
69+
createBinaryPackage(dir)
70+
71+
const result = runBinWrapper(wrapperPath)
72+
expect(result.exitCode).toBe(0)
73+
expect(result.stdout).toContain("altimate-code-test-ok")
74+
})
75+
76+
test("finds binary in parent node_modules (hoisted)", () => {
77+
const { dir, cleanup: c } = installTmpdir()
78+
cleanup = c
79+
80+
// Hoisted layout:
81+
// dir/node_modules/@altimateai/altimate-code-{p}-{a}/bin/binary (hoisted binary)
82+
// dir/packages/app/node_modules/@altimateai/altimate-code/bin/wrapper
83+
createBinaryPackage(dir)
84+
85+
const nestedBin = path.join(dir, "packages", "app", "node_modules", "@altimateai", "altimate-code", "bin")
86+
fs.mkdirSync(nestedBin, { recursive: true })
87+
const wrapperPath = path.join(nestedBin, "altimate-code")
88+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
89+
90+
const result = runBinWrapper(wrapperPath)
91+
expect(result.exitCode).toBe(0)
92+
expect(result.stdout).toContain("altimate-code-test-ok")
93+
})
94+
95+
test("fails with helpful error when no binary exists", () => {
96+
const { dir, cleanup: c } = installTmpdir()
97+
cleanup = c
98+
99+
const wrapperPath = copyBinWrapper(dir)
100+
101+
const result = runBinWrapper(wrapperPath)
102+
expect(result.exitCode).toBe(1)
103+
expect(result.stderr).toContain("package manager failed to install")
104+
})
105+
106+
test("error message lists expected package names", () => {
107+
const { dir, cleanup: c } = installTmpdir()
108+
cleanup = c
109+
110+
const wrapperPath = copyBinWrapper(dir)
111+
112+
const result = runBinWrapper(wrapperPath)
113+
expect(result.exitCode).toBe(1)
114+
expect(result.stderr).toContain(`@altimateai/altimate-code-${CURRENT_PLATFORM}-${CURRENT_ARCH}`)
115+
})
116+
})
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import os from "os"
2+
import path from "path"
3+
import fs from "fs"
4+
import { spawnSync } from "child_process"
5+
6+
const PLATFORM_MAP: Record<string, string> = { darwin: "darwin", linux: "linux", win32: "windows" }
7+
const ARCH_MAP: Record<string, string> = { x64: "x64", arm64: "arm64", arm: "arm" }
8+
9+
export const CURRENT_PLATFORM = PLATFORM_MAP[os.platform()] ?? os.platform()
10+
export const CURRENT_ARCH = ARCH_MAP[os.arch()] ?? os.arch()
11+
export const CURRENT_PKG_NAME = `@altimateai/altimate-code-${CURRENT_PLATFORM}-${CURRENT_ARCH}`
12+
export const BINARY_NAME = CURRENT_PLATFORM === "windows" ? "altimate-code.exe" : "altimate-code"
13+
14+
const REPO_PKG_DIR = path.resolve(import.meta.dir, "../..")
15+
export const POSTINSTALL_SCRIPT = path.join(REPO_PKG_DIR, "script/postinstall.mjs")
16+
export const BIN_WRAPPER_SCRIPT = path.join(REPO_PKG_DIR, "bin/altimate-code")
17+
18+
export function installTmpdir(): { dir: string; cleanup: () => void } {
19+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "altimate-install-test-"))
20+
return {
21+
dir,
22+
cleanup() {
23+
fs.rmSync(dir, { recursive: true, force: true })
24+
},
25+
}
26+
}
27+
28+
interface MainPackageOpts {
29+
version?: string
30+
noBinDir?: boolean
31+
}
32+
33+
export function createMainPackageDir(baseDir: string, opts?: MainPackageOpts) {
34+
const version = opts?.version ?? "1.0.0-test"
35+
36+
fs.copyFileSync(POSTINSTALL_SCRIPT, path.join(baseDir, "postinstall.mjs"))
37+
38+
fs.writeFileSync(
39+
path.join(baseDir, "package.json"),
40+
JSON.stringify({ name: "@altimateai/altimate-code", version }, null, 2),
41+
)
42+
43+
if (!opts?.noBinDir) {
44+
fs.mkdirSync(path.join(baseDir, "bin"), { recursive: true })
45+
}
46+
}
47+
48+
interface BinaryPackageOpts {
49+
platform?: string
50+
arch?: string
51+
noBinaryFile?: boolean
52+
}
53+
54+
export function createBinaryPackage(baseDir: string, opts?: BinaryPackageOpts) {
55+
const platform = opts?.platform ?? CURRENT_PLATFORM
56+
const arch = opts?.arch ?? CURRENT_ARCH
57+
const pkgName = `@altimateai/altimate-code-${platform}-${arch}`
58+
const binaryName = platform === "windows" ? "altimate-code.exe" : "altimate-code"
59+
60+
const pkgDir = path.join(baseDir, "node_modules", "@altimateai", `altimate-code-${platform}-${arch}`)
61+
fs.mkdirSync(pkgDir, { recursive: true })
62+
63+
fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify({ name: pkgName, version: "1.0.0-test" }, null, 2))
64+
65+
if (!opts?.noBinaryFile) {
66+
const binDir = path.join(pkgDir, "bin")
67+
fs.mkdirSync(binDir, { recursive: true })
68+
const binaryPath = path.join(binDir, binaryName)
69+
fs.writeFileSync(binaryPath, '#!/bin/sh\necho "altimate-code-test-ok"')
70+
fs.chmodSync(binaryPath, 0o755)
71+
}
72+
73+
return pkgDir
74+
}
75+
76+
export function createDummyBinary(dir: string, name?: string): string {
77+
const binaryPath = path.join(dir, name ?? "altimate-code-dummy")
78+
fs.writeFileSync(binaryPath, '#!/bin/sh\necho "altimate-code-test-ok"')
79+
fs.chmodSync(binaryPath, 0o755)
80+
return binaryPath
81+
}
82+
83+
export function runPostinstall(cwd: string) {
84+
const result = spawnSync("node", ["postinstall.mjs"], {
85+
cwd,
86+
encoding: "utf-8",
87+
timeout: 10_000,
88+
})
89+
return {
90+
exitCode: result.status ?? -1,
91+
stdout: result.stdout ?? "",
92+
stderr: result.stderr ?? "",
93+
}
94+
}
95+
96+
export function runBinWrapper(binPath: string, args: string[] = [], env?: Record<string, string>) {
97+
const cleanEnv = { ...process.env }
98+
delete cleanEnv.ALTIMATE_CODE_BIN_PATH
99+
100+
const result = spawnSync("node", [binPath, ...args], {
101+
encoding: "utf-8",
102+
timeout: 10_000,
103+
env: { ...cleanEnv, ...env },
104+
})
105+
return {
106+
exitCode: result.status ?? -1,
107+
stdout: result.stdout ?? "",
108+
stderr: result.stderr ?? "",
109+
}
110+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { describe, test, expect, afterEach } from "bun:test"
2+
import path from "path"
3+
import fs from "fs"
4+
import {
5+
installTmpdir,
6+
createMainPackageDir,
7+
createBinaryPackage,
8+
runPostinstall,
9+
runBinWrapper,
10+
BIN_WRAPPER_SCRIPT,
11+
CURRENT_PLATFORM,
12+
} from "./fixture"
13+
14+
let cleanup: (() => void) | undefined
15+
16+
afterEach(() => {
17+
cleanup?.()
18+
cleanup = undefined
19+
})
20+
21+
describe("install pipeline integration", () => {
22+
test("full flow: layout -> postinstall -> bin wrapper executes dummy binary", () => {
23+
const { dir, cleanup: c } = installTmpdir()
24+
cleanup = c
25+
26+
// 1. Build npm-like package layout
27+
createMainPackageDir(dir)
28+
createBinaryPackage(dir)
29+
30+
// 2. Postinstall creates .altimate-code hard link
31+
const postResult = runPostinstall(dir)
32+
expect(postResult.exitCode).toBe(0)
33+
34+
const cachedBin = path.join(dir, "bin", ".altimate-code")
35+
expect(fs.existsSync(cachedBin)).toBe(true)
36+
37+
// 3. Place bin wrapper in the same bin/ directory
38+
const wrapperPath = path.join(dir, "bin", "altimate-code")
39+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
40+
41+
// 4. Wrapper finds cached .altimate-code and executes it
42+
const wrapperResult = runBinWrapper(wrapperPath)
43+
expect(wrapperResult.exitCode).toBe(0)
44+
expect(wrapperResult.stdout).toContain("altimate-code-test-ok")
45+
})
46+
47+
test("missing optional dep: postinstall fails, bin wrapper also fails gracefully", () => {
48+
const { dir, cleanup: c } = installTmpdir()
49+
cleanup = c
50+
51+
// Layout WITHOUT binary package — simulates expired npm token / silent optionalDep failure
52+
createMainPackageDir(dir)
53+
54+
// 1. Postinstall fails because platform binary package is missing
55+
const postResult = runPostinstall(dir)
56+
expect(postResult.exitCode).toBe(1)
57+
expect(postResult.stderr).toContain("Failed to setup altimate-code binary")
58+
59+
// 2. No cached binary was created
60+
expect(fs.existsSync(path.join(dir, "bin", ".altimate-code"))).toBe(false)
61+
62+
// 3. Bin wrapper also fails with helpful error when invoked directly
63+
const wrapperPkgBin = path.join(dir, "node_modules", "@altimateai", "altimate-code", "bin")
64+
fs.mkdirSync(wrapperPkgBin, { recursive: true })
65+
const wrapperPath = path.join(wrapperPkgBin, "altimate-code")
66+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
67+
68+
const wrapperResult = runBinWrapper(wrapperPath)
69+
expect(wrapperResult.exitCode).toBe(1)
70+
expect(wrapperResult.stderr).toContain("package manager failed to install")
71+
})
72+
73+
test("wrong-platform-only install: both scripts fail with clear errors", () => {
74+
const { dir, cleanup: c } = installTmpdir()
75+
cleanup = c
76+
77+
createMainPackageDir(dir)
78+
const wrongPlatform = CURRENT_PLATFORM === "darwin" ? "linux" : "darwin"
79+
createBinaryPackage(dir, { platform: wrongPlatform })
80+
81+
// 1. Postinstall fails — can't find binary for current platform
82+
const postResult = runPostinstall(dir)
83+
expect(postResult.exitCode).toBe(1)
84+
85+
// 2. Bin wrapper also fails — wrong-platform package doesn't match
86+
const wrapperPkgBin = path.join(dir, "node_modules", "@altimateai", "altimate-code", "bin")
87+
fs.mkdirSync(wrapperPkgBin, { recursive: true })
88+
const wrapperPath = path.join(wrapperPkgBin, "altimate-code")
89+
fs.copyFileSync(BIN_WRAPPER_SCRIPT, wrapperPath)
90+
91+
const wrapperResult = runBinWrapper(wrapperPath)
92+
expect(wrapperResult.exitCode).toBe(1)
93+
expect(wrapperResult.stderr).toContain("package manager failed to install")
94+
})
95+
})

0 commit comments

Comments
 (0)