#!/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("/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" # --- FDROID_PREVIOUS_INDEX (weblib/weblib-viewer#95) --- # # The preservation itself is measured by test_preserve_added.py (unit) and, # end to end, against a copy of the live dev index -- not here: this suite's # fixture APK is deliberately not parseable by androguard (see the top of # this file), so `fdroid update` indexes zero packages and there is no # `added` date to preserve. What IS testable here is the part that runs # before fdroidserver is ever reached: the contract that a set-but-wrong # FDROID_PREVIOUS_INDEX aborts loudly rather than silently flattening every # already-published build's history on the next publish. echo "--- fault: FDROID_PREVIOUS_INDEX pointing at a file that does not exist is refused ---" assert_failure "fdroid-update.sh refuses a FDROID_PREVIOUS_INDEX that does not exist" \ env FDROID_PREVIOUS_INDEX="$work/never-fetched.json" \ bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS echo "--- fault: a truncated/garbage previous index is refused before fdroid update runs ---" printf '%s' '{"packages": {"de.example' > "$work/truncated.json" assert_failure "fdroid-update.sh refuses a previous index that is not valid JSON" \ env FDROID_PREVIOUS_INDEX="$work/truncated.json" \ bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS echo "--- fault: valid JSON that is not an index (e.g. an S3 error document) is refused ---" printf '%s' '{"error": "NoSuchKey"}' > "$work/notanindex.json" assert_failure "fdroid-update.sh refuses a previous index with no top-level packages" \ env FDROID_PREVIOUS_INDEX="$work/notanindex.json" \ bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS echo "--- a valid previous index is staged, survives the run, and is verified afterwards ---" # An empty-but-well-formed index is exactly the shape a channel's # first-ever publish would leave behind, and it proves the staged file does # not confuse `fdroid update` -- which overwrites it -- while still being # read back for the after-the-fact check. printf '%s' '{"repo": {}, "packages": {}}' > "$work/prev-empty.json" out="$work/prev-empty.log" if env FDROID_PREVIOUS_INDEX="$work/prev-empty.json" \ bash "$here/fdroid-update.sh" "$work" index.jks indexkey IDX_KS_PASS >"$out" 2>&1; then pass "fdroid-update.sh runs with FDROID_PREVIOUS_INDEX set" else fail "fdroid-update.sh runs with FDROID_PREVIOUS_INDEX set" cat "$out" >&2 fi assert_contains "the previous index was staged for fdroidserver to read" "$out" \ "fdroid update will carry the 'added' date of 0 known build(s)" assert_contains "the result was checked, not merely hoped for" "$out" \ "already-published build(s) kept their original 'added' timestamp" echo "--- and the staged copy never replaces the freshly generated index ---" assert_success "index-v2.json after the run is the new one, not the staged previous one" \ bash "$here/with-nixpkgs.sh" python3 python3 -c " import json index = json.load(open('repo/index-v2.json')) assert index['repo']['address'] == 'https://fdroid-repo-ci-test.example.invalid/repo', index['repo'] " report_and_exit