1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
#!/bin/bash
#
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# The one runnable check for userctl. busctl, kwriteconfig6, git and gpg are
# stubs on PATH that log their arguments, so nothing here reaches the real
# AccountsService, emaildefaults or git config. jq is the real one.
#
# Usage: ./test-userctl.sh (exit 0 = all passed)
set -u
here="$(cd "$(dirname "$0")" && pwd)"
ctl="$here/userctl"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir "$tmp/bin"
cat > "$tmp/bin/stub" <<'EOF'
#!/bin/bash
n="${0##*/}"
{ printf '%s' "$n"; printf ' [%s]' "$@"; echo; } >> "$LOG"
[[ "${FAIL:-}" == "$n" ]] && { echo boom >&2; exit 1; }
case "$n" in
busctl) [[ " $* " == *" get-property "* ]] && printf '{"type":"s","data":"%s"}\n' "${@: -1}" ;;
git) [[ " $* " == *" --get "* ]] && { [[ -n "${GIT_VALUE:-}" ]] && echo "$GIT_VALUE" || exit 1; } ;;
gpg) cat "$GPG_FIXTURE" ;;
esac
exit 0
EOF
chmod +x "$tmp/bin/stub"
for n in busctl kwriteconfig6 git gpg; do ln -s stub "$tmp/bin/$n"; done
export PATH="$tmp/bin:$PATH" HOME="$tmp" LOG="$tmp/log"
obj="/org/freedesktop/Accounts/User$(id -u)"
acc="busctl [--system] [call] [--] [org.freedesktop.Accounts] [$obj] [org.freedesktop.Accounts.User]"
pass=0
fail=0
check() {
local label="$1" want="$2" got="$3"
if [[ "$want" == "$got" ]]; then
printf 'ok %s\n' "$label"; pass=$((pass + 1))
else
printf 'FAIL %s:\n want %q\n got %q\n' "$label" "$want" "$got"; fail=$((fail + 1))
fi
}
reset() { : > "$LOG"; unset FAIL GIT_VALUE; }
# get: one line per field, AccountsService through --json, the key from git.
reset
export GIT_VALUE=ABCDEF
check "get" "name=RealName
email=Email
avatar=IconFile
language=Language
location=Location
signingkey=ABCDEF" "$("$ctl" get)"
# keys: the primary fingerprint and first uid of each secret key; subkey
# fingerprints and later uids are skipped.
reset
cat > "$tmp/gpg.txt" <<'EOF'
sec:u:255:22:AAAA1111:1700000000:::u:::scESC:::+:::23::0:
fpr:::::::::1111222233334444555566667777AAAA1111:
grp:::::::::0000:
uid:r::::1700000000::HASH::Old Name <old@example.org>::::::::::0:
uid:u::::1700000000::HASH::Jane Doèr <jane@example.org>::::::::::0:
uid:u::::1700000000::HASH::Jane D. <jd@example.org>::::::::::0:
ssb:u:255:18:SUB1:1700000000::::::e:::+:::23:
fpr:::::::::9999888877776666555544443333SUB1:
sec:r:255:22:DEAD0000:1700000000:::u:::scESC:::#:::23::0:
fpr:::::::::DEAD0000DEAD0000DEAD0000DEAD00000000:
uid:r::::1700000000::HASH::Revoked Key <rev@example.org>::::::::::0:
sec:u:255:22:BBBB2222:1700000000:::u:::scESC:::#:::23::0:
fpr:::::::::AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222:
uid:u::::1700000000::HASH::Work Key <work@example.org>::::::::::0:
EOF
export GPG_FIXTURE="$tmp/gpg.txt"
check "keys" "1111222233334444555566667777AAAA1111 Jane Doèr <jane@example.org>
AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222 Work Key <work@example.org>" "$("$ctl" keys)"
# set name: AccountsService, emaildefaults, git with the surname shortened.
reset
out="$("$ctl" set name "Jane Doèr")"
check "set name prints nothing" "" "$out"
check "set name writes" "$acc [SetRealName] [s] [Jane Doèr]
kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [FullName] [--] [Jane Doèr]
git [config] [--global] [user.name] [Jane D.]" "$(cat "$LOG")"
gitname() { reset; "$ctl" set name "$1" >/dev/null; grep '^git' "$LOG"; }
check "three words" "git [config] [--global] [user.name] [Anna Maria R.]" "$(gitname "Anna Maria Rossi")"
check "one word" "git [config] [--global] [user.name] [Cher]" "$(gitname "Cher")"
check "accented initial" "git [config] [--global] [user.name] [Ève À.]" "$(gitname "Ève Àlba")"
check "extra spaces" "git [config] [--global] [user.name] [Jane D.]" "$(gitname " Jane Doèr ")"
# set email: the same three stores.
reset
"$ctl" set email jane@example.org >/dev/null
check "set email writes" "$acc [SetEmail] [s] [jane@example.org]
kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [EmailAddress] [--] [jane@example.org]
git [config] [--global] [user.email] [jane@example.org]" "$(cat "$LOG")"
# AccountsService only.
for pair in avatar:SetIconFile language:SetLanguage location:SetLocation; do
reset
"$ctl" set "${pair%%:*}" v >/dev/null
check "set ${pair%%:*} writes" "$acc [${pair#*:}] [s] [v]" "$(cat "$LOG")"
done
# signingkey: a fingerprint is written; empty unsets, but only if one is set.
reset
"$ctl" set signingkey ABCD >/dev/null
check "set signingkey" "git [config] [--global] [user.signingkey] [ABCD]" "$(cat "$LOG")"
reset
export GIT_VALUE=OLD
"$ctl" set signingkey "" >/dev/null
check "unset signingkey" "git [config] [--global] [--get] [user.signingkey]
git [config] [--global] [--unset-all] [user.signingkey]" "$(cat "$LOG")"
reset
"$ctl" set signingkey "" >/dev/null
check "unset when none is set" "git [config] [--global] [--get] [user.signingkey]" "$(cat "$LOG")"
# A leading dash is a value, not an option.
reset
"$ctl" set location -x >/dev/null
check "dash value" "$acc [SetLocation] [s] [-x]" "$(cat "$LOG")"
# A failing read: one error line, exit 1, no key=value lines.
reset
export FAIL=busctl
out="$("$ctl" get)"; rc=$?
check "get failure exit" "1" "$rc"
check "get failure output" "error=boom" "$out"
# A failing write stops there, says why on stdout, and exits 1.
reset
export FAIL=busctl
out="$("$ctl" set name "Jane Doèr")"; rc=$?
check "failure exit" "1" "$rc"
check "failure message" "busctl: boom" "$out"
check "failure stops" "1" "$(wc -l < "$LOG" | tr -d ' ')"
# An unknown field is refused.
reset
"$ctl" set shoesize 42 >/dev/null 2>&1; rc=$?
check "unknown field" "2" "$rc"
printf '\n%d passed, %d failed\n' "$pass" "$fail"
[[ $fail -eq 0 ]]
|