Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
21a4c22
Port mutators and message signing to Volley for 3.5.4
Apr 3, 2026
9ec1cfe
Align Volley initialization behavior with other service layers
Apr 3, 2026
2618d3f
Add empty service layer iniitialization option to match requirements …
Apr 13, 2026
a86484d
chore: integrate mini-sdk testing and update github workflows
Apr 13, 2026
b3549c5
fix: move core testing repo checkout into workspace to prevent github…
Apr 13, 2026
e8a7562
fix: migrate to okhttp sibling checkout pattern with PAT
Apr 13, 2026
cacbbf4
Add github workflow for build and test
Apr 13, 2026
bdaf78f
chore: align documentation and interface with okhttp for empty config
Apr 13, 2026
6af621e
ci: bump actions to support Node 24
Apr 13, 2026
1c8d639
fix(mutator): decouple mutator logic from legacy network fail variable
Apr 13, 2026
8266cb0
docs: update changelog for mutator decoupling
Apr 13, 2026
5b58e0d
docs: mark setProceedOnNetworkFail as obsolete
Apr 13, 2026
f2eea0c
test: remove obsolete reflection call targeting decoupled field
Apr 13, 2026
6c5c5d6
build: conditionally include test framework paths
Apr 13, 2026
6790e33
fix(sfv): return new item instance with params to prevent parameter d…
Apr 13, 2026
756e31b
fix(volley): getBaseHttpStack returns null when Approov is disabled t…
Apr 13, 2026
8354815
fix(sig): correct loop comparison in containsComponentIdentifier
Apr 13, 2026
2b4ed4b
fix(volley): make createConnection safe for HTTP and bypass pinning i…
Apr 13, 2026
4d7996f
fix(log): correct typo in already initialized warning message
Apr 13, 2026
49ea9f3
docs(volley): clarify mutator fallback behavior in javadoc
Apr 13, 2026
f8e2ac8
fix(volley): remove REJECTED from fallback header allowlist to preven…
Apr 13, 2026
b2d020f
Fix ehaviour on header substitution and adapt tests; complete testing…
Apr 14, 2026
0f6fe1b
Fix Copilot review comments and normalize token headers
Apr 14, 2026
be840ac
Make test dependencies conditional on project availability
Apr 14, 2026
6d77f81
Preserve ApproovException contract by trapping native fetch crashes
Apr 14, 2026
209a428
feat: implement thread-safe failure mode caching
Apr 21, 2026
005a33a
Update settings.graddle to support local and ci repo usage for mini-sdk
Apr 21, 2026
ae9dc48
Update ApproovTestSupport to use the latest cached failure field name…
Apr 21, 2026
09968f5
Fix FileInputStream leak in settings.gradle
Apr 22, 2026
ceb8e37
Shade BouncyCastle dependency and remove it from pom.xml
May 18, 2026
a50cda7
Docs: Update CHANGELOG.md with epic #519 changes
May 19, 2026
2ef0f81
Fix API initialization gating
May 20, 2026
44704de
Align default initialization comment parameter to null to avoid nativ…
May 22, 2026
9221d0b
fix: resolve ASN1Sequence class cast bug in signature decoding
May 22, 2026
f013839
Document initialize comment options and reinitialization in REFERENCE.md
May 22, 2026
6807fb9
Simplify initialization: delegate re-init decisions to platform SDK
May 27, 2026
5093d1a
fix: preserve service layer state on SDK initialization failure
May 29, 2026
a516d0d
Revise security policy for version support and reporting
naynovi Jun 1, 2026
d34678b
Merge branch 'pr-12' into feature/3.6.0
charlesoj6205 Jun 2, 2026
e326226
Relocate BouncyCastle to io.approov.internal.volley.bouncycastle
Jun 2, 2026
8cbf347
Remove hardcoded worker URL fallbacks from CI workflow
Jun 2, 2026
46d1149
fix: ignore empty config reinitialization when already initialized wi…
Jun 3, 2026
ba877c8
refactor: simplify empty config reinitialization check using isApproo…
Jun 3, 2026
9b88c3e
Align Volley empty config tests
charlesoj6205 Jun 4, 2026
df68b16
Merge feature/3.6.0 and update changelog
charlesoj6205 Jun 5, 2026
fb1525e
Fix consumer ProGuard keep rule for relocated BouncyCastle package
adriantuk Jun 10, 2026
2bff4ab
Scope publish artifact upload to the service repository checkout
adriantuk Jun 10, 2026
9f1b499
Allow tests to compile and run without the core testing framework
adriantuk Jun 10, 2026
b4e805d
Fix loss of leading fractional zeros in DecimalItem serialization
adriantuk Jun 10, 2026
289a32f
Add migration notes for behavioral changes to USAGE.md
adriantuk Jun 10, 2026
67f5d65
Document deliberate RFC 9421 deviations in the component provider
adriantuk Jun 10, 2026
d49e647
Remove overly broad consumer keep rule for (String, int) constructors
adriantuk Jun 10, 2026
5ee43a2
Add contract test for PinningHostnameVerifier pin checking
adriantuk Jun 10, 2026
3ea6050
Remove hardcoded worker endpoints from tests and gate live requests
adriantuk Jun 10, 2026
3cb284d
Make the Base64 test stub behaviorally faithful and document the HTTP…
adriantuk Jun 10, 2026
de66d64
Document the intentional failure asymmetry between signing algorithms
adriantuk Jun 10, 2026
4ca5c9a
Avoid duplicate CI runs for branches with open pull requests
adriantuk Jun 10, 2026
d1b5279
Drop Map overrides requiring Android API 24 from Parameters
adriantuk Jun 10, 2026
5fa1b33
Remove editor settings from version control
adriantuk Jun 10, 2026
69ce306
Drop redundant compileOnly declaration of the shadow jar
adriantuk Jun 10, 2026
098259d
Remove leftover toString call on a String parameter
adriantuk Jun 10, 2026
8a843aa
Record headers added by message signing in the request mutations
adriantuk Jun 10, 2026
c5fca9d
Document provenance and modifications of the vendored sfv package
adriantuk Jun 10, 2026
cc618bd
Rename initialization test to match the verified contract
adriantuk Jun 10, 2026
790d061
Record review fixes in the changelog
adriantuk Jun 10, 2026
f26bf2a
Tolerate mini-sdk revisions without the testing reply URL accessors
adriantuk Jun 10, 2026
acebc4e
fix: guard secure string substitution against null/empty values
Jun 12, 2026
5cb4e5a
ci: bake service-layer version into AAR and validate CHANGELOG on pub…
Jun 12, 2026
d673143
fix: update contract test assertions for versioned setUserProperty
Jun 12, 2026
f7ec5ec
docs: complete version 3.5.5 documentation and changelog updates
ivolz Jun 13, 2026
b9ca778
Fix reviewer feedback and resolve inline thread comments on PR 9
ivolz Jun 13, 2026
542e663
Address Copilot PR review comments regarding SECURITY.md grammar and …
ivolz Jun 13, 2026
b243ece
fix(message-signing): account-signing silent fallback symmetry and ty…
ivolz Jun 13, 2026
37caa5a
Implement message signing fail-open policy for decoding and formattin…
ivolz Jun 14, 2026
915511e
Fix GitHub Actions secrets syntax error in step level if checks
ivolz Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
WORKSPACE: "${{ github.workspace }}"
GIT_BRANCH: "${{ github.ref }}"
CURRENT_TAG: "${{ github.ref_name }}"
Expand All @@ -23,7 +24,16 @@ jobs:
run: git config --global --add safe.directory '*'

- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: approov-service-volley

- name: Checkout Core Testing Framework
uses: actions/checkout@v6
with:
repository: approov/core-service-layers-testing
token: ${{ secrets.CORE_SERVICE_LAYERS_TESTING_PAT }}
path: core-service-layers-testing

- name: Set Up Java
uses: actions/setup-java@v4
Expand Down Expand Up @@ -70,17 +80,24 @@ jobs:
KEY_ID=$(gpg --list-keys --with-colons | grep pub | cut -d: -f5)
echo -e "trust\n5\ny\nquit" | gpg --batch --yes --command-fd 0 --edit-key $KEY_ID

- name: Run Tests
working-directory: approov-service-volley
run: ./gradlew test

- name: Build AAR
run: ./gradlew assembleRelease
working-directory: approov-service-volley
run: ./gradlew assembleRelease

- name: Create Package
working-directory: approov-service-volley
run: cd .maven && ./build-and-sign.sh

- name: Publish Package
working-directory: approov-service-volley
run: cd .maven && ./maven-publish.sh

- name: Upload Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: full-repo-artifact-${{ github.ref_name }}
path: ${{ github.workspace }}
292 changes: 292 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
name: Build and Test

on:
push:
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
WORKSPACE: "${{ github.workspace }}"
GIT_BRANCH: "${{ github.ref }}"
CURRENT_TAG: "${{ github.ref_name }}"
# Worker endpoints: consumed from GitHub organisation variables first.
# If the org variable is not set the value is an empty string; the probe
# step below falls back to the same hardcoded defaults that are baked into
# the mini-sdk (MiniAttesterConfig / Approov.m), keeping them in sync.
# Worker management (create / redeploy) logic is centralized in the
Comment thread
ivolz marked this conversation as resolved.
Outdated
# core-service-layers-testing script, but invoked here when needed.
TESTING_REPLY_URL: ${{ vars.TESTING_REPLY_URL }}
TESTING_REPLY_URL_UNPROTECTED: ${{ vars.TESTING_REPLY_URL_UNPROTECTED }}
# Required for the redeploy script invocation on failure
CLOUDFLARE_API_TOKEN_WORKERS_DEV: ${{ secrets.CLOUDFLARE_API_TOKEN_WORKERS_DEV }}
CLOUDFLARE_ACCOUNT_ID_WORKERS_DEV: ${{ secrets.CLOUDFLARE_ACCOUNT_ID_WORKERS_DEV }}

