fix: migrate PaperMC to v3 API, fix NeoForge beta fallback, add test suite
- Migrate Paper loader from sunset v2 API to v3 (fill.papermc.io) - Fix NeoForge: fall back to beta versions when no stable exists (set -e + grep) - Fix setEula/setperms ordering (was writing eula.txt before /data was writable) - Fix setEula/serverInfoWrite to use absolute paths (/data/server/) - Add VOLUME directive removal (was shadowing bind mounts) - Add comprehensive API test suite (110 tests across 6 suites) - Move test.sh to tests/integration.sh, switch to named volumes - Add Docker build cache to CI workflows - Add .editorconfig, expand .gitignore, improve README docs
This commit is contained in:
Executable
+271
@@ -0,0 +1,271 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user