From 4418cf1d41bc9869dda6ed698be0e6ab9506e766 Mon Sep 17 00:00:00 2001 From: Adam Sardo Date: Sat, 9 May 2026 23:32:42 +1000 Subject: [PATCH] Add BATS test suite --- .github/workflows/test.yml | 20 ++++ Makefile | 6 ++ README.md | 13 ++- test/node-reinstall.bats | 196 +++++++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 100644 test/node-reinstall.bats diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..da778a9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,20 @@ +name: test + +on: + pull_request: + push: + +jobs: + bats: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Install bats-core + run: | + git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core + sudo /tmp/bats-core/install.sh /usr/local + - name: Run tests + run: make test diff --git a/Makefile b/Makefile index 3b5077a..cb8c852 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,10 @@ install: uninstall: rm -f $(PREFIX)/bin/$(BIN) +test: + bash -n ./node-reinstall + bats test + readme: perl -pi -w -e "s/Usage:.*/$(USAGE)/" README.md sed '/Commands/,$$ d' README.md > changes.md @@ -15,3 +19,5 @@ readme: echo "## Commands" >> README.md echo '' >> README.md ./node-reinstall -h | sed -n -e '/Commands:/,// p' | tail -n +3 >> README.md + +.PHONY: install uninstall test readme diff --git a/README.md b/README.md index 93ebe77..ea47be9 100644 --- a/README.md +++ b/README.md @@ -99,4 +99,15 @@ Whenever you feel like you need to completely re-install Node and NPM, simply ex node-reinstall --nave re-install using nave node-reinstall --nvm re-install using stable nvm - the default node-reinstall --nvm-latest re-install using latest nvm - creationix/nvm:master - node-reinstall 5.0.0 specify a default node version +node-reinstall 5.0.0 specify a default node version + +## Testing + +Install [bats-core](https://github.com/bats-core/bats-core), then run: + +``` +make test +``` + +The test suite shadows destructive commands with mocks and runs `node-reinstall` +against temporary `HOME` and `PREFIX` paths. diff --git a/test/node-reinstall.bats b/test/node-reinstall.bats new file mode 100644 index 0000000..58497e6 --- /dev/null +++ b/test/node-reinstall.bats @@ -0,0 +1,196 @@ +#!/usr/bin/env bats + +setup() { + TEST_ROOT="${BATS_TEST_TMPDIR:-${BATS_TMPDIR}/node-reinstall-${BATS_TEST_NUMBER}}" + export HOME="${TEST_ROOT}/home" + export PREFIX="${TEST_ROOT}/prefix" + export MOCK_BIN="${TEST_ROOT}/bin" + export COMMAND_LOG="${TEST_ROOT}/commands.log" + + mkdir -p "$HOME" "$PREFIX/bin" "$MOCK_BIN" + : > "$COMMAND_LOG" + + create_mock_commands + export PATH="${MOCK_BIN}:${PATH}" +} + +create_mock_commands() { + cat > "${MOCK_BIN}/sudo" <<'SH' +#!/usr/bin/env bash +printf 'sudo %s\n' "$*" >> "$COMMAND_LOG" +exit 0 +SH + + cat > "${MOCK_BIN}/rm" <<'SH' +#!/usr/bin/env bash +printf 'rm %s\n' "$*" >> "$COMMAND_LOG" +exit 0 +SH + + cat > "${MOCK_BIN}/which" <<'SH' +#!/usr/bin/env bash +case "$1" in + npm) + if [ "${MOCK_NPM_PRESENT:-1}" = "1" ]; then + printf '%s/npm\n' "$MOCK_BIN" + exit 0 + fi + exit 1 + ;; + *) + command -v "$1" + ;; +esac +SH + + cat > "${MOCK_BIN}/node" <<'SH' +#!/usr/bin/env bash +if [ "$1" = "--version" ] && [ -n "${MOCK_NODE_VERSION:-}" ]; then + printf '%s\n' "$MOCK_NODE_VERSION" + exit 0 +fi +exit 1 +SH + + cat > "${MOCK_BIN}/npm" <<'SH' +#!/usr/bin/env bash +printf 'npm %s\n' "$*" >> "$COMMAND_LOG" +if [ "$*" = "-g list --depth 0 --parseable" ]; then + if [ -n "${MOCK_GLOBAL_MODULES:-}" ]; then + printf '%s\n' "$MOCK_GLOBAL_MODULES" + fi + exit 0 +fi +exit 0 +SH + + cat > "${MOCK_BIN}/curl" <<'SH' +#!/usr/bin/env bash +printf 'curl %s\n' "$*" >> "$COMMAND_LOG" +case "$*" in + *api.github.com/repos/nvm-sh/nvm/releases/latest*) + printf '%s\n' '{"tag_name":"v0.39.7"}' + ;; + *raw.githubusercontent.com/nvm-sh/nvm/*/install.sh*) + printf '%s\n' 'echo "mock nvm installer"' + ;; + *raw.githubusercontent.com/isaacs/nave/master/nave.sh*) + printf '%s\n' '# mock nave installer' + ;; +esac +SH + + cat > "${MOCK_BIN}/nvm" <<'SH' +#!/usr/bin/env bash +printf 'nvm %s\n' "$*" >> "$COMMAND_LOG" +exit 0 +SH + + cat > "${MOCK_BIN}/nave" <<'SH' +#!/usr/bin/env bash +printf 'nave %s\n' "$*" >> "$COMMAND_LOG" +exit 0 +SH + + chmod +x "${MOCK_BIN}/"* +} + +assert_output_contains() { + case "$output" in + *"$1"*) return 0 ;; + *) + printf 'Expected output to contain: %s\nActual output:\n%s\n' "$1" "$output" + return 1 + ;; + esac +} + +assert_log_contains() { + grep -F -- "$1" "$COMMAND_LOG" +} + +assert_log_not_contains() { + ! grep -F -- "$1" "$COMMAND_LOG" +} + +@test "--help prints usage without asking for sudo" { + run ./node-reinstall --help + + [ "$status" -eq 0 ] + assert_output_contains "Usage:" + assert_output_contains "node-reinstall [--nave|--nvm|--nvm-latest]" + assert_log_not_contains "sudo" +} + +@test "--version prints the script version without asking for sudo" { + run ./node-reinstall --version + + [ "$status" -eq 0 ] + [ "$output" = "0.0.17" ] + assert_log_not_contains "sudo" +} + +@test "unknown options fail before destructive commands run" { + run ./node-reinstall --wat + + [ "$status" -eq 1 ] + assert_output_contains "error: Unknown option" + assert_output_contains "Usage:" + assert_log_not_contains "sudo" + assert_log_not_contains "rm " +} + +@test "first-time install uses the requested Node version with nvm" { + export MOCK_NPM_PRESENT=0 + + run ./node-reinstall --force 20 + + [ "$status" -eq 0 ] + assert_output_contains "Installing Node, npm." + assert_output_contains "node-reinstall is done." + assert_log_contains "sudo -v" + assert_log_contains "sudo rm -rf ${HOME}/.nvm" + assert_log_contains "rm -rf ${HOME}/local ${HOME}/lib ${HOME}/include ${HOME}/node* ${HOME}/npm ${HOME}/.npm* ${HOME}/.node-gyp" + assert_log_contains "curl -H Accept: application/vnd.github.v3+json https://api.github.com/repos/nvm-sh/nvm/releases/latest" + assert_log_contains "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh" + assert_log_contains "nvm install 20" + assert_log_contains "nvm alias default 20" +} + +@test "installed Node version is reused when no version is requested" { + export MOCK_NODE_VERSION="v18.19.0" + export MOCK_NPM_PRESENT=0 + + run ./node-reinstall --force + + [ "$status" -eq 0 ] + assert_output_contains "Found Node.js version v18.19.0 already installed." + assert_log_contains "nvm install v18.19.0" + assert_log_contains "nvm alias default v18.19.0" +} + +@test "--nave switches installer and version manager paths" { + export MOCK_NPM_PRESENT=0 + + run ./node-reinstall --force --nave 18.17.1 + + [ "$status" -eq 0 ] + assert_log_contains "sudo rm -rf ${HOME}/.nave" + assert_log_contains "curl -sL https://raw.githubusercontent.com/isaacs/nave/master/nave.sh -o ${PREFIX}/bin/nave" + assert_log_contains "nave usemain 18.17.1" + assert_log_not_contains "nvm install" +} + +@test "global npm modules are captured and reinstalled in force mode" { + export MOCK_NODE_VERSION="v16.13.0" + export MOCK_NPM_PRESENT=1 + export MOCK_GLOBAL_MODULES="/usr/local/lib/node_modules/http-server" + + run ./node-reinstall --force + + [ "$status" -eq 0 ] + assert_output_contains "Will reinstall these global npm modules:" + assert_output_contains "http-server" + assert_log_contains "npm -g list --depth 0 --parseable" + assert_log_contains "npm install --global http-server" +}