steps:
# -----------------------------------------------------------------------
# 1. Checkout this repository
# -----------------------------------------------------------------------
- name: Set up Git
run: git config --global --add safe.directory '*'

- name: Checkout approov-service-volley
uses: actions/checkout@v6
with:
path: approov-service-volley

# -----------------------------------------------------------------------
# 2. Clone core-service-layers-testing as a sibling directory
# settings.gradle expects: ../core-service-layers-testing/...
# so both repos must sit under the same parent ($GITHUB_WORKSPACE).
# -----------------------------------------------------------------------
- name: Checkout core-service-layers-testing
uses: actions/checkout@v6
with:
repository: approov/core-service-layers-testing
token: ${{ secrets.CORE_SERVICE_LAYERS_TESTING_PAT }}
path: core-service-layers-testing

# -----------------------------------------------------------------------
# 3. Verify worker endpoints (with auto-redeploy)
#
# URLs are resolved with the following precedence (mirrors the mini-sdk
# fallback logic in MiniAttesterConfig / Approov.m):
# 1. GitHub org variable (TESTING_REPLY_URL / _UNPROTECTED)
# 2. Mini-SDK hardcoded defaults (replay.ivol.workers.dev / ...)
#
# If the workers are unavailable, this step invokes the management
# script in core-service-layers-testing to redeploy them.
# -----------------------------------------------------------------------
- name: Verify worker endpoints
run: |
# ── URL resolution ──────────────────────────────────────────────────
PROTECTED_URL="${TESTING_REPLY_URL:-https://replay.ivol.workers.dev}"
UNPROTECTED_URL="${TESTING_REPLY_URL_UNPROTECTED:-https://replay-unprotected.ivol.workers.dev}"

echo "TESTING_REPLY_URL=$PROTECTED_URL" >> "$GITHUB_ENV"
echo "TESTING_REPLY_URL_UNPROTECTED=$UNPROTECTED_URL" >> "$GITHUB_ENV"

