Skip to content

Commit dc93504

Browse files
committed
Add per-module bats coverage for apl-feed CLI helpers
Adds five test files covering scripts/apl-feed/{common,http,backup,id,status}.sh in isolation. End-to-end coverage stays in test_apl_feed_cli.bats; the new files exercise individual helpers so a regression in canonicalize_uuid or feed_env_get fails locally rather than five layers up. Tests assert current behavior. They surface two cross-script drifts (image-install marker not honored by feed_env_paths; mlat_disabled_by_config does not recognize USER=disable) for follow-up — production code is unchanged.
1 parent 0ac226c commit dc93504

5 files changed

Lines changed: 1844 additions & 0 deletions

File tree

test/test_apl_feed_backup.bats

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/env bats
2+
3+
# Per-module unit tests for scripts/apl-feed/backup.sh.
4+
#
5+
# Restore round-trips, --check, --uuid, rollback on secret-write
6+
# failure, and --dry-run rejection are covered end-to-end in
7+
# test_apl_feed_cli.bats — not duplicated here. This file isolates
8+
# read_backup_file (validation matrix) and config_backup (write
9+
# semantics + edge cases).
10+
11+
setup() {
12+
LIB_DIR="$BATS_TEST_DIRNAME/../scripts/apl-feed"
13+
ROOT_DIR="$(mktemp -d)"
14+
TMPDIR="$ROOT_DIR/tmp"
15+
mkdir -p "$TMPDIR" \
16+
"$ROOT_DIR/etc/airplanes" \
17+
"$ROOT_DIR/usr/local/share/airplanes" \
18+
"$ROOT_DIR/boot"
19+
export TMPDIR
20+
APL_FEED_SECRET_OWNER="$(id -un)"
21+
APL_FEED_SECRET_GROUP="$(id -gn)"
22+
export APL_FEED_SECRET_OWNER APL_FEED_SECRET_GROUP
23+
24+
bats_exit_trap="$(trap -p EXIT)"
25+
# shellcheck source=../scripts/apl-feed/common.sh
26+
source "$LIB_DIR/common.sh"
27+
# shellcheck source=../scripts/apl-feed/backup.sh
28+
source "$LIB_DIR/backup.sh"
29+
eval "$bats_exit_trap"
30+
ROOT="$ROOT_DIR"
31+
32+
# Most config_backup cases need a valid local UUID + secret.
33+
UUID='11111111-2222-3333-4444-555555555555'
34+
SECRET='ABCDEFGHIJKLMNOP'
35+
printf '%s\n' "$UUID" > "$ROOT_DIR/etc/airplanes/feeder-id"
36+
printf '%s\n' "$SECRET" > "$ROOT_DIR/etc/airplanes/feeder-claim-secret"
37+
chmod 0640 "$ROOT_DIR/etc/airplanes/feeder-claim-secret"
38+
}
39+
40+
teardown() {
41+
rm -rf "$ROOT_DIR"
42+
}
43+
44+
write_backup() {
45+
# write_backup <path> <feeder_uuid|null> <secret|null> <version|null>
46+
local path="$1" uuid="$2" secret="$3" version="$4"
47+
jq -n \
48+
--arg uuid "$uuid" \
49+
--arg secret "$secret" \
50+
--arg version "$version" \
51+
'{schema_version:1, created_at:"2026-04-28T00:00:00Z", feeder_uuid:$uuid, claim:{secret:$secret, version:($version | if . == "null" then null elif . == "" then empty else tonumber end)}}' \
52+
> "$path"
53+
}
54+
55+
# --- read_backup_file ---
56+
57+
@test "read_backup_file: schema_version != 1 dies" {
58+
jq -n '{schema_version:2, feeder_uuid:"11111111-2222-3333-4444-555555555555", claim:{secret:"ABCDEFGHIJKLMNOP"}}' > "$ROOT_DIR/bad.json"
59+
run bash -c "
60+
set -euo pipefail
61+
source '$LIB_DIR/common.sh'
62+
source '$LIB_DIR/backup.sh'
63+
read_backup_file '$ROOT_DIR/bad.json'
64+
"
65+
[ "$status" -ne 0 ]
66+
[[ "$output" == *'unsupported backup schema_version'* ]]
67+
}
68+
69+
@test "read_backup_file: missing schema_version dies" {
70+
jq -n '{feeder_uuid:"11111111-2222-3333-4444-555555555555", claim:{secret:"ABCDEFGHIJKLMNOP"}}' > "$ROOT_DIR/bad.json"
71+
run bash -c "
72+
set -euo pipefail
73+
source '$LIB_DIR/common.sh'
74+
source '$LIB_DIR/backup.sh'
75+
read_backup_file '$ROOT_DIR/bad.json'
76+
"
77+
[ "$status" -ne 0 ]
78+
[[ "$output" == *'unsupported backup schema_version'* ]]
79+
}
80+
81+
@test "read_backup_file: malformed JSON dies under strict mode" {
82+
printf 'not-json\n' > "$ROOT_DIR/bad.json"
83+
run bash -c "
84+
set -euo pipefail
85+
source '$LIB_DIR/common.sh'
86+
source '$LIB_DIR/backup.sh'
87+
read_backup_file '$ROOT_DIR/bad.json'
88+
"
89+
[ "$status" -ne 0 ]
90+
}
91+
92+
@test "read_backup_file: malformed feeder_uuid dies" {
93+
write_backup "$ROOT_DIR/bad.json" 'not-a-uuid' 'ABCDEFGHIJKLMNOP' 'null'
94+
run bash -c "
95+
set -euo pipefail
96+
source '$LIB_DIR/common.sh'
97+
source '$LIB_DIR/backup.sh'
98+
read_backup_file '$ROOT_DIR/bad.json'
99+
"
100+
[ "$status" -ne 0 ]
101+
[[ "$output" == *'invalid feeder_uuid'* ]]
102+
}
103+
104+
@test "read_backup_file: malformed claim.secret dies" {
105+
write_backup "$ROOT_DIR/bad.json" '11111111-2222-3333-4444-555555555555' 'too-short' 'null'
106+
run bash -c "
107+
set -euo pipefail
108+
source '$LIB_DIR/common.sh'
109+
source '$LIB_DIR/backup.sh'
110+
read_backup_file '$ROOT_DIR/bad.json'
111+
"
112+
[ "$status" -ne 0 ]
113+
[[ "$output" == *'invalid claim.secret'* ]]
114+
}
115+
116+
@test "read_backup_file: claim.version=null permitted, BACKUP_VERSION empty" {
117+
write_backup "$ROOT_DIR/ok.json" '11111111-2222-3333-4444-555555555555' 'ABCDEFGHIJKLMNOP' 'null'
118+
read_backup_file "$ROOT_DIR/ok.json"
119+
[ "$BACKUP_UUID" = '11111111-2222-3333-4444-555555555555' ]
120+
[ "$BACKUP_SECRET" = 'ABCDEFGHIJKLMNOP' ]
121+
[ -z "$BACKUP_VERSION" ]
122+
}
123+
124+
@test "read_backup_file: missing claim.version key permitted, BACKUP_VERSION empty" {
125+
jq -n '{schema_version:1, created_at:"2026-04-28T00:00:00Z", feeder_uuid:"11111111-2222-3333-4444-555555555555", claim:{secret:"ABCDEFGHIJKLMNOP"}}' > "$ROOT_DIR/ok.json"
126+
read_backup_file "$ROOT_DIR/ok.json"
127+
[ "$BACKUP_UUID" = '11111111-2222-3333-4444-555555555555' ]
128+
[ "$BACKUP_SECRET" = 'ABCDEFGHIJKLMNOP' ]
129+
[ -z "$BACKUP_VERSION" ]
130+
}
131+
132+
@test "read_backup_file: valid integer claim.version populates BACKUP_VERSION" {
133+
write_backup "$ROOT_DIR/ok.json" '11111111-2222-3333-4444-555555555555' 'ABCDEFGHIJKLMNOP' '7'
134+
read_backup_file "$ROOT_DIR/ok.json"
135+
[ "$BACKUP_VERSION" = '7' ]
136+
}
137+
138+
# --- config_backup ---
139+
140+
@test "config_backup: refuses pre-existing output file" {
141+
: > "$ROOT_DIR/out.json"
142+
run bash -c "
143+
set -euo pipefail
144+
source '$LIB_DIR/common.sh'
145+
source '$LIB_DIR/backup.sh'
146+
ROOT='$ROOT_DIR'
147+
config_backup '$ROOT_DIR/out.json'
148+
"
149+
[ "$status" -ne 0 ]
150+
[[ "$output" == *'already exists'* ]]
151+
}
152+
153+
@test "config_backup: refuses --force flag" {
154+
run bash -c "
155+
set -euo pipefail
156+
source '$LIB_DIR/common.sh'
157+
source '$LIB_DIR/backup.sh'
158+
ROOT='$ROOT_DIR'
159+
config_backup --force '$ROOT_DIR/out.json'
160+
"
161+
[ "$status" -ne 0 ]
162+
[[ "$output" == *'unknown flag for backup: --force'* ]]
163+
}
164+
165+
@test "config_backup: writes 0600 file" {
166+
config_backup "$ROOT_DIR/out.json" >/dev/null
167+
perms="$(stat -c '%a' "$ROOT_DIR/out.json" 2>/dev/null || stat -f '%Lp' "$ROOT_DIR/out.json")"
168+
[ "$perms" = '600' ]
169+
}
170+
171+
@test "config_backup: version=null when no version file present" {
172+
config_backup "$ROOT_DIR/out.json" >/dev/null
173+
run jq -r '.claim.version' "$ROOT_DIR/out.json"
174+
[ "$status" -eq 0 ]
175+
[ "$output" = 'null' ]
176+
}
177+
178+
@test "config_backup: integer version when version file present" {
179+
printf '7\n' > "$ROOT_DIR/etc/airplanes/feeder-claim-secret.version"
180+
config_backup "$ROOT_DIR/out.json" >/dev/null
181+
run jq -r '.claim.version' "$ROOT_DIR/out.json"
182+
[ "$output" = '7' ]
183+
}
184+
185+
@test "config_backup: schema_version, feeder_uuid, secret round-trip via jq" {
186+
config_backup "$ROOT_DIR/out.json" >/dev/null
187+
[ "$(jq -r '.schema_version' "$ROOT_DIR/out.json")" = '1' ]
188+
[ "$(jq -r '.feeder_uuid' "$ROOT_DIR/out.json")" = "$UUID" ]
189+
[ "$(jq -r '.claim.secret' "$ROOT_DIR/out.json")" = "$SECRET" ]
190+
}
191+
192+
@test "config_backup: created_at is ISO 8601 UTC" {
193+
config_backup "$ROOT_DIR/out.json" >/dev/null
194+
created="$(jq -r '.created_at' "$ROOT_DIR/out.json")"
195+
[[ "$created" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]
196+
}
197+
198+
@test "config_backup: missing UUID file dies" {
199+
rm -f "$ROOT_DIR/etc/airplanes/feeder-id"
200+
run bash -c "
201+
set -euo pipefail
202+
source '$LIB_DIR/common.sh'
203+
source '$LIB_DIR/backup.sh'
204+
ROOT='$ROOT_DIR'
205+
config_backup '$ROOT_DIR/out.json'
206+
"
207+
[ "$status" -ne 0 ]
208+
[[ "$output" == *'no Feeder ID file'* ]]
209+
}
210+
211+
@test "config_backup: invalid UUID at primary path dies" {
212+
printf 'not-a-uuid\n' > "$ROOT_DIR/etc/airplanes/feeder-id"
213+
run bash -c "
214+
set -euo pipefail
215+
source '$LIB_DIR/common.sh'
216+
source '$LIB_DIR/backup.sh'
217+
ROOT='$ROOT_DIR'
218+
config_backup '$ROOT_DIR/out.json'
219+
"
220+
[ "$status" -ne 0 ]
221+
[[ "$output" == *'invalid Feeder ID format'* ]]
222+
}
223+
224+
@test "config_backup: missing existing secret file dies" {
225+
rm -f "$ROOT_DIR/etc/airplanes/feeder-claim-secret"
226+
run bash -c "
227+
set -euo pipefail
228+
source '$LIB_DIR/common.sh'
229+
source '$LIB_DIR/backup.sh'
230+
ROOT='$ROOT_DIR'
231+
config_backup '$ROOT_DIR/out.json'
232+
"
233+
[ "$status" -ne 0 ]
234+
}
235+
236+
@test "config_backup: malformed existing secret dies" {
237+
printf 'too-short\n' > "$ROOT_DIR/etc/airplanes/feeder-claim-secret"
238+
run bash -c "
239+
set -euo pipefail
240+
source '$LIB_DIR/common.sh'
241+
source '$LIB_DIR/backup.sh'
242+
ROOT='$ROOT_DIR'
243+
config_backup '$ROOT_DIR/out.json'
244+
"
245+
[ "$status" -ne 0 ]
246+
[[ "$output" == *'invalid secret format'* ]]
247+
}
248+
249+
@test "config_backup: requires a file argument" {
250+
run bash -c "
251+
set -euo pipefail
252+
source '$LIB_DIR/common.sh'
253+
source '$LIB_DIR/backup.sh'
254+
ROOT='$ROOT_DIR'
255+
config_backup
256+
"
257+
[ "$status" -ne 0 ]
258+
[[ "$output" == *'backup requires a file'* ]]
259+
}
260+
261+
@test "config_backup: rejects more than one file argument" {
262+
run bash -c "
263+
set -euo pipefail
264+
source '$LIB_DIR/common.sh'
265+
source '$LIB_DIR/backup.sh'
266+
ROOT='$ROOT_DIR'
267+
config_backup '$ROOT_DIR/a.json' '$ROOT_DIR/b.json'
268+
"
269+
[ "$status" -ne 0 ]
270+
[[ "$output" == *'exactly one file'* ]]
271+
}

0 commit comments

Comments
 (0)