#!/usr/bin/env bash # Tests for PaperMC Downloads Service API (v3) # Covers: API reachability, build resolution, download URL, checksums # Usage: ./tests/test_paper.sh [mc_version] source "$(dirname "$0")/test_utils.sh" MC_VERSION="${1:-$(get_latest_mc_version)}" if [[ -z "$MC_VERSION" ]]; then echo "Could not determine MC version" exit 1 fi echo -e "\n${BOLD}PaperMC API Tests (MC ${MC_VERSION})${NC}" echo "" PAPER_API="https://fill.papermc.io/v3" PAPER_UA="dockercraft-test/1.0" paper_api_get() { curl -sf --connect-timeout 10 --max-time 30 \ -H "User-Agent: ${PAPER_UA}" "$1" 2>/dev/null || true } # ---------- Project info endpoint ------------------------------------------ test_project_api_reachable() { local body body=$(paper_api_get "${PAPER_API}/projects/paper") assert_not_empty "$body" "Project API body should not be empty" assert_json_valid "$body" "Project API should return valid JSON" } test_project_has_versions() { local body body=$(paper_api_get "${PAPER_API}/projects/paper") assert_json_field "$body" '.versions' "Should have .versions field" } test_project_versions_is_object() { local body body=$(paper_api_get "${PAPER_API}/projects/paper") local type type=$(echo "$body" | jq -r '.versions | type') assert_equals "$type" "object" ".versions should be a JSON object" } test_project_has_version_groups() { local body body=$(paper_api_get "${PAPER_API}/projects/paper") local count count=$(echo "$body" | jq '.versions | length') if [[ "$count" -gt 0 ]]; then return 0 else echo "ASSERT FAILED: .versions should have groups, got ${count}" return 1 fi } # ---------- Builds endpoint ------------------------------------------------- test_builds_api_reachable() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") assert_not_empty "$body" "Builds API body should not be empty" } test_builds_is_array() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local type type=$(echo "$body" | jq -r 'type') assert_equals "$type" "array" "Response should be a JSON array" } test_builds_has_entries() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local count count=$(echo "$body" | jq 'length') if [[ "$count" -gt 0 ]]; then return 0 else echo "ASSERT FAILED: builds array should have entries, got ${count}" return 1 fi } test_build_entry_has_id() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") assert_json_field "$body" '.[0].id' "Build entry should have .id field" } test_build_id_is_numeric() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local id id=$(echo "$body" | jq -r '.[0].id') assert_matches "$id" "^[0-9]+$" "Build id '${id}' should be numeric" } test_build_entry_has_channel() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") assert_json_field "$body" '.[0].channel' "Build entry should have .channel field" } test_build_channel_is_valid() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local channel channel=$(echo "$body" | jq -r '.[0].channel') assert_matches "$channel" "^(STABLE|EXPERIMENTAL)$" \ "Build channel '${channel}' should be STABLE or EXPERIMENTAL" } test_build_entry_has_downloads() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") assert_json_field "$body" '.[0].downloads."server:default"' \ "Build entry should have .downloads.\"server:default\"" } # ---------- Stable builds -------------------------------------------------- test_stable_builds_exist() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local stable_count stable_count=$(echo "$body" | jq '[.[] | select(.channel == "STABLE")] | length') if [[ "$stable_count" -gt 0 ]]; then return 0 else echo "ASSERT FAILED: No stable builds found for MC ${MC_VERSION}" return 1 fi } test_latest_stable_build_id() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local id id=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE")) | .id') assert_not_empty "$id" "Latest stable build id should not be empty" assert_matches "$id" "^[0-9]+$" "Build id should be numeric" } # ---------- Download URL --------------------------------------------------- test_download_url_present() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local url url=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty') assert_not_empty "$url" "Stable build download URL should not be empty" } test_download_url_format() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local url url=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty') assert_matches "$url" ".*paper-.*\.jar$" \ "Download URL should end with a paper jar filename" } test_download_url_reachable() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local url url=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty') if [[ -z "$url" ]]; then echo "No download URL found" return 1 fi assert_http_ok "$url" "Download URL should return HTTP 200" } test_download_name_matches_pattern() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local name name=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".name) // empty') assert_matches "$name" "^paper-${MC_VERSION}-[0-9]+\.jar$" \ "Download name '${name}' should match paper-{version}-{build}.jar" } # ---------- Checksums ------------------------------------------------------ test_download_has_sha256() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local sha sha=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".checksums.sha256) // empty') assert_matches "$sha" "^[a-f0-9]{64}$" \ "SHA256 '${sha}' should be a 64-char hex string" } test_download_has_size() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local size size=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".size) // empty') assert_matches "$size" "^[0-9]+$" "Download size '${size}' should be numeric" } test_download_size_reasonable() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local size size=$(echo "$body" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".size)') # Paper jar should be between 1MB and 100MB if [[ "$size" -gt 1000000 && "$size" -lt 100000000 ]]; then return 0 else echo "ASSERT FAILED: Download size ${size} bytes is outside expected range (1MB-100MB)" return 1 fi } # ---------- Build ordering ------------------------------------------------- test_builds_are_ordered() { local body body=$(paper_api_get "${PAPER_API}/projects/paper/versions/${MC_VERSION}/builds") local first_id last_id first_id=$(echo "$body" | jq '.[0].id') last_id=$(echo "$body" | jq '.[-1].id') # v3 API returns builds in descending order (newest first) assert_version_gte "$first_id" "$last_id" "Build ids should be in descending order" } # ---------- Run all tests -------------------------------------------------- run_test "Project API is reachable and valid JSON" test_project_api_reachable run_test "Project has .versions field" test_project_has_versions run_test ".versions is a JSON object" test_project_versions_is_object run_test "Project has version groups" test_project_has_version_groups run_test "Builds API is reachable" test_builds_api_reachable run_test "Response is a JSON array" test_builds_is_array run_test "Builds array has entries" test_builds_has_entries run_test "Build entry has .id field" test_build_entry_has_id run_test "Build id is numeric" test_build_id_is_numeric run_test "Build entry has .channel field" test_build_entry_has_channel run_test "Build channel is STABLE or EXPERIMENTAL" test_build_channel_is_valid run_test "Build entry has downloads" test_build_entry_has_downloads run_test "Stable builds exist for MC ${MC_VERSION}" test_stable_builds_exist run_test "Latest stable build id is numeric" test_latest_stable_build_id run_test "Download URL is present" test_download_url_present run_test "Download URL has correct format" test_download_url_format run_test "Download URL returns HTTP 200" test_download_url_reachable run_test "Download name matches paper-{ver}-{build}.jar" test_download_name_matches_pattern run_test "Download has SHA256 checksum" test_download_has_sha256 run_test "Download has file size" test_download_has_size run_test "Download size is reasonable (1MB-100MB)" test_download_size_reasonable run_test "Build ids are in descending order (newest first)" test_builds_are_ordered print_summary