# ── Probe function ──────────────────────────────────────────────────
probe_worker() {
local url="$1"
curl --silent --show-error --fail --max-time 10 \
-X POST "$url" \
-H "Content-Type: application/json" \
-d '{"check":"probe"}' | grep -q '"body"'
}

echo "==> Probing workers..."
if probe_worker "$PROTECTED_URL" && probe_worker "$UNPROTECTED_URL"; then
echo " All workers are healthy."
exit 0
fi

echo " WARNING: One or more workers are down. Attempting redeploy..."

# ── Invoke redeploy script ──────────────────────────────────────────
# The script is in the sibling directory cloned in Step 2.
# It uses the CLOUDFLARE_* env variables defined at the job level.
./core-service-layers-testing/cloudflare-workers/redeploy-workers.sh

echo "==> Verifying after redeploy..."
# Try up to 3 times with a 5s delay between attempts
for i in {1..3}; do
if probe_worker "$PROTECTED_URL" && probe_worker "$UNPROTECTED_URL"; then
echo " Workers successfully restored."
exit 0
fi
echo " Waiting for propagation (attempt $i/3)..."
sleep 5
done

echo "ERROR: Workers are still unreachable after redeployment effort."
exit 1

# -----------------------------------------------------------------------
# 4. Java / Android toolchain setup
# -----------------------------------------------------------------------
- name: Set Up Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'

- name: Install Android SDK command-line tools
run: |
sudo apt-get update -q
sudo apt-get install -y -q unzip curl
mkdir -p "$ANDROID_HOME/cmdline-tools"
curl -o android-sdk.zip \
https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip
unzip -q android-sdk.zip -d "$ANDROID_HOME/cmdline-tools"
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/tools"
rm android-sdk.zip
echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV"
echo "$ANDROID_HOME/cmdline-tools/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator" >> "$GITHUB_PATH"

- name: Accept Android SDK licenses
run: yes | sdkmanager --licenses || true

- name: Install required Android SDK packages
run: |
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"

# -----------------------------------------------------------------------
# 5. Build and run tests
# Gradle is invoked from inside the checked-out service directory.
# The mini-sdk is already available via the sibling path wired into
# settings.gradle — no extra configuration needed here.
# -----------------------------------------------------------------------
- name: Build AAR
working-directory: approov-service-volley
run: ./gradlew assembleRelease

- name: Run unit tests
working-directory: approov-service-volley
env:
TESTING_REPLY_URL: ${{ env.TESTING_REPLY_URL }}
TESTING_REPLY_URL_UNPROTECTED: ${{ env.TESTING_REPLY_URL_UNPROTECTED }}
run: ./gradlew test

# -----------------------------------------------------------------------
# 6. Print test summary to the console and to the Actions Job Summary
# Parses every JUnit XML produced by Gradle and emits:
# • a formatted table in the step log
# • a markdown table in the Job Summary (Actions UI → Summary tab)
# -----------------------------------------------------------------------
- name: Print test summary
if: always()
working-directory: approov-service-volley
run: |
python3 - <<'EOF'
import os, sys, glob, xml.etree.ElementTree as ET
from collections import defaultdict

PASS = "\u2705"
FAIL = "\u274c"
SKIP = "\u23ed\ufe0f"
ERROR = "\u26a0\ufe0f"

results_root = "approov-service/build/test-results"
xml_files = sorted(glob.glob(f"{results_root}/**/*.xml", recursive=True))

if not xml_files:
print("No test-result XML files found.")
sys.exit(0)

