diff --git a/android/app/src/androidTest/java/org/whoneedhelp/mobile/CrossClientStagingInstrumentedTest.java b/android/app/src/androidTest/java/org/whoneedhelp/mobile/CrossClientStagingInstrumentedTest.java index f31f122..683266b 100644 --- a/android/app/src/androidTest/java/org/whoneedhelp/mobile/CrossClientStagingInstrumentedTest.java +++ b/android/app/src/androidTest/java/org/whoneedhelp/mobile/CrossClientStagingInstrumentedTest.java @@ -48,8 +48,8 @@ public final class CrossClientStagingInstrumentedTest { waitForElement("remember-login-button"); click("remember-login-button"); waitForElementText( - "main-content", - "Help can be closer than you think." + "home-title", + "Need help nearby? Ask the community." ); } diff --git a/scripts/android-browser-staging-e2e.sh b/scripts/android-browser-staging-e2e.sh index 6dacb1b..0f570c8 100755 --- a/scripts/android-browser-staging-e2e.sh +++ b/scripts/android-browser-staging-e2e.sh @@ -41,6 +41,12 @@ browser_image="who-need-help-e2e-tests:android-$run_id" android_image="who-need-help-android:$variant-cross-client-$run_id" container="who-need-help-android-$variant-cross-client-$run_id" avd_volume="who-need-help-android-avd-$variant-cross-client-$run_id" +# Android's Automated Test Device image removes applications and background +# services that are unrelated to app-level automation. Keep the API 37/16 KB +# compatibility coverage in the instrumentation matrix and public smoke test; +# use the Google ATD image for this resource-intensive two-client workflow. +android_api=35 +android_system_image=system-images/android-35/google_atd/x86_64 service_class=org.whoneedhelp.mobile.TrackingService fixture_password="$(openssl rand -hex 24)" android_message="android-$run_id" @@ -123,6 +129,10 @@ if [[ ! -s "$TEST_APK" ]]; then exit 1 fi +"$ROOT/scripts/verify-android-artifact-source.sh" \ + "$ROOT/android/dist-$variant" \ + "$build_script" + set -a # shellcheck source=/dev/null . "$ENV_FILE" @@ -645,8 +655,7 @@ docker build \ --build-arg "WNH_DEBUG_BASE_URL=$WNH_BASE_URL" \ --build-arg "WNH_TRACKING_MIN_TIME_MS=$WNH_TRACKING_MIN_TIME_MS" \ --build-arg "WNH_TRACKING_HTTP_TIMEOUT_MS=$WNH_TRACKING_HTTP_TIMEOUT_MS" \ - --build-arg \ - "ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.0/google_apis_ps16k/x86_64" \ + --build-arg "ANDROID_EMULATOR_SYSTEM_IMAGE=$android_system_image" \ --target emulator \ --tag "$android_image" \ android >"$output_dir/android-image-build.log" @@ -808,7 +817,8 @@ fi { printf 'run_id=%s\n' "$run_id" - printf 'android_api=37.0\n' + printf 'android_api=%s\n' "$android_api" + printf 'android_system_image=%s\n' "$android_system_image" printf 'package=%s\n' "$package" printf 'public_origin=%s\n' "$WNH_BASE_URL" printf 'magic_link_login=true\n' diff --git a/scripts/android-development-build.sh b/scripts/android-development-build.sh index 722ad82..d257d70 100755 --- a/scripts/android-development-build.sh +++ b/scripts/android-development-build.sh @@ -58,6 +58,9 @@ docker build \ --output "type=local,dest=$ROOT/android/dist-development" \ "$ROOT/android" +"$ROOT/scripts/android-source-fingerprint.sh" \ + >"$ROOT/android/dist-development/source-fingerprint.sha256" + "$ROOT/scripts/android-app-links-verify.sh" \ "$ENV_FILE" \ "$ROOT/android/dist-development" \ diff --git a/scripts/android-release-build.sh b/scripts/android-release-build.sh index 1d0d130..5ec9dff 100755 --- a/scripts/android-release-build.sh +++ b/scripts/android-release-build.sh @@ -72,6 +72,9 @@ docker build \ --output "type=local,dest=$OUTPUT_DIR" \ "$ROOT/android" +"$ROOT/scripts/android-source-fingerprint.sh" \ + >"$OUTPUT_DIR/source-fingerprint.sha256" + for artifact in \ "$OUTPUT_DIR/who-need-help-release.apk" \ "$OUTPUT_DIR/who-need-help-release.aab" \ @@ -79,6 +82,7 @@ for artifact in \ "$OUTPUT_DIR/package-name.txt" \ "$OUTPUT_DIR/bundle-signing-verification.txt" \ "$OUTPUT_DIR/bundletool-validation.txt" \ + "$OUTPUT_DIR/source-fingerprint.sha256" \ "$OUTPUT_DIR/lint-results-release.html"; do if [ ! -s "$artifact" ]; then echo "Android release build did not export the expected artifact: $artifact" >&2 diff --git a/scripts/android-source-fingerprint.sh b/scripts/android-source-fingerprint.sh new file mode 100755 index 0000000..cd75a13 --- /dev/null +++ b/scripts/android-source-fingerprint.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) + +mapfile -d '' -t source_files < <( + git -C "$ROOT" ls-files \ + --cached \ + --others \ + --exclude-standard \ + -z \ + -- android | + LC_ALL=C sort -z +) + +if [[ "${#source_files[@]}" -eq 0 ]]; then + echo "No Android source files were found." >&2 + exit 1 +fi + +{ + for relative_path in "${source_files[@]}"; do + absolute_path="$ROOT/$relative_path" + + if [[ ! -f "$absolute_path" ]]; then + echo "Android source input is not a regular file: $relative_path" >&2 + exit 1 + fi + + printf '%s\0' "$relative_path" + sha256sum "$absolute_path" | awk '{print $1}' + done +} | sha256sum | awk '{print $1}' diff --git a/scripts/android-staging-build.sh b/scripts/android-staging-build.sh index faf86e6..4001e56 100755 --- a/scripts/android-staging-build.sh +++ b/scripts/android-staging-build.sh @@ -59,6 +59,9 @@ docker build \ --output "type=local,dest=$ROOT/android/dist-staging" \ "$ROOT/android" +"$ROOT/scripts/android-source-fingerprint.sh" \ + >"$ROOT/android/dist-staging/source-fingerprint.sha256" + "$ROOT/scripts/android-app-links-verify.sh" \ "$ENV_FILE" \ "$ROOT/android/dist-staging" \ diff --git a/scripts/android-staging-smoke.sh b/scripts/android-staging-smoke.sh index d9db9ce..f32210e 100755 --- a/scripts/android-staging-smoke.sh +++ b/scripts/android-staging-smoke.sh @@ -57,6 +57,10 @@ if [ ! -s "$TEST_APK" ]; then exit 1 fi +"$ROOT/scripts/verify-android-artifact-source.sh" \ + "$ROOT/android/dist-$variant" \ + "$build_script" + set -a # shellcheck source=/dev/null . "$ENV_FILE" diff --git a/scripts/quality.sh b/scripts/quality.sh index 428111e..39b498d 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -30,6 +30,7 @@ socket_proxy_container="wnh-socket-proxy-audit-$run_id" scan_dir=$(mktemp -d "${TMPDIR:-/tmp}/wnh-quality-scan.XXXXXX") scan_list="${scan_dir}.files" scan_tar="${scan_dir}.tar" +android_fingerprint_probe="$ROOT/android/.quality-source-fingerprint-$run_id" umask 077 QUALITY_POSTGRES_USER="wnh_quality_$(openssl rand -hex 6)" @@ -49,6 +50,7 @@ cleanup() { "$boundary_mock_image" "$socket_proxy_image" "$postgis_image" \ "$caddy_image" "$traefik_image" \ >/dev/null 2>&1 || true + rm -f "$android_fingerprint_probe" rm -rf "$scan_dir" "$scan_list" "$scan_tar" } trap cleanup EXIT HUP INT TERM @@ -97,6 +99,47 @@ grep -Fx '/.runner' .dockerignore >/dev/null grep -Fx '/act_runner' .dockerignore >/dev/null grep -Fx '/act_runner-data/' .dockerignore >/dev/null +echo "Checking Android artifacts are bound to their exact source tree" +android_fingerprint_before=$(./scripts/android-source-fingerprint.sh) +android_artifact_probe="$scan_dir/android-artifact" +mkdir "$android_artifact_probe" +printf '%s\n' "$android_fingerprint_before" \ + >"$android_artifact_probe/source-fingerprint.sha256" +./scripts/verify-android-artifact-source.sh \ + "$android_artifact_probe" \ + scripts/android-development-build.sh >/dev/null + +printf '%s\n' 'quality source mutation' >"$android_fingerprint_probe" +android_fingerprint_after=$(./scripts/android-source-fingerprint.sh) +if [ "$android_fingerprint_before" = "$android_fingerprint_after" ]; then + echo "Android source fingerprint did not change for a source mutation." >&2 + exit 1 +fi +if ./scripts/verify-android-artifact-source.sh \ + "$android_artifact_probe" \ + scripts/android-development-build.sh >/dev/null 2>&1; then + echo "Android artifact verifier accepted stale source inputs." >&2 + exit 1 +fi + +rm -f "$android_fingerprint_probe" +test "$(./scripts/android-source-fingerprint.sh)" = "$android_fingerprint_before" +printf '%s\n' malformed >"$android_artifact_probe/source-fingerprint.sha256" +if ./scripts/verify-android-artifact-source.sh \ + "$android_artifact_probe" \ + scripts/android-development-build.sh >/dev/null 2>&1; then + echo "Android artifact verifier accepted a malformed fingerprint." >&2 + exit 1 +fi +rm -f "$android_artifact_probe/source-fingerprint.sha256" +if ./scripts/verify-android-artifact-source.sh \ + "$android_artifact_probe" \ + scripts/android-development-build.sh >/dev/null 2>&1; then + echo "Android artifact verifier accepted a missing fingerprint." >&2 + exit 1 +fi +unset android_fingerprint_before android_fingerprint_after + echo "Checking atomic environment credential imports" credential_env="$scan_dir/credentials.env" cp .env.example "$credential_env" diff --git a/scripts/verify-android-artifact-source.sh b/scripts/verify-android-artifact-source.sh new file mode 100755 index 0000000..49c8147 --- /dev/null +++ b/scripts/verify-android-artifact-source.sh @@ -0,0 +1,40 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +artifact_dir=${1:-} +build_command=${2:-the matching Android build script} + +if [ -z "$artifact_dir" ]; then + echo "usage: $0 ARTIFACT_DIR [BUILD_COMMAND]" >&2 + exit 2 +fi + +case "$artifact_dir" in + /*) ;; + *) artifact_dir="$ROOT/$artifact_dir" ;; +esac + +fingerprint_file="$artifact_dir/source-fingerprint.sha256" + +if [ ! -s "$fingerprint_file" ]; then + echo "Android artifacts do not record their source fingerprint: $fingerprint_file" >&2 + echo "Run $build_command before using these artifacts." >&2 + exit 1 +fi + +expected=$(tr -d '\r\n' <"$fingerprint_file") +actual=$("$ROOT/scripts/android-source-fingerprint.sh") + +if ! printf '%s\n' "$expected" | grep -Eq '^[0-9a-f]{64}$'; then + echo "Android artifact source fingerprint is malformed." >&2 + exit 1 +fi + +if [ "$expected" != "$actual" ]; then + echo "Android artifacts are stale relative to the current Android source." >&2 + echo "Run $build_command before smoke or cross-client testing." >&2 + exit 1 +fi + +printf 'Verified Android artifact source fingerprint: %s\n' "$actual"