#!/usr/bin/env bats # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2026 Bryan Joshua Pedini # Root handling: what needs root, sudo re-exec with identical arguments, clear failures, # and the commands that must work unprivileged. load test_helper setup() { common_setup export SKEPTICAL_UPDATER_EUID=1000 } @test "the interactive upgrade as non-root without sudo fails clearly (exit 1)" { run skup <<<'' assert_failure 1 assert_output --partial 'upgrading needs root and sudo is not available; run skeptical-updater as root (or use --dry-run)' [[ -z $(cat "$FAKE_LOG") ]] } @test "list with a refresh needs root; without sudo it fails clearly" { run skup list assert_failure 1 assert_output --partial 'refreshing the package lists needs root (use --no-refresh)' log_lacks 'update' } @test "list --no-refresh works unprivileged" { run skup list --no-refresh assert_success assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:' } @test "trusted, info and show work unprivileged" { write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-10-09T09:40:12Z" run skup trusted assert_success assert_line "apt${T}su-dummy-a${T}user${T}2026-10-09T09:40:12Z" run skup info su-dummy-b assert_success assert_line 'reason: installed as a dependency' assert_line 'required by: su-dummy-a' run skup show su-dummy-a assert_success assert_line 'Package: su-dummy-a' } @test "--dry-run works unprivileged: the refresh is printed, not run" { run skup --dry-run <<<'' assert_success assert_line 'Refreshing package lists... skipped (not root), the command would be:' assert_line '+ apt-get update -qq -o APT::Update::Error-Mode=any' assert_line '+ apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o DPkg::Lock::Timeout=60 --with-new-pkgs upgrade' log_lacks '^apt-get update' log_lacks '^apt-get -y' } @test "with sudo available, non-root re-execs through sudo: this file itself, same arguments, hidden marker" { use_fakes apt sudo run skup --all list assert_success log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=$STATE --all list" log_lacks '^apt-get' } @test "the sudo re-exec carries --no-color when NO_COLOR is set" { use_fakes apt sudo unset SKEPTICAL_UPDATER_STATE_DIR NO_COLOR=1 run skup --backend=apt assert_success log_has "sudo -- $SU_BIN --sudo-reexec --no-color --backend=apt" } @test "an explicit --state-dir is passed through unchanged" { use_fakes apt sudo run skup --state-dir=/srv/x list assert_success log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=/srv/x list" } @test "a copy that is not executable and not in PATH is re-run through bash with its absolute path" { use_fakes apt sudo mkdir -p "$BATS_TEST_TMPDIR/copy" cp "$SU_BIN" "$BATS_TEST_TMPDIR/copy/su-copy" chmod 0644 "$BATS_TEST_TMPDIR/copy/su-copy" cd "$BATS_TEST_TMPDIR/copy" run env PATH="$TEST_PATH" bash su-copy list assert_success log_matches "^sudo -- /[^ ]*/bash $BATS_TEST_TMPDIR/copy/su-copy --sudo-reexec --state-dir=$STATE list\$" } @test "an executable copy invoked through bash is still re-run directly (sudoers rules match the file)" { use_fakes apt sudo mkdir -p "$BATS_TEST_TMPDIR/copy" cp "$SU_BIN" "$BATS_TEST_TMPDIR/copy/su-copy" chmod 0755 "$BATS_TEST_TMPDIR/copy/su-copy" run env PATH="$TEST_PATH" bash "$BATS_TEST_TMPDIR/copy/su-copy" list assert_success log_has "sudo -- $BATS_TEST_TMPDIR/copy/su-copy --sudo-reexec --state-dir=$STATE list" } @test "after a sudo re-exec that did not make us root, there is no second re-exec" { use_fakes apt sudo run skup --sudo-reexec list assert_failure 1 assert_output --partial 're-executed through sudo but still not running as root (euid 1000); not trying again' log_lacks '^sudo' } @test "--sudo-reexec is only recognised as the first argument and is never forwarded" { run skup list --sudo-reexec assert_failure 1 assert_output --partial 'unknown option: --sudo-reexec' SKEPTICAL_UPDATER_EUID=0 run skup --sudo-reexec list --no-refresh assert_success log_has 'apt-get -s --with-new-pkgs upgrade' log_lacks 'sudo-reexec' } @test "trust into a writable state dir does not need root" { use_fakes apt sudo run skup trust su-dummy-c assert_success assert_output 'trusted su-dummy-c' log_lacks '^sudo' } @test "trust into a non-writable state dir re-execs through sudo" { ro=$BATS_TEST_DIRNAME/no-such-state-dir [[ -w $BATS_TEST_DIRNAME ]] && skip "the repository is writable here (the suite mounts it read-only in Docker)" use_fakes apt sudo run skup --state-dir="$ro" trust su-dummy-c assert_success log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=$ro trust su-dummy-c" } @test "--dry-run never re-execs through sudo" { use_fakes apt sudo run skup --dry-run list assert_success log_lacks '^sudo' } @test "as root nothing is re-executed" { use_fakes apt sudo SKEPTICAL_UPDATER_EUID=0 run skup list assert_success log_lacks '^sudo' log_has 'apt-get update -qq -o APT::Update::Error-Mode=any' }