Files
skeptical-updater/tests/unit/statefile.bats
T
bryanpedini def537af58 feat(aur): list and upgrade AUR packages through paru or yay on Arch
On the pacman backend, an installed AUR helper (paru, else yay;
--aur-helper=paru|yay|none, --no-aur) adds the AUR updates to the review
list. The helper never runs as root: when the tool is root through sudo it
runs as SUDO_USER (runuser -u, else sudo -u, with that user's HOME, from
that home); unprivileged it runs as is; a root login gets no AUR, with a
warning. The banner names the helper ("+ AUR via paru").

- listing: <helper> -Qua (also with --no-refresh), rows with repository
  "aur"; [ignored] is shown [held]; a failing -Qua warns and the session
  goes on with the repository rows
- every row has a marker column after the number: "*" for AUR rows, a
  space otherwise (on every backend)
- N?? merges pacman -Qi with <helper> -Sia; N?, N+, Nx stay with pacman;
  N- excludes through the helper's --ignore
- <enter>: the repository step exactly as before, then <helper> -Sua
  --ignore <excluded> as the invoking user (--noconfirm only when stdin is
  not a terminal); pacman is skipped when only AUR rows are left; a helper
  failure is reported and its status returned; --dry-run prints it
- other backends never run a helper; --aur-helper is ignored with a warning

paru 2.1.0 and yay 13.0.1 formats verified in discovery/pacman.md section
15 (proof: discovery/pacman/aur.sh). Unit tests replay the recorded output
(fake paru, yay, runuser, getent); the archlinux E2E run installs the
foreign su-dummy-aur and drives a fake paru through a fake sudo built on
passwordless su for wheel.
2026-10-10 15:06:18 +02:00

261 lines
9.4 KiB
Bash

