diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c957b3..2af7b361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Fixed +- `bashunit upgrade` no longer reports success when the binary download fails; it now exits non-zero, prints the URL it tried, surfaces the underlying curl/wget error, and validates the downloaded file is non-empty before replacing the binary + ### Added - `--show-output` displays captured test output on assertion failures (#637) - npm registry distribution: `npm install -g bashunit` ships the prebuilt single-file binary (#244) diff --git a/src/io.sh b/src/io.sh index c3f2a1e4..a928b37b 100644 --- a/src/io.sh +++ b/src/io.sh @@ -21,10 +21,11 @@ function bashunit::io::download_to() { local url="$1" local output="$2" if bashunit::dependencies::has_curl; then - curl -L -J -o "$output" "$url" 2>/dev/null + curl -fsSL -o "$output" "$url" elif bashunit::dependencies::has_wget; then - wget -q -O "$output" "$url" 2>/dev/null + wget -q -O "$output" "$url" else + echo "no curl or wget available" >&2 return 1 fi } diff --git a/src/upgrade.sh b/src/upgrade.sh index f65783b4..fd128d2e 100644 --- a/src/upgrade.sh +++ b/src/upgrade.sh @@ -1,25 +1,54 @@ #!/usr/bin/env bash function bashunit::upgrade::upgrade() { - local script_path - script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local install_dir="${BASHUNIT_INSTALL_DIR:-}" + if [ -z "$install_dir" ]; then + install_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + fi + local target="$install_dir/bashunit" + local latest_tag latest_tag="$(bashunit::helper::get_latest_tag)" + if [ -z "$latest_tag" ]; then + echo "Failed to resolve latest bashunit version. Check your internet connection and that 'git' is installed." >&2 + return 1 + fi + if [ "$BASHUNIT_VERSION" = "$latest_tag" ]; then echo "> You are already on latest version" - return + return 0 fi echo "> Upgrading bashunit to latest version" - cd "$script_path" || exit local url="https://github.com/TypedDevs/bashunit/releases/download/$latest_tag/bashunit" - if ! bashunit::io::download_to "$url" "bashunit"; then - echo "Failed to download bashunit" + local err_file + err_file="$(mktemp 2>/dev/null || echo "/tmp/bashunit_upgrade_err.$$")" + local download_status=0 + bashunit::io::download_to "$url" "$target" 2>"$err_file" || download_status=$? + + if [ "$download_status" -ne 0 ]; then + echo "Failed to download bashunit $latest_tag from $url" >&2 + if [ -s "$err_file" ]; then + echo "Reason:" >&2 + sed 's/^/ /' "$err_file" >&2 + fi + rm -f "$err_file" "$target" + return 1 + fi + rm -f "$err_file" + + if [ ! -s "$target" ]; then + echo "Failed to download bashunit $latest_tag from $url (empty file)" >&2 + rm -f "$target" + return 1 fi - chmod u+x "bashunit" + if ! chmod u+x "$target"; then + echo "Failed to make $target executable" >&2 + return 1 + fi echo "> bashunit upgraded successfully to latest version $latest_tag" } diff --git a/tests/unit/upgrade_test.sh b/tests/unit/upgrade_test.sh new file mode 100644 index 00000000..cc5c355a --- /dev/null +++ b/tests/unit/upgrade_test.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2329 # Test functions are invoked indirectly by bashunit + +function set_up() { + WORK_DIR="$(mktemp -d)" + export BASHUNIT_INSTALL_DIR="$WORK_DIR" +} + +function tear_down() { + unset BASHUNIT_INSTALL_DIR + rm -rf "$WORK_DIR" +} + +function fake_get_latest_tag() { + echo "9.9.9" +} + +function fake_get_empty_tag() { + echo "" +} + +function fake_download_fail() { + echo "curl: (6) Could not resolve host: github.com" >&2 + return 1 +} + +function fake_download_success() { + local _url="$1" + local output="$2" + printf '#!/usr/bin/env bash\necho fake\n' >"$output" +} + +function fake_download_empty() { + local _url="$1" + local output="$2" + : >"$output" +} + +function fake_get_current_tag() { + echo "$BASHUNIT_VERSION" +} + +function test_upgrade_aborts_when_download_fails() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_latest_tag + bashunit::mock bashunit::io::download_to fake_download_fail + + local output + local exit_code=0 + output="$(bashunit::upgrade::upgrade 2>&1)" || exit_code=$? + + assert_contains "Failed to download bashunit 9.9.9 from" "$output" + assert_contains "https://github.com/TypedDevs/bashunit/releases/download/9.9.9/bashunit" "$output" + assert_not_contains "upgraded successfully" "$output" + assert_equals "1" "$exit_code" + assert_file_not_exists "$WORK_DIR/bashunit" +} + +function test_upgrade_surfaces_underlying_download_error_message() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_latest_tag + bashunit::mock bashunit::io::download_to fake_download_fail + + local output + output="$(bashunit::upgrade::upgrade 2>&1)" || true + + assert_contains "Reason:" "$output" + assert_contains "Could not resolve host" "$output" +} + +function test_upgrade_aborts_when_downloaded_file_is_empty() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_latest_tag + bashunit::mock bashunit::io::download_to fake_download_empty + + local output + local exit_code=0 + output="$(bashunit::upgrade::upgrade 2>&1)" || exit_code=$? + + assert_contains "empty file" "$output" + assert_not_contains "upgraded successfully" "$output" + assert_equals "1" "$exit_code" + assert_file_not_exists "$WORK_DIR/bashunit" +} + +function test_upgrade_aborts_when_latest_tag_cannot_be_resolved() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_empty_tag + + local output + local exit_code=0 + output="$(bashunit::upgrade::upgrade 2>&1)" || exit_code=$? + + assert_contains "Failed to resolve latest bashunit version" "$output" + assert_equals "1" "$exit_code" +} + +function test_upgrade_reports_success_when_download_succeeds() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_latest_tag + bashunit::mock bashunit::io::download_to fake_download_success + + local output + output="$(bashunit::upgrade::upgrade 2>&1)" + + assert_contains "Upgrading bashunit to latest version" "$output" + assert_contains "upgraded successfully to latest version 9.9.9" "$output" + assert_not_contains "Failed to download" "$output" + assert_file_exists "$WORK_DIR/bashunit" + assert_successful_code "$([ -x "$WORK_DIR/bashunit" ] && echo 0 || echo 1)" +} + +function test_upgrade_skips_when_already_on_latest() { + bashunit::mock bashunit::helper::get_latest_tag fake_get_current_tag + + local output + output="$(bashunit::upgrade::upgrade 2>&1)" + + assert_contains "You are already on latest version" "$output" + assert_not_contains "Failed to download" "$output" + assert_not_contains "upgraded successfully" "$output" +}