Files
fdroid-repo-ci/test_fdroid_update.sh
T
claudeandbit 4a25e3681a Require FDROID_REPO_URL/FDROID_REPO_NAME so fdroid-update.sh cannot publish fdroidserver's placeholder
The published repo advertised fdroidserver's own demo values
(repo.address "https://MyFirstFDroidRepo.org/fdroid/repo", repo.name "My
First F-Droid Repo Demo") because fdroid-update.sh wrote config.yml itself
but never set repo_url/repo_name/repo_description, so fdroidserver fell
back to its template. The "Add to F-Droid" button on the generated
index.html is built from repo_url, which is how bit noticed. A publish
that succeeds while advertising the wrong URL is silently wrong -- worse
than a failed job.

Add FDROID_REPO_URL/FDROID_REPO_NAME/FDROID_REPO_DESCRIPTION environment
variables (not new positionals -- the script already has five plus
pass-through extra args). The first two are now a hard requirement, naming
the missing one; FDROID_REPO_DESCRIPTION stays optional. This is a breaking
change to a public composite action's interface, acceptable only because
weblib-viewer is the sole consumer and is updated in the same change
(weblib-viewer#26).

Also discovered while wiring this up and worth recording since it
contradicts the "no /fdroid/repo suffix" assumption in weblib's working
notes: fdroidserver's own common.py unconditionally refuses to run once
config.yml sets repo_url at all, unless it ends in the literal suffix
"/repo" -- there is no flag to disable that check, and the value is
embedded verbatim as repo.address. So FDROID_REPO_URL has to end in /repo,
and publish.sh's existing <prefix> argument has to put the published tree
under a URL path that actually ends in /repo to match, or fdroid update
refuses to run at all (checked positive) / the address points at a path
the bucket never serves (checked against the live fdroid-weblib-dev bucket
layout).

Extends test_fdroid_update.sh: config.yml carries the values, the resulting
index-v2.json has the right repo.address/name/description (not
fdroidserver's placeholder), a name with an embedded single quote survives
the YAML round-trip, and the script fails when FDROID_REPO_URL or
FDROID_REPO_NAME is absent or FDROID_REPO_URL lacks the /repo suffix.
run_tests.sh: 104 passed / 4 skipped (was 98/4).

Filed as fdroid-repo-ci#11.

Co-authored-by: bit <bit@das-labor.org>
2026-09-09 20:23:16 +00:00

135 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# End-to-end test of fdroid-update.sh: a throwaway index-signing keystore, a
# repo/ directory holding one already-signed fixture APK (from sign-apk.sh --
# binary-repo mode never builds or app-signs anything itself), a real
# `fdroid update` resolved through with-nixpkgs.sh/nixpkgs-pin.
#
# The fixture APK's AndroidManifest.xml is not real binary AXML -- building
# one needs aapt2, i.e. the Android SDK this repo deliberately avoids
# depending on. fdroidserver's own parser (androguard) rejects it and skips
# the app ("Failed to get APK information"), which is expected and asserted
# on below rather than hidden: what this test proves is the *indexing and
# index-signing* pipeline, which is what fdroid-update.sh exists for and
# what fdroid-repo-ci#1 asks to be verified -- "assert the index exists" --
# not that fdroidserver's manifest parser accepts a hand-built fixture.
set -euo pipefail
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source "$here/test_lib.sh"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
cd "$work"
mkdir -p repo
echo "generating throwaway index keystore..."
bash "$here/with-nixpkgs.sh" jdk21 keytool -genkeypair \
-keystore index.jks -storepass idxpass123 -keypass idxpass123 \
-alias indexkey -keyalg RSA -keysize 2048 -validity 1 \
-dname "CN=Index, OU=Test, O=Test, L=Test, S=Test, C=US" >/dev/null 2>&1
assert_file_exists "throwaway index keystore was created" index.jks
echo "signing a fixture APK with sign-apk.sh, to sit in repo/..."
bash "$here/with-nixpkgs.sh" jdk21 keytool -genkeypair \
-keystore app.jks -storepass apppass123 -keypass apppass123 \
-alias appkey -keyalg RSA -keysize 2048 -validity 1 \
-dname "CN=App, OU=Test, O=Test, L=Test, S=Test, C=US" >/dev/null 2>&1
bash "$here/with-nixpkgs.sh" python3 python3 - "$work" <<'PYEOF'
import struct, sys, zipfile
def add_stored(zf, name, data, align=4):
info = zipfile.ZipInfo(name)
info.compress_type = zipfile.ZIP_STORED
header_len = 30 + len(name.encode())
start = zf.fp.tell() + header_len
pad = (-start) % align
if pad:
if pad < 4:
pad += align
extra_len = pad - 4
info.extra = struct.pack("<HH", 0xD935, extra_len) + b"\x00" * extra_len
zf.writestr(info, data)
work = sys.argv[1]
with zipfile.ZipFile(f"{work}/unsigned.apk", "w") as zf:
add_stored(zf, "AndroidManifest.xml", b"0123456789")
PYEOF
export APP_KS_PASS=apppass123
bash "$here/sign-apk.sh" unsigned.apk repo/com.example.testapp_1.apk app.jks appkey APP_KS_PASS \
--min-sdk-version 21 --max-sdk-version 34 >/dev/null
assert_file_exists "fixture APK signed and placed in repo/" repo/com.example.testapp_1.apk
echo "--- fault: no repo/ directory is refused ---"
assert_failure "fdroid-update.sh refuses a root with no repo/" \
bash "$here/fdroid-update.sh" "$work/does-not-have-repo" index.jks indexkey IDX_KS_PASS
echo "--- fault: missing password env var is refused ---"
assert_failure "fdroid-update.sh refuses when the password env var is unset" \
env -u IDX_KS_PASS bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
export IDX_KS_PASS=idxpass123
echo "--- fault: missing FDROID_REPO_URL is refused (fdroid-repo-ci#11) ---"
assert_failure "fdroid-update.sh refuses when FDROID_REPO_URL is unset" \
env -u FDROID_REPO_URL FDROID_REPO_NAME="weblib" \
bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
echo "--- fault: missing FDROID_REPO_NAME is refused (fdroid-repo-ci#11) ---"
assert_failure "fdroid-update.sh refuses when FDROID_REPO_NAME is unset" \
env -u FDROID_REPO_NAME FDROID_REPO_URL="https://example.invalid" \
bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
echo "--- fault: FDROID_REPO_URL not ending in /repo is refused by fdroidserver itself ---"
assert_failure "fdroid-update.sh (fdroidserver) refuses a repo_url without the /repo suffix" \
env FDROID_REPO_URL="https://fdroid-repo-ci-test.example.invalid" \
FDROID_REPO_NAME="fdroid-repo-ci test repo" \
bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
echo "--- revert: a real run, needs no network ---"
export FDROID_REPO_URL="https://fdroid-repo-ci-test.example.invalid/repo"
export FDROID_REPO_NAME="fdroid-repo-ci test repo"
export FDROID_REPO_DESCRIPTION="a throwaway repo built by test_fdroid_update.sh"
assert_success "fdroid-update.sh runs fdroid update successfully" \
bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
assert_file_exists "index-v2.json exists and is non-empty" repo/index-v2.json
assert_file_exists "index-v1.jar (signed index) exists and is non-empty" repo/index-v1.jar
assert_file_exists "entry.jar (signed entry, apksigner v1) exists and is non-empty" repo/entry.jar
echo "--- index-v2.json is valid JSON, not just a non-empty file ---"
assert_success "index-v2.json parses as JSON" \
bash "$here/with-nixpkgs.sh" python3 python3 -c "import json; json.load(open('repo/index-v2.json'))"
echo "--- index-v2.json carries the repo metadata, not fdroidserver's placeholder ---"
assert_success "repo.address is FDROID_REPO_URL, not MyFirstFDroidRepo.org" \
bash "$here/with-nixpkgs.sh" python3 python3 -c "
import json
repo = json.load(open('repo/index-v2.json'))['repo']
assert repo['address'] == 'https://fdroid-repo-ci-test.example.invalid/repo', repo['address']
assert repo['name']['en-US'] == 'fdroid-repo-ci test repo', repo['name']
assert repo['description']['en-US'] == 'a throwaway repo built by test_fdroid_update.sh', repo['description']
assert 'MyFirstFDroidRepo' not in json.dumps(repo)
"
echo "--- index-v1.jar is a validly signed jar (its own signature, not apksigner's) ---"
assert_success "jarsigner -verify accepts the signed index" \
bash "$here/with-nixpkgs.sh" jdk21 jarsigner -verify repo/index-v1.jar
echo "--- config.yml never holds a literal password ---"
assert_failure "config.yml has no literal password (uses {env: ...} instead)" \
grep -q "idxpass123" "$work/config.yml"
echo "--- a value with an embedded single quote survives config.yml round-trip ---"
export FDROID_REPO_NAME="bit's test repo"
assert_success "fdroid-update.sh accepts a name with an embedded quote" \
bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS
assert_success "the quoted name round-trips through config.yml into index-v2.json" \
bash "$here/with-nixpkgs.sh" python3 python3 -c "
import json
repo = json.load(open('repo/index-v2.json'))['repo']
assert repo['name']['en-US'] == \"bit's test repo\", repo['name']
"
export FDROID_REPO_NAME="fdroid-repo-ci test repo"
report_and_exit