#!/usr/bin/env bats
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2026 Bryan Joshua Pedini
# The statefile: creation, format, sorting, dedupe, comments, atomic writes, trust/untrust/refresh.
load test_helper
setup() { common_setup; }
@test "trust creates the directory (0755) and the file (0644) with the header" {
run skup trust su-dummy-c
assert_success
assert_output 'trusted su-dummy-c'
[[ $(stat -c %a "$STATE") == 755 ]]
[[ $(stat -c %a "$STATE_FILE") == 644 ]]
run cat "$STATE_FILE"
assert_line --index 0 "$HEADER"
assert_line --index 1 --regexp "^apt${T}su-dummy-c${T}user${T}${DATE_RE}\$"
[[ ${#lines[@]} -eq 2 ]]
}
@test "intermediate directories are created too" {
run skup --state-dir="$BATS_TEST_TMPDIR/a/b/c" trust su-dummy-c
assert_success
[[ -f $BATS_TEST_TMPDIR/a/b/c/trusted.tsv ]]
[[ $(stat -c %a "$BATS_TEST_TMPDIR/a/b/c") == 755 ]]
}
@test "trust writes the user row and dep:<root> rows for the installed closure" {
run skup trust su-dummy-a
assert_success
assert_output 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
run cat "$STATE_FILE"
assert_line --index 1 --regexp "^apt${T}su-dummy-a${T}user${T}${DATE_RE}\$"
assert_line --index 2 --regexp "^apt${T}su-dummy-b${T}dep:su-dummy-a${T}${DATE_RE}\$"
log_has "dpkg-query -W -f \${db:Status-Abbrev}\\t\${Package}\\t\${Pre-Depends}\\t\${Depends}\\t\${Provides}\\n"
}
@test "the apt closure follows Provides (virtual packages) and alternatives" {
run skup trust su-dummy-w
assert_success
assert_output 'trusted su-dummy-w (+1 dependency: su-dummy-v)'
}
@test "the apt closure walks real chains through virtual packages (base-files -> awk -> mawk)" {
run skup trust base-files
assert_success
assert_output 'trusted base-files (+4 dependencies: gcc-12-base libc6 libgcc-s1 mawk)'
run cut -f2,3 "$STATE_FILE"
assert_line "mawk${T}dep:base-files"
assert_line "gcc-12-base${T}dep:base-files"
}
@test "a not-installed (rc) package is not part of a closure" {
run skup trust su-dummy-f
assert_success
assert_output 'trusted su-dummy-f'
}
@test "records are sorted on (backend, package); comments are kept in order" {
write_state '# my own note' \
"pacman${T}zsh${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}zsh${T}user${T}2026-01-01T00:00:00Z" \
'# second note' \
"apt${T}foo-bar${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}foo${T}dep:zsh${T}2026-01-01T00:00:00Z" \
"dnf${T}abc${T}user${T}2026-01-01T00:00:00Z"
run skup trust su-dummy-c
assert_success
run cat "$STATE_FILE"
assert_line --index 0 '# my own note'
assert_line --index 1 '# second note'
assert_line --index 2 "apt${T}foo${T}dep:zsh${T}2026-01-01T00:00:00Z"
assert_line --index 3 "apt${T}foo-bar${T}user${T}2026-01-01T00:00:00Z"
assert_line --index 4 --regexp "^apt${T}su-dummy-c${T}user${T}"
assert_line --index 5 "apt${T}zsh${T}user${T}2026-01-01T00:00:00Z"
assert_line --index 6 "dnf${T}abc${T}user${T}2026-01-01T00:00:00Z"
assert_line --index 7 "pacman${T}zsh${T}user${T}2026-01-01T00:00:00Z"
[[ ${#lines[@]} -eq 8 ]]
}
@test "duplicates are collapsed: a user row wins over a dep row, else the first one" {
write_state "$HEADER" \
"apt${T}b${T}dep:x${T}2026-01-01T00:00:00Z" \
"apt${T}b${T}user${T}2026-02-02T00:00:00Z" \
"apt${T}c${T}dep:x${T}2026-03-03T00:00:00Z" \
"apt${T}c${T}dep:y${T}2026-04-04T00:00:00Z"
run skup trust su-dummy-c
assert_success
run cat "$STATE_FILE"
assert_line "apt${T}b${T}user${T}2026-02-02T00:00:00Z"
assert_line "apt${T}c${T}dep:x${T}2026-03-03T00:00:00Z"
refute_line --partial 'dep:y'
refute_line --partial "b${T}dep:x"
}
@test "a user row is never downgraded to dep:" {
run skup trust su-dummy-b
run skup trust su-dummy-a
assert_success
assert_output 'trusted su-dummy-a (its installed dependency was already trusted)'
run cut -f2,3 "$STATE_FILE"
assert_line "su-dummy-b${T}user"
refute_line --partial 'dep:'
}
@test "an existing dep: row is upgraded to user, keeping one row" {
run skup trust su-dummy-a
run skup trust su-dummy-b
assert_success
assert_output 'trusted su-dummy-b'
run cut -f2,3 "$STATE_FILE"
assert_line "su-dummy-b${T}user"
[[ $(grep -c su-dummy-b "$STATE_FILE") -eq 1 ]]
}
@test "trusting an already trusted package reports it and changes nothing" {
run skup trust su-dummy-c
before=$(cat "$STATE_FILE")
run skup trust su-dummy-c
assert_success
assert_output 'su-dummy-c was already trusted'
[[ $(cat "$STATE_FILE") == "$before" ]]
}
@test "malformed lines are kept verbatim, with a warning" {
write_state "$HEADER" "apt${T}only-two" "apt${T}x${T}weird${T}2026-01-01T00:00:00Z"
run skup trust su-dummy-c
assert_success
assert_output --partial "trusted.tsv:2: malformed line kept verbatim: apt${T}only-two"
assert_output --partial "trusted.tsv:3: malformed line kept verbatim"
run cat "$STATE_FILE"
assert_line "apt${T}only-two"
assert_line "apt${T}x${T}weird${T}2026-01-01T00:00:00Z"
}
@test "blank lines and CRLF line ends are tolerated" {
printf '%s\r\n\r\n%s\r\n\n' "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" >"$BATS_TEST_TMPDIR/st"
mkdir -p "$STATE"
mv "$BATS_TEST_TMPDIR/st" "$STATE_FILE"
run skup list --no-refresh
assert_line '3 upgradable, 1 trusted (hidden, use --all to show), 2 to review:'
}
@test "writes are atomic (new file renamed over the old one) and leave no temp file" {
run skup trust su-dummy-c
ino1=$(stat -c %i "$STATE_FILE")
run skup trust su-dummy-a
assert_success
ino2=$(stat -c %i "$STATE_FILE")
[[ $ino1 != "$ino2" ]]
run ls -A "$STATE"
assert_output "$(printf '%s\n' trusted.lock trusted.tsv)"
}
@test "a failing write leaves the old file intact" {
run skup trust su-dummy-c
before=$(cat "$STATE_FILE")
# make the state dir read-only (root ignores directory modes: skip then)
chmod 0555 "$STATE"
if [[ -w $STATE ]]; then
chmod 0755 "$STATE"
skip "running as root: directory modes are not enforced"
fi
run skup trust su-dummy-a
chmod 0755 "$STATE"
assert_failure 1
[[ $(cat "$STATE_FILE") == "$before" ]]
}
@test "untrust removes the row for PKG and every dep:PKG row, nothing else" {
write_state "$HEADER" \
"apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}su-dummy-b${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z" \
"apt${T}x${T}dep:y${T}2026-01-01T00:00:00Z" \
"apt${T}su-dummy-c${T}user${T}2026-01-01T00:00:00Z" \
"pacman${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" \
"pacman${T}su-dummy-b${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z"
run skup untrust su-dummy-a
assert_success
assert_output 'untrusted su-dummy-a (2 rows removed)'
run cat "$STATE_FILE"
assert_output "$(printf '%s\n' "$HEADER" \
"apt${T}su-dummy-c${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}x${T}dep:y${T}2026-01-01T00:00:00Z" \
"pacman${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" \
"pacman${T}su-dummy-b${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z")"
}
@test "untrust of a dependency-only package removes its dep row" {
run skup trust su-dummy-a
run skup untrust su-dummy-b
assert_success
assert_output 'untrusted su-dummy-b (1 row removed)'
run cut -f2 "$STATE_FILE"
assert_line su-dummy-a
refute_line su-dummy-b
}
@test "untrust of a package that is not trusted warns, exit 0" {
run skup untrust nope
assert_success
assert_output --partial 'warning: nope is not trusted (no row for backend apt)'
[[ ! -e $STATE_FILE ]]
}
@test "refresh recomputes closures: adds missing rows, drops stale ones, keeps the rest" {
write_state "$HEADER" \
"apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}su-dummy-b${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z" \
"apt${T}stale${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z" \
"apt${T}orphan${T}dep:gone${T}2026-01-01T00:00:00Z" \
"apt${T}su-dummy-w${T}user${T}2026-01-01T00:00:00Z" \
"pacman${T}stale${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z"
run skup refresh
assert_success
assert_output 'refreshed the dependency closures of 2 trusted roots: 1 added, 2 removed'
run cat "$STATE_FILE"
assert_line "apt${T}su-dummy-b${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z"
assert_line --regexp "^apt${T}su-dummy-v${T}dep:su-dummy-w${T}${DATE_RE}\$"
refute_line --partial "apt${T}stale"
refute_line --partial 'orphan'
assert_line "pacman${T}stale${T}dep:su-dummy-a${T}2026-01-01T00:00:00Z"
assert_line "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
}
@test "refresh with no user roots for this backend does nothing" {
write_state "$HEADER" "pacman${T}x${T}user${T}2026-01-01T00:00:00Z"
run skup refresh
assert_success
assert_output 'No trusted root packages for backend apt: nothing to refresh.'
}
@test "trusted prints the statefile verbatim" {
write_state "$HEADER" '# note' "apt${T}x${T}user${T}2026-01-01T00:00:00Z"
run skup trusted
assert_success
assert_output "$(printf '%s\n' "$HEADER" '# note' "apt${T}x${T}user${T}2026-01-01T00:00:00Z")"
}
@test "trusted without a statefile says so, exit 0" {
run skup trusted
assert_success
assert_output "No trusted packages yet ($STATE_FILE does not exist)."
}
@test "only rows of the current backend hide packages" {
write_state "$HEADER" "pacman${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
run skup list --no-refresh
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
assert_line --regexp '^ \[1\] su-dummy-a '
}
@test "the reason does not matter for hiding: dep rows hide too" {
write_state "$HEADER" "apt${T}su-dummy-b${T}dep:whatever${T}2026-01-01T00:00:00Z"
run skup list --no-refresh
assert_line '3 upgradable, 1 trusted (hidden, use --all to show), 2 to review:'
refute_output --partial 'su-dummy-b'
}