# Group suites by variant directory name (testDebugUnitTest, etc.)
by_variant = defaultdict(list)
for path in xml_files:
variant = os.path.basename(os.path.dirname(path))
try:
root = ET.parse(path).getroot()
except ET.ParseError:
continue
suite = {
"name": root.get("name", os.path.basename(path)),
"tests": int(root.get("tests", 0)),
"failures": int(root.get("failures", 0)),
"errors": int(root.get("errors", 0)),
"skipped": int(root.get("skipped", 0)),
"time": float(root.get("time", 0)),
"cases": [],
}
for tc in root.findall("testcase"):
status = PASS
detail = ""
if tc.find("failure") is not None:
status = FAIL
detail = (tc.find("failure").get("message") or "").split("\n")[0][:120]
elif tc.find("error") is not None:
status = ERROR
detail = (tc.find("error").get("message") or "").split("\n")[0][:120]
elif tc.find("skipped") is not None:
status = SKIP
suite["cases"].append({
"name": tc.get("name", "?"),
"time": float(tc.get("time", 0)),
"status": status,
"detail": detail,
})
by_variant[variant].append(suite)

# ── Console output ──────────────────────────────────────────────────
overall_ok = True
for variant, suites in sorted(by_variant.items()):
total_t = sum(s["tests"] for s in suites)
total_f = sum(s["failures"] + s["errors"] for s in suites)
total_s = sum(s["skipped"] for s in suites)
total_p = total_t - total_f - total_s
icon = PASS if total_f == 0 else FAIL
if total_f > 0:
overall_ok = False
print()
print(f"{'='*70}")
print(f" {icon} {variant} | {total_p}/{total_t} passed "
f"| {total_f} failed | {total_s} skipped")
print(f"{'='*70}")
for suite in suites:
short = suite["name"].split(".")[-1]
p = suite["tests"] - suite["failures"] - suite["errors"] - suite["skipped"]
f = suite["failures"] + suite["errors"]
print(f" {PASS if f==0 else FAIL} {short:<55} {p}/{suite['tests']} ({suite['time']:.2f}s)")
for case in suite["cases"]:
if case["status"] != PASS:
print(f" {case['status']} {case['name']}")
if case["detail"]:
print(f" {case['detail']}")
print()

# ── GitHub Job Summary (markdown) ───────────────────────────────────
summary_path = os.environ.get("GITHUB_STEP_SUMMARY", "")
if not summary_path:
sys.exit(0 if overall_ok else 1)

with open(summary_path, "a") as md:
md.write("## \U0001f9ea Unit Test Results\n\n")
for variant, suites in sorted(by_variant.items()):
total_t = sum(s["tests"] for s in suites)
total_f = sum(s["failures"] + s["errors"] for s in suites)
total_s = sum(s["skipped"] for s in suites)
total_p = total_t - total_f - total_s
badge = "\U0001f7e2 PASSED" if total_f == 0 else "\U0001f534 FAILED"
md.write(f"### {variant} &nbsp; {badge}\n\n")
md.write(f"> **{total_p} passed** &nbsp;|&nbsp; "
f"**{total_f} failed** &nbsp;|&nbsp; "
f"**{total_s} skipped** &nbsp;|&nbsp; "
f"**{total_t} total**\n\n")
md.write("| Status | Test suite | Tests | Failed | Skipped | Time |\n")
md.write("|--------|------------|------:|-------:|--------:|-----:|\n")
for suite in suites:
short = suite["name"].split(".")[-1]
f = suite["failures"] + suite["errors"]
icon = "\U0001f7e2" if f == 0 else "\U0001f534"
md.write(f"| {icon} | `{short}` | {suite['tests']} | {f} | {suite['skipped']} | {suite['time']:.2f}s |\n")
# Expand failures inline
failures = [c for s in suites for c in s["cases"] if c["status"] in (FAIL, ERROR)]
if failures:
md.write("\n<details><summary>Failed tests</summary>\n\n")
for c in failures:
md.write(f"- {c['status']} `{c['name']}`")
if c["detail"]:
md.write(f" \n > {c['detail']}")
md.write("\n")
md.write("\n</details>\n")
md.write("\n")

sys.exit(0 if overall_ok else 1)
EOF

# -----------------------------------------------------------------------
# 7. Upload HTML reports as a downloadable artifact
# -----------------------------------------------------------------------
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-${{ github.run_number }}
path: approov-service-volley/approov-service/build/reports/tests/
retention-days: 14
Loading