diff --git a/docs/superpowers/evidence/phone-focus-adb-test-split-2026-08-17.json b/docs/superpowers/evidence/phone-focus-adb-test-split-2026-08-17.json new file mode 100644 index 00000000..6793ef32 --- /dev/null +++ b/docs/superpowers/evidence/phone-focus-adb-test-split-2026-08-17.json @@ -0,0 +1,51 @@ +{ + "intent": "Bring phone_focus_mode/lib/tests/test_adb_common.sh (287 lines) under the repo-wide 250-line cap without losing a single test, completing the adb_common split started in the previous commit.", + "scope": [ + "phone_focus_mode/lib/tests/test_adb_common.sh — reduced to the ten logging, cooldown, lock, sanitiser, root and device-selection tests", + "phone_focus_mode/lib/tests/test_adb_trusted.sh — new: the six enrolled-device tests, mirroring the adb_trusted.sh split", + "phone_focus_mode/lib/tests/adb_test_harness.sh — new: the shared counters, run_test, TEST_TMPDIR, XDG_STATE_HOME redirect and adb() mock", + "Non-goal: adding, deleting or weakening any assertion; all sixteen are carried across verbatim" + ], + "changes": [ + "Split the sixteen tests along the same seam as the source split in the previous commit: the six that exercise adb_verify_trusted_identity and adb_forget_trusted_device moved to test_adb_trusted.sh, matching adb_trusted.sh, and the other ten stayed.", + "Moved the shared fixtures into adb_test_harness.sh, sourced by both files from their own directory. It keeps the XDG_STATE_HOME redirect into a mktemp dir, so neither test file can touch the real ~/.local/state/phone_focus_mode — that redirect had to move as a unit with the mock, since a test file sourcing the library without it would write to live state.", + "Both test files keep the original exit convention ([[ FAIL -eq 0 ]] as the last statement), so each fails the shell with a non-zero status rather than merely printing a count.", + "The harness keeps the single-quoted injection payloads verbatim, including the comment explaining that expanding $evil or `cmd` there would execute them in the harness and test nothing." + ], + "verification": [ + { + "command": "bash lib/tests/test_adb_common.sh && bash lib/tests/test_adb_trusted.sh", + "result": "pass", + "evidence": "10 passed / 0 failed and 6 passed / 0 failed. 10 + 6 = 16, exactly the pre-split count, which is the discriminating check for a test split: had a test been dropped or a file failed to source its fixtures, the total would have fallen while both files still reported success." + }, + { + "command": "Exit-status check on both files", + "result": "pass", + "evidence": "Both exit 0. Checked explicitly rather than read off the printed summary, because the summary line prints regardless of outcome." + }, + { + "command": "Mutation check: a copy of test_adb_common.sh with one assertion body replaced by `return 1`", + "result": "pass", + "evidence": "The mutated copy exits 1. This proves the split files are not vacuously green — that the run_test dispatch still reaches the test bodies and a genuine failure still fails the script." + }, + { + "command": "pre-commit run --files on all three files", + "result": "pass", + "evidence": "Every hook passes, including shellcheck with no --severity filter, and the shebang/executable check that rejected the previous commit's first attempt. Zero suppressions added." + }, + { + "command": "bash ~/utils/scripts/check_file_length.sh --all", + "result": "pass", + "evidence": "test_adb_common.sh 134, test_adb_trusted.sh 105, adb_test_harness.sh 76 — no adb file of any kind appears in the over-cap listing now, source or test." + } + ], + "risks": [ + "adb_test_harness.sh carries a shebang and the executable bit to match every sibling in lib/tests/, but it is not a test file — running it directly defines fixtures and exits 0 without asserting anything. Its header comment says so. A future test-runner that globs lib/tests/*.sh would count it as a passing suite.", + "The two test files now each source the library independently, so a fixture change has to satisfy both. That is the same tradeoff as the source split and is the reason the harness exists rather than the fixtures being duplicated.", + "This package has no CI workflow, so these tests only run when invoked by hand or by the pre-push ci-mirror; nothing runs them on every commit." + ], + "rollback": [ + "git revert the commit — one edited file and two added files, with no data, config or schema migration.", + "After rollback, re-run bash phone_focus_mode/lib/tests/test_adb_common.sh; expect 16 passed, 0 failed from the single file." + ] +} diff --git a/phone_focus_mode/lib/tests/adb_test_harness.sh b/phone_focus_mode/lib/tests/adb_test_harness.sh new file mode 100755 index 00000000..3eb20c86 --- /dev/null +++ b/phone_focus_mode/lib/tests/adb_test_harness.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# lib/tests/adb_test_harness.sh — shared fixtures for the adb_common test files. +# Sourced by test_adb_common.sh and test_adb_trusted.sh; not a test file itself. +# +# Provides: the PASS/FAIL counters, run_test, a TEST_TMPDIR with an EXIT trap, +# an XDG_STATE_HOME redirect so no test touches the real state dir, the adb() +# mock, and the two injection payloads the sanitiser is tested against. +# Unit tests for adb_common.sh helper functions (no real device needed). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PASS=0 +FAIL=0 + +_t_pass() { + PASS=$((PASS + 1)) + printf ' OK: %s\n' "$1" +} + +_t_fail() { + FAIL=$((FAIL + 1)) + printf ' FAIL: %s\n' "$1" +} + +TEST_TMPDIR="$(mktemp -d)" +trap 'rm -rf "${TEST_TMPDIR}"' EXIT + +export XDG_STATE_HOME="${TEST_TMPDIR}/state" +mkdir -p "${XDG_STATE_HOME}" + +source "${SCRIPT_DIR}/../adb_common.sh" + +ADB_MOCK_MODEL=$'Pixel "7";$(rm -rf /)`danger`\nline2' +# Single quotes are the whole point: this is an injection payload that must +# reach the code under test LITERALLY. Expanding $evil / `cmd` here would +# execute them in the test harness and test nothing. +# shellcheck disable=SC2016 +ADB_MOCK_FINGERPRINT='google/pixel:14/UP1A.231005.007/$evil;`cmd`' + +adb() { + if [[ "$#" -eq 1 && "$1" == "devices" ]]; then + printf 'List of devices attached\nSERIAL123\tdevice\n' + return 0 + fi + + if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.product.model" ]]; then + printf '%s\r\n' "${ADB_MOCK_MODEL}" + return 0 + fi + + if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.build.fingerprint" ]]; then + printf '%s\r\n' "${ADB_MOCK_FINGERPRINT}" + return 0 + fi + + if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "su" && "$5" == "--mount-master" && "$6" == "-c" && "$7" == "echo ok" ]]; then + printf 'ok\n' + return 0 + fi + + printf 'Unexpected adb invocation:' >&2 + printf ' %q' "$@" >&2 + printf '\n' >&2 + return 1 +} + +run_test() { + local name="$1" + shift + + if "$@"; then + _t_pass "${name}" + else + _t_fail "${name}" + fi +} diff --git a/phone_focus_mode/lib/tests/test_adb_common.sh b/phone_focus_mode/lib/tests/test_adb_common.sh index f88cc1cc..acc9a283 100755 --- a/phone_focus_mode/lib/tests/test_adb_common.sh +++ b/phone_focus_mode/lib/tests/test_adb_common.sh @@ -1,73 +1,12 @@ #!/usr/bin/env bash -# Unit tests for adb_common.sh helper functions (no real device needed). +# Unit tests for adb_common.sh and adb_locking.sh (no real device needed). +# The enrolled-device tests live in test_adb_trusted.sh; both share the +# fixtures in adb_test_harness.sh. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PASS=0 -FAIL=0 - -_t_pass() { - PASS=$((PASS + 1)) - printf ' OK: %s\n' "$1" -} - -_t_fail() { - FAIL=$((FAIL + 1)) - printf ' FAIL: %s\n' "$1" -} - -TEST_TMPDIR="$(mktemp -d)" -trap 'rm -rf "${TEST_TMPDIR}"' EXIT - -export XDG_STATE_HOME="${TEST_TMPDIR}/state" -mkdir -p "${XDG_STATE_HOME}" - -source "${SCRIPT_DIR}/../adb_common.sh" - -ADB_MOCK_MODEL=$'Pixel "7";$(rm -rf /)`danger`\nline2' -# Single quotes are the whole point: this is an injection payload that must -# reach the code under test LITERALLY. Expanding $evil / `cmd` here would -# execute them in the test harness and test nothing. -# shellcheck disable=SC2016 -ADB_MOCK_FINGERPRINT='google/pixel:14/UP1A.231005.007/$evil;`cmd`' - -adb() { - if [[ "$#" -eq 1 && "$1" == "devices" ]]; then - printf 'List of devices attached\nSERIAL123\tdevice\n' - return 0 - fi - - if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.product.model" ]]; then - printf '%s\r\n' "${ADB_MOCK_MODEL}" - return 0 - fi - - if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.build.fingerprint" ]]; then - printf '%s\r\n' "${ADB_MOCK_FINGERPRINT}" - return 0 - fi - - if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "su" && "$5" == "--mount-master" && "$6" == "-c" && "$7" == "echo ok" ]]; then - printf 'ok\n' - return 0 - fi - - printf 'Unexpected adb invocation:' >&2 - printf ' %q' "$@" >&2 - printf '\n' >&2 - return 1 -} - -run_test() { - local name="$1" - shift - - if "$@"; then - _t_pass "${name}" - else - _t_fail "${name}" - fi -} +_HARNESS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=adb_test_harness.sh +source "${_HARNESS_DIR}/adb_test_harness.sh" test_box_does_not_crash() { _box "Test title" "line 1" "line 2" >/dev/null 2>&1 @@ -180,92 +119,6 @@ EOF return 0 } -test_verify_trusted_identity_accepts_exact_match() { - export ADB_SERIAL='SERIAL123' - adb_save_trusted_device - adb_verify_trusted_identity >/dev/null 2>&1 -} - -test_verify_trusted_identity_rejects_model_mismatch() { - export ADB_SERIAL='SERIAL123' - adb_save_trusted_device - - cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF' -TRUSTED_SERIAL='SERIAL123' -TRUSTED_MODEL='Different Model' -TRUSTED_FINGERPRINT='google/pixel:14/UP1A.231005.007/evilcmd' -EOF - - if (adb_verify_trusted_identity >/dev/null 2>&1); then - return 1 - fi - - return 0 -} - -test_verify_trusted_identity_rejects_fingerprint_mismatch() { - export ADB_SERIAL='SERIAL123' - adb_save_trusted_device - - cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF' -TRUSTED_SERIAL='SERIAL123' -TRUSTED_MODEL='Pixel 7rm -rf /dangerline2' -TRUSTED_FINGERPRINT='different/fingerprint' -EOF - - if (adb_verify_trusted_identity >/dev/null 2>&1); then - return 1 - fi - - return 0 -} - -test_verify_trusted_identity_accepts_any_enrolled_device() { - # Two phones enrolled from one PC: the second must not evict the first, - # which is the whole reason the store is a directory. - export ADB_SERIAL='SERIAL_ONE' - adb_save_trusted_device - export ADB_SERIAL='SERIAL_TWO' - adb_save_trusted_device - - adb_verify_trusted_identity >/dev/null 2>&1 || return 1 - - export ADB_SERIAL='SERIAL_ONE' - adb_verify_trusted_identity >/dev/null 2>&1 || return 1 - - return 0 -} - -test_verify_trusted_identity_rejects_unenrolled_device() { - export ADB_SERIAL='SERIAL_ONE' - adb_save_trusted_device - - # An unknown serial must still abort, or multi-device support would have - # quietly turned the guard off. - export ADB_SERIAL='SERIAL_STRANGER' - if (adb_verify_trusted_identity >/dev/null 2>&1); then - return 1 - fi - - return 0 -} - -test_forget_trusted_device_removes_only_that_record() { - export ADB_SERIAL='SERIAL_ONE' - adb_save_trusted_device - export ADB_SERIAL='SERIAL_TWO' - adb_save_trusted_device - - adb_forget_trusted_device 'SERIAL_ONE' >/dev/null 2>&1 - - adb_list_trusted_serials | grep -qx 'SERIAL_TWO' || return 1 - if adb_list_trusted_serials | grep -qx 'SERIAL_ONE'; then - return 1 - fi - - return 0 -} - run_test "_box output without crash" test_box_does_not_crash run_test "adb_check_cooldown 0 returns 0 (proceed)" test_check_cooldown_zero_allows run_test "adb_check_cooldown blocks when marker is fresh" test_check_cooldown_blocks_when_marker_fresh @@ -276,12 +129,6 @@ run_test "_sanitize_device_string strips dangerous characters" test_sanitize_dev run_test "adb_save_trusted_device sanitizes and safely quotes values" test_save_trusted_device_sanitizes_and_quotes run_test "adb_verify_root succeeds when root shell returns ok" test_verify_root_uses_root_shell run_test "adb_select_device rejects multiple devices even with trusted record" test_select_device_rejects_multiple_devices_even_with_trusted_record -run_test "adb_verify_trusted_identity accepts exact saved identity" test_verify_trusted_identity_accepts_exact_match -run_test "adb_verify_trusted_identity rejects model mismatch" test_verify_trusted_identity_rejects_model_mismatch -run_test "adb_verify_trusted_identity rejects fingerprint mismatch" test_verify_trusted_identity_rejects_fingerprint_mismatch -run_test "adb_verify_trusted_identity accepts any enrolled device" test_verify_trusted_identity_accepts_any_enrolled_device -run_test "adb_verify_trusted_identity rejects unenrolled device" test_verify_trusted_identity_rejects_unenrolled_device -run_test "adb_forget_trusted_device removes only that record" test_forget_trusted_device_removes_only_that_record printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}" [[ "${FAIL}" -eq 0 ]] diff --git a/phone_focus_mode/lib/tests/test_adb_trusted.sh b/phone_focus_mode/lib/tests/test_adb_trusted.sh new file mode 100755 index 00000000..61c7e6e4 --- /dev/null +++ b/phone_focus_mode/lib/tests/test_adb_trusted.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# Unit tests for adb_trusted.sh: verifying a connected phone against the +# enrolled-device records, and forgetting one. Shares the fixtures in +# adb_test_harness.sh with test_adb_common.sh. +set -euo pipefail + +_HARNESS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=adb_test_harness.sh +source "${_HARNESS_DIR}/adb_test_harness.sh" + +test_verify_trusted_identity_accepts_exact_match() { + export ADB_SERIAL='SERIAL123' + adb_save_trusted_device + adb_verify_trusted_identity >/dev/null 2>&1 +} + +test_verify_trusted_identity_rejects_model_mismatch() { + export ADB_SERIAL='SERIAL123' + adb_save_trusted_device + + cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF' +TRUSTED_SERIAL='SERIAL123' +TRUSTED_MODEL='Different Model' +TRUSTED_FINGERPRINT='google/pixel:14/UP1A.231005.007/evilcmd' +EOF + + if (adb_verify_trusted_identity >/dev/null 2>&1); then + return 1 + fi + + return 0 +} + +test_verify_trusted_identity_rejects_fingerprint_mismatch() { + export ADB_SERIAL='SERIAL123' + adb_save_trusted_device + + cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF' +TRUSTED_SERIAL='SERIAL123' +TRUSTED_MODEL='Pixel 7rm -rf /dangerline2' +TRUSTED_FINGERPRINT='different/fingerprint' +EOF + + if (adb_verify_trusted_identity >/dev/null 2>&1); then + return 1 + fi + + return 0 +} + +test_verify_trusted_identity_accepts_any_enrolled_device() { + # Two phones enrolled from one PC: the second must not evict the first, + # which is the whole reason the store is a directory. + export ADB_SERIAL='SERIAL_ONE' + adb_save_trusted_device + export ADB_SERIAL='SERIAL_TWO' + adb_save_trusted_device + + adb_verify_trusted_identity >/dev/null 2>&1 || return 1 + + export ADB_SERIAL='SERIAL_ONE' + adb_verify_trusted_identity >/dev/null 2>&1 || return 1 + + return 0 +} + +test_verify_trusted_identity_rejects_unenrolled_device() { + export ADB_SERIAL='SERIAL_ONE' + adb_save_trusted_device + + # An unknown serial must still abort, or multi-device support would have + # quietly turned the guard off. + export ADB_SERIAL='SERIAL_STRANGER' + if (adb_verify_trusted_identity >/dev/null 2>&1); then + return 1 + fi + + return 0 +} + +test_forget_trusted_device_removes_only_that_record() { + export ADB_SERIAL='SERIAL_ONE' + adb_save_trusted_device + export ADB_SERIAL='SERIAL_TWO' + adb_save_trusted_device + + adb_forget_trusted_device 'SERIAL_ONE' >/dev/null 2>&1 + + adb_list_trusted_serials | grep -qx 'SERIAL_TWO' || return 1 + if adb_list_trusted_serials | grep -qx 'SERIAL_ONE'; then + return 1 + fi + + return 0 +} + +run_test "adb_verify_trusted_identity accepts exact saved identity" test_verify_trusted_identity_accepts_exact_match +run_test "adb_verify_trusted_identity rejects model mismatch" test_verify_trusted_identity_rejects_model_mismatch +run_test "adb_verify_trusted_identity rejects fingerprint mismatch" test_verify_trusted_identity_rejects_fingerprint_mismatch +run_test "adb_verify_trusted_identity accepts any enrolled device" test_verify_trusted_identity_accepts_any_enrolled_device +run_test "adb_verify_trusted_identity rejects unenrolled device" test_verify_trusted_identity_rejects_unenrolled_device +run_test "adb_forget_trusted_device removes only that record" test_forget_trusted_device_removes_only_that_record + +printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}" +[[ "${FAIL}" -eq 0 ]]