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
+175
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [options]
|
||||
|
||||
Test Minecraft server loader integrations in isolated Docker containers.
|
||||
|
||||
Options:
|
||||
-m, --mc-version <ver> Minecraft version (default: auto-detect latest)
|
||||
-l, --loader <name> Loader to test (default: all)
|
||||
-v, --loader-version <v> Loader version (default: auto-detect latest)
|
||||
-t, --timeout <sec> Max wait for server jar (default: 180)
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
$(basename "$0") Test all loaders (latest MC)
|
||||
$(basename "$0") -m 1.21.1 Test all loaders on MC 1.21.1
|
||||
$(basename "$0") -m 1.21.1 -l fabric Test Fabric on MC 1.21.1
|
||||
$(basename "$0") -m 1.21.1 -l fabric -v 0.19.3 Test Fabric 0.19.3 on MC 1.21.1
|
||||
|
||||
Loaders: vanilla paper fabric forge neoforge bedrock
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
detect_mc_version() {
|
||||
curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://launchermeta.mojang.com/mc/game/version_manifest.json" |
|
||||
jq -r '.latest.release'
|
||||
}
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
IMAGE="dockercraft-test:latest"
|
||||
MC_VERSION=""
|
||||
LOADER=""
|
||||
LOADER_VERSION=""
|
||||
TIMEOUT=180
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
-m|--mc-version) MC_VERSION="$2"; shift 2 ;;
|
||||
-l|--loader) LOADER="$2"; shift 2 ;;
|
||||
-v|--loader-version) LOADER_VERSION="$2"; shift 2 ;;
|
||||
-t|--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$MC_VERSION" ]]; then
|
||||
echo "Detecting latest Minecraft version..."
|
||||
MC_VERSION=$(detect_mc_version)
|
||||
echo " -> ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
if [[ -z "$LOADER" ]]; then
|
||||
tests=("vanilla" "paper" "fabric" "forge" "neoforge" "bedrock")
|
||||
else
|
||||
tests=("$LOADER")
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
local name="$1"
|
||||
docker kill "dc-test-${name}" 2>/dev/null || true
|
||||
docker rm "dc-test-${name}" 2>/dev/null || true
|
||||
docker volume rm "dc-test-vol-${name}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
echo "=== Testing: ${name} ==="
|
||||
cleanup "$name"
|
||||
|
||||
# Use a named volume instead of a bind mount (avoids host filesystem quirks)
|
||||
docker volume create "dc-test-vol-${name}" >/dev/null
|
||||
|
||||
local CONTAINER_ID
|
||||
if CONTAINER_ID=$(docker run -d --name "dc-test-${name}" \
|
||||
-e EULA=true \
|
||||
-e MC_VERSION="${MC_VERSION}" \
|
||||
"$@" \
|
||||
-v "dc-test-vol-${name}:/data" \
|
||||
"${IMAGE}" 2>&1); then
|
||||
|
||||
local waited=0
|
||||
local jar_found=false
|
||||
while [[ $waited -lt $TIMEOUT ]]; do
|
||||
# Check inside the container for the server binary
|
||||
if docker exec "dc-test-${name}" test -f /data/server/server.jar 2>/dev/null || \
|
||||
docker exec "dc-test-${name}" find /data/server -name 'forge-*-server.jar' 2>/dev/null | grep -q . || \
|
||||
docker exec "dc-test-${name}" test -f /data/server/bedrock_server 2>/dev/null; then
|
||||
jar_found=true
|
||||
break
|
||||
fi
|
||||
sleep 3
|
||||
waited=$((waited + 3))
|
||||
echo -n "."
|
||||
done
|
||||
echo ""
|
||||
|
||||
if $jar_found; then
|
||||
if docker exec "dc-test-${name}" test -f /data/server/bedrock_server 2>/dev/null; then
|
||||
echo " PASS (bedrock_server found in ~${waited}s)"
|
||||
else
|
||||
echo " PASS (server jar found in ~${waited}s)"
|
||||
fi
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo ""
|
||||
echo " FAIL: server binary not found after ${TIMEOUT}s"
|
||||
echo " --- container logs ---"
|
||||
docker logs "dc-test-${name}" 2>&1 | tail -50 || true
|
||||
echo " ---------------------"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo " FAIL: container failed to start"
|
||||
echo " ${CONTAINER_ID}"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
cleanup "$name"
|
||||
}
|
||||
|
||||
echo "Building image..."
|
||||
DOCKER_BUILDKIT=0 docker build -t "${IMAGE}" "${PROJECT_ROOT}"
|
||||
|
||||
for t in "${tests[@]}"; do
|
||||
case $t in
|
||||
vanilla)
|
||||
run_test "vanilla" -e MC_LOADER=vanilla
|
||||
;;
|
||||
paper)
|
||||
run_test "paper" -e MC_LOADER=paper
|
||||
;;
|
||||
fabric)
|
||||
run_test "fabric" \
|
||||
-e MC_LOADER=fabric \
|
||||
-e FABRIC_LOADER_VERSION="${LOADER_VERSION}"
|
||||
;;
|
||||
forge)
|
||||
run_test "forge" \
|
||||
-e MC_LOADER=forge \
|
||||
-e FORGE_VERSION="${LOADER_VERSION}"
|
||||
;;
|
||||
neoforge)
|
||||
run_test "neoforge" \
|
||||
-e MC_LOADER=neoforge \
|
||||
-e NEOFORGE_VERSION="${LOADER_VERSION}"
|
||||
;;
|
||||
bedrock)
|
||||
run_test "bedrock" \
|
||||
-e MC_LOADER=bedrock \
|
||||
-e MC_VERSION=
|
||||
;;
|
||||
*)
|
||||
echo "Unknown loader: ${t}"
|
||||
echo "Valid: vanilla paper fabric forge neoforge bedrock"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Results: ${PASS} passed, ${FAIL} failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
# Main test runner for Dockercraft API tests
|
||||
# Runs all test suites against the external APIs to verify they're healthy
|
||||
# and returning expected data.
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/run_tests.sh # Run all tests
|
||||
# ./tests/run_tests.sh -m 1.21.1 # Test with specific MC version
|
||||
# ./tests/run_tests.sh -s mojang # Run only Mojang tests
|
||||
# ./tests/run_tests.sh -s paper,fabric # Run Paper and Fabric tests
|
||||
# ./tests/run_tests.sh --list # List available test suites
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 = all tests passed
|
||||
# 1 = one or more tests failed
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ---------- configuration --------------------------------------------------
|
||||
|
||||
MC_VERSION=""
|
||||
SUITE=""
|
||||
LIST_ONLY=false
|
||||
|
||||
# Available test suites
|
||||
declare -A SUITES=(
|
||||
["mojang"]="test_mojang.sh"
|
||||
["paper"]="test_paper.sh"
|
||||
["fabric"]="test_fabric.sh"
|
||||
["forge"]="test_forge.sh"
|
||||
["neoforge"]="test_neoforge.sh"
|
||||
["bedrock"]="test_bedrock.sh"
|
||||
)
|
||||
|
||||
SUITE_ORDER=("mojang" "paper" "fabric" "forge" "neoforge" "bedrock")
|
||||
|
||||
# ---------- argument parsing -----------------------------------------------
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [options]
|
||||
|
||||
Run API health and correctness tests for Dockercraft.
|
||||
|
||||
Options:
|
||||
-m, --mc-version <ver> Minecraft version (default: auto-detect latest)
|
||||
-s, --suite <name> Run specific suite(s), comma-separated
|
||||
Valid: mojang, paper, fabric, forge, neoforge, bedrock
|
||||
-l, --list List available test suites and exit
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # All suites, latest MC
|
||||
$(basename "$0") -m 1.21.1 # All suites, MC 1.21.1
|
||||
$(basename "$0") -s mojang # Just Mojang API tests
|
||||
$(basename "$0") -s paper,fabric,neoforge # Specific suites
|
||||
$(basename "$0") -l # List suites
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
list_suites() {
|
||||
echo "Available test suites:"
|
||||
echo ""
|
||||
for s in "${SUITE_ORDER[@]}"; do
|
||||
echo " ${s} - ${SUITES[$s]}"
|
||||
done
|
||||
echo ""
|
||||
echo "Usage: $(basename "$0") -s mojang,paper"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
-m|--mc-version) MC_VERSION="$2"; shift 2 ;;
|
||||
-s|--suite) SUITE="$2"; shift 2 ;;
|
||||
-l|--list) LIST_ONLY=true; shift ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if $LIST_ONLY; then
|
||||
list_suites
|
||||
fi
|
||||
|
||||
# ---------- detect MC version if needed ------------------------------------
|
||||
|
||||
if [[ -z "$MC_VERSION" ]]; then
|
||||
echo "Detecting latest Minecraft version..."
|
||||
MC_VERSION=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://launchermeta.mojang.com/mc/game/version_manifest.json" |
|
||||
jq -r '.latest.release')
|
||||
echo " -> ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
# ---------- determine which suites to run ----------------------------------
|
||||
|
||||
if [[ -n "$SUITE" ]]; then
|
||||
IFS=',' read -ra RUN_SUITES <<< "$SUITE"
|
||||
else
|
||||
RUN_SUITES=("${SUITE_ORDER[@]}")
|
||||
fi
|
||||
|
||||
# ---------- validate suite names -------------------------------------------
|
||||
|
||||
for s in "${RUN_SUITES[@]}"; do
|
||||
if [[ -z "${SUITES[$s]+x}" ]]; then
|
||||
echo "Unknown suite: ${s}"
|
||||
echo "Valid suites: ${!SUITES[*]}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# ---------- run suites -----------------------------------------------------
|
||||
|
||||
OVERALL_PASS=0
|
||||
OVERALL_FAIL=0
|
||||
OVERALL_SUITES=0
|
||||
FAILED_SUITES=()
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Dockercraft API Test Runner"
|
||||
echo " MC Version: ${MC_VERSION}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
for s in "${RUN_SUITES[@]}"; do
|
||||
test_file="${SCRIPT_DIR}/${SUITES[$s]}"
|
||||
|
||||
if [[ ! -f "$test_file" ]]; then
|
||||
echo "Test file not found: ${test_file}"
|
||||
OVERALL_FAIL=$((OVERALL_FAIL + 1))
|
||||
FAILED_SUITES+=("$s")
|
||||
continue
|
||||
fi
|
||||
|
||||
OVERALL_SUITES=$((OVERALL_SUITES + 1))
|
||||
|
||||
# Run the suite and capture exit code
|
||||
if bash "$test_file" "$MC_VERSION"; then
|
||||
OVERALL_PASS=$((OVERALL_PASS + 1))
|
||||
else
|
||||
OVERALL_FAIL=$((OVERALL_FAIL + 1))
|
||||
FAILED_SUITES+=("$s")
|
||||
fi
|
||||
done
|
||||
|
||||
# ---------- final summary --------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Overall Results"
|
||||
echo "========================================"
|
||||
echo " Suites run: ${OVERALL_SUITES}"
|
||||
echo " Suites passed: ${OVERALL_PASS}"
|
||||
echo " Suites failed: ${OVERALL_FAIL}"
|
||||
echo ""
|
||||
|
||||
if [[ $OVERALL_FAIL -gt 0 ]]; then
|
||||
echo "Failed suites:"
|
||||
for s in "${FAILED_SUITES[@]}"; do
|
||||
echo " - ${s}"
|
||||
done
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All suites passed!"
|
||||
exit 0
|
||||
Executable
+252
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for Bedrock Dedicated Server download API
|
||||
# Covers: Primary API, fallback API, version parsing
|
||||
# Usage: ./tests/test_bedrock.sh
|
||||
|
||||
source "$(dirname "$0")/test_utils.sh"
|
||||
|
||||
echo -e "\n${BOLD}Bedrock API Tests${NC}"
|
||||
echo ""
|
||||
|
||||
BEDROCK_API_URL="https://net.web.minecraft-services.net/api/v1.0/download/links"
|
||||
BEDROCK_FALLBACK_URL="https://raw.githubusercontent.com/kittizz/bedrock-server-downloads/refs/heads/main/bedrock-server-downloads.json"
|
||||
|
||||
# ---------- Primary API (minecraft-services.net) ---------------------------
|
||||
|
||||
test_bedrock_primary_api_reachable() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
assert_not_empty "$body" "Bedrock primary API body should not be empty"
|
||||
}
|
||||
|
||||
test_bedrock_primary_api_is_json() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
assert_json_valid "$body" "Primary API response should be valid JSON"
|
||||
}
|
||||
|
||||
test_bedrock_primary_api_has_result() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
assert_json_field "$body" '.result' "Should have .result field"
|
||||
}
|
||||
|
||||
test_bedrock_primary_api_has_links() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
assert_json_field "$body" '.result.links' "Should have .result.links array"
|
||||
}
|
||||
|
||||
test_bedrock_primary_api_links_is_array() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local type
|
||||
type=$(echo "$body" | jq -r '.result.links | type')
|
||||
assert_equals "$type" "array" ".result.links should be an array"
|
||||
}
|
||||
|
||||
test_bedrock_primary_has_linux_entry() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local found
|
||||
found=$(echo "$body" | jq -r '.result.links[] | select(.downloadType == "serverBedrockLinux") | .downloadUrl // empty')
|
||||
|
||||
if [[ -n "$found" && "$found" != "null" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: No serverBedrockLinux entry found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_bedrock_primary_linux_url_format() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local url
|
||||
url=$(echo "$body" | jq -r '.result.links[] | select(.downloadType == "serverBedrockLinux") | .downloadUrl // empty')
|
||||
|
||||
assert_matches "$url" "bedrock-server-.*\.zip$" \
|
||||
"Linux download URL should end with bedrock-server-{version}.zip"
|
||||
}
|
||||
|
||||
test_bedrock_primary_linux_url_reachable() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local url
|
||||
url=$(echo "$body" | jq -r '.result.links[] | select(.downloadType == "serverBedrockLinux") | .downloadUrl // empty')
|
||||
|
||||
if [[ -z "$url" ]]; then
|
||||
echo "No Linux download URL found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Just check HTTP HEAD to avoid downloading the full zip
|
||||
local http_code
|
||||
http_code=$(curl -sL -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 30 \
|
||||
-A "Mozilla/5.0 (X11; Linux x86_64)" -I "$url") || true
|
||||
assert_equals "$http_code" "200" "Bedrock download URL should return HTTP 200"
|
||||
}
|
||||
|
||||
test_bedrock_primary_has_windows_entry() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local found
|
||||
found=$(echo "$body" | jq -r '.result.links[] | select(.downloadType == "serverBedrockWin") | .downloadUrl // empty')
|
||||
|
||||
if [[ -n "$found" && "$found" != "null" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "No serverBedrockWin entry found (may be expected on some API versions)"
|
||||
return 77 # skip
|
||||
fi
|
||||
}
|
||||
|
||||
test_bedrock_primary_download_type_values() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local types
|
||||
types=$(echo "$body" | jq -r '.result.links[].downloadType' | sort -u)
|
||||
|
||||
assert_not_empty "$types" "Should have at least one download type"
|
||||
}
|
||||
|
||||
# ---------- Version parsing from URL ---------------------------------------
|
||||
|
||||
test_bedrock_version_from_url() {
|
||||
local body
|
||||
body=$(api_get_ua "$BEDROCK_API_URL")
|
||||
local url
|
||||
url=$(echo "$body" | jq -r '.result.links[] | select(.downloadType == "serverBedrockLinux") | .downloadUrl // empty')
|
||||
|
||||
if [[ -z "$url" ]]; then
|
||||
echo "No Linux download URL found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract version from URL (same regex as bedrock.sh)
|
||||
local ver
|
||||
ver=$(echo "$url" | grep -oP 'bedrock-server-\K[\d.]+(?=\.zip)')
|
||||
assert_not_empty "$ver" "Should extract version from URL"
|
||||
assert_matches "$ver" "^[0-9]+\.[0-9]+\.[0-9]+" \
|
||||
"Version '${ver}' should look like x.y.z"
|
||||
}
|
||||
|
||||
# ---------- Fallback API (GitHub) ------------------------------------------
|
||||
|
||||
test_bedrock_fallback_api_reachable() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
assert_not_empty "$body" "Bedrock fallback API body should not be empty"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_api_is_json() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
assert_json_valid "$body" "Fallback API response should be valid JSON"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_has_release() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
assert_json_field "$body" '.release' "Should have .release field"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_release_is_object() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
local type
|
||||
type=$(echo "$body" | jq -r '.release | type')
|
||||
assert_equals "$type" "object" ".release should be a JSON object"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_release_has_entries() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
local count
|
||||
count=$(echo "$body" | jq '.release | length')
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: .release should have entries, got ${count}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_bedrock_fallback_entry_has_linux_url() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
local first_key
|
||||
first_key=$(echo "$body" | jq -r '.release | keys | .[0]')
|
||||
local url
|
||||
url=$(echo "$body" | jq -r ".release[\"${first_key}\"].linux.url // empty")
|
||||
|
||||
assert_not_empty "$url" "First release entry should have .linux.url"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_linux_url_format() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
local first_key
|
||||
first_key=$(echo "$body" | jq -r '.release | keys | .[0]')
|
||||
local url
|
||||
url=$(echo "$body" | jq -r ".release[\"${first_key}\"].linux.url // empty")
|
||||
|
||||
assert_matches "$url" "bedrock-server-.*\.zip$" \
|
||||
"Fallback URL should end with bedrock-server-{version}.zip"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_highest_version() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
# The script sorts by version and takes the last (highest) entry
|
||||
local highest
|
||||
highest=$(echo "$body" | jq -r '
|
||||
.release | to_entries | sort_by(.key | split(".") | map(tonumber)) | last | .key
|
||||
')
|
||||
|
||||
assert_not_empty "$highest" "Should find a highest version"
|
||||
assert_matches "$highest" "^[0-9]+\.[0-9]+\.[0-9]+" \
|
||||
"Highest version '${highest}' should look like x.y.z"
|
||||
}
|
||||
|
||||
# ---------- Preview entry --------------------------------------------------
|
||||
|
||||
test_bedrock_fallback_has_preview() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
assert_json_field "$body" '.preview' "Should have .preview field"
|
||||
}
|
||||
|
||||
test_bedrock_fallback_preview_is_object() {
|
||||
local body
|
||||
body=$(api_get "$BEDROCK_FALLBACK_URL")
|
||||
local type
|
||||
type=$(echo "$body" | jq -r '.preview | type')
|
||||
assert_equals "$type" "object" ".preview should be a JSON object"
|
||||
}
|
||||
|
||||
# ---------- Run all tests --------------------------------------------------
|
||||
|
||||
run_test "Primary API is reachable" test_bedrock_primary_api_reachable
|
||||
run_test "Primary API response is valid JSON" test_bedrock_primary_api_is_json
|
||||
run_test "Primary API has .result field" test_bedrock_primary_api_has_result
|
||||
run_test "Primary API has .result.links" test_bedrock_primary_api_has_links
|
||||
run_test ".result.links is an array" test_bedrock_primary_api_links_is_array
|
||||
run_test "Has serverBedrockLinux entry" test_bedrock_primary_has_linux_entry
|
||||
run_test "Linux download URL has correct format" test_bedrock_primary_linux_url_format
|
||||
run_test "Linux download URL returns HTTP 200" test_bedrock_primary_linux_url_reachable
|
||||
run_test "Has serverBedrockWin entry" test_bedrock_primary_has_windows_entry
|
||||
run_test "Download types are present" test_bedrock_primary_download_type_values
|
||||
run_test "Version can be parsed from download URL" test_bedrock_version_from_url
|
||||
run_test "Fallback API (GitHub) is reachable" test_bedrock_fallback_api_reachable
|
||||
run_test "Fallback API response is valid JSON" test_bedrock_fallback_api_is_json
|
||||
run_test "Fallback has .release field" test_bedrock_fallback_has_release
|
||||
run_test ".release is a JSON object" test_bedrock_fallback_release_is_object
|
||||
run_test ".release has entries" test_bedrock_fallback_release_has_entries
|
||||
run_test "First release entry has .linux.url" test_bedrock_fallback_entry_has_linux_url
|
||||
run_test "Fallback URL has correct format" test_bedrock_fallback_linux_url_format
|
||||
run_test "Fallback has a highest version" test_bedrock_fallback_highest_version
|
||||
run_test "Fallback has .preview field" test_bedrock_fallback_has_preview
|
||||
run_test ".preview is a JSON object" test_bedrock_fallback_preview_is_object
|
||||
|
||||
print_summary
|
||||
Executable
+205
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for Fabric meta API
|
||||
# Covers: Fabric loader version resolution, installer version, download URL
|
||||
# Usage: ./tests/test_fabric.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}Fabric API Tests (MC ${MC_VERSION})${NC}"
|
||||
echo ""
|
||||
|
||||
# ---------- Fabric loader meta API -----------------------------------------
|
||||
|
||||
test_fabric_meta_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
assert_not_empty "$body" "Fabric meta body should not be empty"
|
||||
assert_json_valid "$body" "Fabric meta should be valid JSON"
|
||||
}
|
||||
|
||||
test_fabric_meta_is_array() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local type
|
||||
type=$(echo "$body" | jq -r 'type')
|
||||
assert_equals "$type" "array" "Response should be a JSON array"
|
||||
}
|
||||
|
||||
test_fabric_meta_has_entries() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local count
|
||||
count=$(echo "$body" | jq 'length')
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: Loader array should have entries, got ${count}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_fabric_loader_entry_has_version() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
assert_json_field "$body" '.[0].loader.version' "Loader entry should have .loader.version"
|
||||
}
|
||||
|
||||
test_fabric_loader_version_is_semver() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.[0].loader.version')
|
||||
|
||||
assert_matches "$version" "^[0-9]+\.[0-9]+\.[0-9]+" \
|
||||
"Loader version '${version}' should be semver-like"
|
||||
}
|
||||
|
||||
test_fabric_entry_has_required_fields() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
assert_json_field "$body" '.[0].loader.version' "Should have loader version"
|
||||
assert_json_field "$body" '.[0].loader.maven' "Should have loader maven coordinate"
|
||||
}
|
||||
|
||||
test_fabric_loader_maven_coordinate() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local maven
|
||||
maven=$(echo "$body" | jq -r '.[0].loader.maven')
|
||||
assert_matches "$maven" "^net.fabricmc:fabric-loader:" \
|
||||
"Maven coordinate should be net.fabricmc:fabric-loader:{version}"
|
||||
}
|
||||
|
||||
test_fabric_loader_has_stable_flag() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local stable
|
||||
stable=$(echo "$body" | jq -r '.[0].loader.stable')
|
||||
assert_matches "$stable" "^(true|false)$" "Stable flag should be true or false"
|
||||
}
|
||||
|
||||
test_fabric_has_intermediary() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
assert_json_field "$body" '.[0].intermediary.version' "Should have intermediary version"
|
||||
}
|
||||
|
||||
# ---------- Fabric installer metadata (Maven XML) -------------------------
|
||||
|
||||
test_fabric_installer_metadata_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
assert_not_empty "$body" "Installer metadata should not be empty"
|
||||
}
|
||||
|
||||
test_fabric_installer_metadata_is_xml() {
|
||||
local body
|
||||
body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
assert_contains "$body" "<metadata>" "Should be valid XML with <metadata> root"
|
||||
}
|
||||
|
||||
test_fabric_installer_has_latest() {
|
||||
local body
|
||||
body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
local latest
|
||||
latest=$(echo "$body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
assert_not_empty "$latest" "Installer should have a latest version"
|
||||
}
|
||||
|
||||
test_fabric_installer_latest_is_semver() {
|
||||
local body
|
||||
body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
local latest
|
||||
latest=$(echo "$body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
|
||||
assert_matches "$latest" "^[0-9]+\.[0-9]+\.[0-9]+" \
|
||||
"Installer version '${latest}' should be semver-like"
|
||||
}
|
||||
|
||||
# ---------- Fabric server jar download URL ---------------------------------
|
||||
|
||||
test_fabric_download_url_format() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local loader_version
|
||||
loader_version=$(echo "$body" | jq -r '.[0].loader.version')
|
||||
|
||||
local installer_body
|
||||
installer_body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
local installer_version
|
||||
installer_version=$(echo "$installer_body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
|
||||
local expected_url="https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${loader_version}/${installer_version}/server/jar"
|
||||
assert_not_empty "$expected_url" "Constructed URL should not be empty"
|
||||
assert_matches "$expected_url" "^https://meta.fabricmc.net/v2/versions/loader/" \
|
||||
"URL should start with Fabric meta base"
|
||||
}
|
||||
|
||||
test_fabric_download_url_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local loader_version
|
||||
loader_version=$(echo "$body" | jq -r '.[0].loader.version')
|
||||
|
||||
local installer_body
|
||||
installer_body=$(api_get "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml")
|
||||
local installer_version
|
||||
installer_version=$(echo "$installer_body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
|
||||
local url="https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${loader_version}/${installer_version}/server/jar"
|
||||
assert_http_ok "$url" "Fabric server jar URL should return HTTP 200"
|
||||
}
|
||||
|
||||
# ---------- Specific version spot checks -----------------------------------
|
||||
|
||||
test_fabric_known_1_20_4() {
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/1.20.4")
|
||||
local count
|
||||
count=$(echo "$body" | jq 'length')
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: Fabric should support MC 1.20.4"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_fabric_latest_loader_version_gte() {
|
||||
# The latest loader version should be >= 0.14.0 (a known old version)
|
||||
local body
|
||||
body=$(api_get "https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.[0].loader.version')
|
||||
|
||||
assert_version_gte "$version" "0.14.0" "Latest loader version should be >= 0.14.0"
|
||||
}
|
||||
|
||||
# ---------- Run all tests --------------------------------------------------
|
||||
|
||||
run_test "Fabric meta API is reachable and valid JSON" test_fabric_meta_reachable
|
||||
run_test "Response is a JSON array" test_fabric_meta_is_array
|
||||
run_test "Loader array has entries" test_fabric_meta_has_entries
|
||||
run_test "Loader entry has .loader.version" test_fabric_loader_entry_has_version
|
||||
run_test "Loader version is semver format" test_fabric_loader_version_is_semver
|
||||
run_test "Loader entry has required fields (version, maven)" test_fabric_entry_has_required_fields
|
||||
run_test "Loader maven coordinate is correct" test_fabric_loader_maven_coordinate
|
||||
run_test "Loader has stable flag" test_fabric_loader_has_stable_flag
|
||||
run_test "Entry has intermediary version" test_fabric_has_intermediary
|
||||
run_test "Installer maven-metadata.xml is reachable" test_fabric_installer_metadata_reachable
|
||||
run_test "Metadata is valid XML" test_fabric_installer_metadata_is_xml
|
||||
run_test "Metadata has latest version" test_fabric_installer_has_latest
|
||||
run_test "Installer latest version is semver" test_fabric_installer_latest_is_semver
|
||||
run_test "Download URL format is correct" test_fabric_download_url_format
|
||||
run_test "Download URL returns HTTP 200" test_fabric_download_url_reachable
|
||||
run_test "Fabric supports MC 1.20.4" test_fabric_known_1_20_4
|
||||
run_test "Latest loader version >= 0.14.0" test_fabric_latest_loader_version_gte
|
||||
|
||||
print_summary
|
||||
Executable
+236
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for Minecraft Forge API
|
||||
# Covers: Forge promotions API, version resolution, download URL, old/new forge logic
|
||||
# Usage: ./tests/test_forge.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}Forge API Tests (MC ${MC_VERSION})${NC}"
|
||||
echo ""
|
||||
|
||||
# ---------- Forge promotions API -------------------------------------------
|
||||
|
||||
test_forge_promotions_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
assert_not_empty "$body" "Promotions body should not be empty"
|
||||
assert_json_valid "$body" "Promotions should be valid JSON"
|
||||
}
|
||||
|
||||
test_forge_promotions_has_promos() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
assert_json_field "$body" '.promos' "Should have .promos field"
|
||||
}
|
||||
|
||||
test_forge_promos_is_object() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local type
|
||||
type=$(echo "$body" | jq -r '.promos | type')
|
||||
assert_equals "$type" "object" ".promos should be a JSON object"
|
||||
}
|
||||
|
||||
test_forge_promos_has_known_key() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local known_key="1.20.4-latest"
|
||||
local found
|
||||
found=$(echo "$body" | jq -r ".promos[\"${known_key}\"] // empty")
|
||||
|
||||
if [[ -n "$found" && "$found" != "null" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: promos should have '1.20.4-latest' key"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_forge_promos_value_format() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local known_key="1.20.4-latest"
|
||||
local version
|
||||
version=$(echo "$body" | jq -r ".promos[\"${known_key}\"] // empty")
|
||||
|
||||
# Forge versions look like "20.4.234" or "14.23.5.2860"
|
||||
assert_matches "$version" "^[0-9]+\.[0-9]+\.[0-9]+" \
|
||||
"Forge version '${version}' should look like x.y.z"
|
||||
}
|
||||
|
||||
# ---------- Forge download URL construction --------------------------------
|
||||
|
||||
test_forge_download_url_format() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.promos["1.20.4-latest"] // empty')
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Could not determine Forge version for 1.20.4"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local expected_url="https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.4-${version}/forge-1.20.4-${version}-installer.jar"
|
||||
assert_not_empty "$expected_url" "Download URL should not be empty"
|
||||
assert_matches "$expected_url" "maven.minecraftforge.net.*installer\.jar" \
|
||||
"URL should point to Forge maven installer"
|
||||
}
|
||||
|
||||
test_forge_download_url_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.promos["1.20.4-latest"] // empty')
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Could not determine Forge version for 1.20.4"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local url="https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.4-${version}/forge-1.20.4-${version}-installer.jar"
|
||||
assert_http_ok "$url" "Forge installer URL should return HTTP 200"
|
||||
}
|
||||
|
||||
# ---------- Old vs New Forge version logic ---------------------------------
|
||||
# Forge >= 1.17.1 uses the new launcher (unix_args.txt)
|
||||
# Forge < 1.17.1 uses the old launcher (server.jar symlink)
|
||||
|
||||
test_forge_new_version_boundary() {
|
||||
# This replicates the logic from common.sh newForge()
|
||||
local test_version="1.17.1"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
assert_equals "$result" "1.17.1" "1.17.1 >= 1.17.1 (boundary case)"
|
||||
}
|
||||
|
||||
test_forge_old_version_is_old() {
|
||||
local test_version="1.16.5"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
# When sort -V puts test_version first, it's < 1.17.1
|
||||
assert_equals "$result" "$test_version" "1.16.5 < 1.17.1 (old forge)"
|
||||
}
|
||||
|
||||
test_forge_new_version_is_new() {
|
||||
local test_version="1.20.4"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
# When sort -V puts 1.17.1 first, test_version is >= 1.17.1
|
||||
assert_equals "$result" "1.17.1" "1.20.4 >= 1.17.1 (new forge)"
|
||||
}
|
||||
|
||||
test_forge_1_17_exactly_is_old() {
|
||||
local test_version="1.17"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
# 1.17 < 1.17.1
|
||||
assert_equals "$result" "$test_version" "1.17 < 1.17.1 (old forge, edge case)"
|
||||
}
|
||||
|
||||
test_forge_1_18_is_new() {
|
||||
local test_version="1.18"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
assert_equals "$result" "1.17.1" "1.18 >= 1.17.1 (new forge)"
|
||||
}
|
||||
|
||||
test_forge_1_21_is_new() {
|
||||
local test_version="1.21.1"
|
||||
local result
|
||||
result=$(printf '%s\n' "1.17.1" "$test_version" | sort -V | head -n1)
|
||||
assert_equals "$result" "1.17.1" "1.21.1 >= 1.17.1 (new forge)"
|
||||
}
|
||||
|
||||
# ---------- Forge Maven metadata -------------------------------------------
|
||||
|
||||
test_forge_maven_metadata_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml")
|
||||
assert_not_empty "$body" "Forge maven metadata should be reachable"
|
||||
}
|
||||
|
||||
test_forge_maven_metadata_is_xml() {
|
||||
local body
|
||||
body=$(api_get "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml")
|
||||
assert_contains "$body" "<metadata>" "Should be valid XML"
|
||||
}
|
||||
|
||||
test_forge_maven_has_versions() {
|
||||
local body
|
||||
body=$(api_get "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml")
|
||||
assert_contains "$body" "<versions>" "Should have <versions> element"
|
||||
}
|
||||
|
||||
# ---------- Forge version resolution for specific MC versions --------------
|
||||
|
||||
test_forge_supports_1_20_4() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.promos["1.20.4-latest"] // empty')
|
||||
assert_not_empty "$version" "Forge should support MC 1.20.4"
|
||||
}
|
||||
|
||||
test_forge_supports_1_19_2() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.promos["1.19.2-latest"] // empty')
|
||||
assert_not_empty "$version" "Forge should support MC 1.19.2"
|
||||
}
|
||||
|
||||
test_forge_supports_1_12_2() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r '.promos["1.12.2-latest"] // empty')
|
||||
assert_not_empty "$version" "Forge should support MC 1.12.2 (classic version)"
|
||||
}
|
||||
|
||||
test_forge_latest_matches_current_mc() {
|
||||
local body
|
||||
body=$(api_get "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json")
|
||||
local version
|
||||
version=$(echo "$body" | jq -r ".promos[\"${MC_VERSION}-latest\"] // empty")
|
||||
# This may not exist for all MC versions, so we treat it as informational
|
||||
if [[ -n "$version" && "$version" != "null" ]]; then
|
||||
echo "Forge ${MC_VERSION}-latest = ${version}"
|
||||
return 0
|
||||
else
|
||||
echo "No forge ${MC_VERSION}-latest in promos (may be expected)"
|
||||
return 77 # skip
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- Run all tests --------------------------------------------------
|
||||
|
||||
run_test "Forge promotions API is reachable" test_forge_promotions_reachable
|
||||
run_test "Promotions JSON has .promos field" test_forge_promotions_has_promos
|
||||
run_test ".promos is a JSON object" test_forge_promos_is_object
|
||||
run_test "Promos has '1.20.4-latest' key" test_forge_promos_has_known_key
|
||||
run_test "Promos value has Forge version format" test_forge_promos_value_format
|
||||
run_test "Download URL matches Forge maven pattern" test_forge_download_url_format
|
||||
run_test "Download URL returns HTTP 200" test_forge_download_url_reachable
|
||||
run_test "1.17.1 boundary case (new forge)" test_forge_new_version_boundary
|
||||
run_test "1.16.5 is old forge" test_forge_old_version_is_old
|
||||
run_test "1.20.4 is new forge" test_forge_new_version_is_new
|
||||
run_test "1.17 is old forge (edge case)" test_forge_1_17_exactly_is_old
|
||||
run_test "1.18 is new forge" test_forge_1_18_is_new
|
||||
run_test "1.21.1 is new forge" test_forge_1_21_is_new
|
||||
run_test "Forge maven metadata is reachable" test_forge_maven_metadata_reachable
|
||||
run_test "Maven metadata is valid XML" test_forge_maven_metadata_is_xml
|
||||
run_test "Maven metadata has versions" test_forge_maven_has_versions
|
||||
run_test "Forge supports MC 1.20.4" test_forge_supports_1_20_4
|
||||
run_test "Forge supports MC 1.19.2" test_forge_supports_1_19_2
|
||||
run_test "Forge supports MC 1.12.2 (classic)" test_forge_supports_1_12_2
|
||||
run_test "Forge latest matches current MC version" test_forge_latest_matches_current_mc
|
||||
|
||||
print_summary
|
||||
Executable
+243
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for Mojang version manifest API
|
||||
# Covers: vanilla server downloads, Java version detection
|
||||
# Usage: ./tests/test_mojang.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}Mojang API Tests (MC ${MC_VERSION})${NC}"
|
||||
echo ""
|
||||
|
||||
# ---------- Mojang manifest ------------------------------------------------
|
||||
|
||||
test_manifest_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
assert_not_empty "$body" "Manifest body should not be empty"
|
||||
assert_json_valid "$body" "Manifest should be valid JSON"
|
||||
}
|
||||
|
||||
test_manifest_has_latest() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
assert_json_field "$body" '.latest.release' "Manifest should have .latest.release"
|
||||
assert_json_field "$body" '.latest.snapshot' "Manifest should have .latest.snapshot"
|
||||
}
|
||||
|
||||
test_manifest_has_versions_array() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
local count
|
||||
count=$(echo "$body" | jq '.versions | length')
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: versions array should have entries, got ${count}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_manifest_version_entry_format() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
# Each version entry should have id, type, url, time, releaseTime
|
||||
local first_entry
|
||||
first_entry=$(echo "$body" | jq '.versions[0]')
|
||||
assert_json_field "$body" '.versions[0].id' "Version entry should have .id"
|
||||
assert_json_field "$body" '.versions[0].type' "Version entry should have .type"
|
||||
assert_json_field "$body" '.versions[0].url' "Version entry should have .url"
|
||||
}
|
||||
|
||||
test_manifest_contains_mc_version() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
local found
|
||||
found=$(echo "$body" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .id")
|
||||
assert_equals "$found" "$MC_VERSION" "Manifest should contain MC ${MC_VERSION}"
|
||||
}
|
||||
|
||||
# ---------- Version data (per-version manifest) ----------------------------
|
||||
|
||||
get_version_url() {
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
echo "$body" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url"
|
||||
}
|
||||
|
||||
test_version_data_reachable() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
assert_not_empty "$version_url" "Version URL should exist for ${MC_VERSION}"
|
||||
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
assert_not_empty "$body" "Version data body should not be empty"
|
||||
assert_json_valid "$body" "Version data should be valid JSON"
|
||||
}
|
||||
|
||||
test_version_data_has_downloads() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
assert_json_field "$body" '.downloads.server.url' "Version data should have .downloads.server.url"
|
||||
}
|
||||
|
||||
test_version_data_has_java_version() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
assert_json_field "$body" '.javaVersion.majorVersion' "Version data should have .javaVersion.majorVersion"
|
||||
}
|
||||
|
||||
test_version_data_has_client() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
assert_json_field "$body" '.downloads.client.url' "Version data should have .downloads.client.url"
|
||||
}
|
||||
|
||||
# ---------- Server jar URL validation --------------------------------------
|
||||
|
||||
test_server_jar_url_format() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
local server_url
|
||||
server_url=$(echo "$body" | jq -r '.downloads.server.url')
|
||||
|
||||
assert_matches "$server_url" "^https://(.*minecraft\\.net|piston-data\\.mojang\\.com)/" \
|
||||
"Server URL should be a Mojang CDN or piston-data URL"
|
||||
}
|
||||
|
||||
test_server_jar_url_reachable() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
local server_url
|
||||
server_url=$(echo "$body" | jq -r '.downloads.server.url')
|
||||
|
||||
assert_http_ok "$server_url" "Server jar URL should return HTTP 200"
|
||||
}
|
||||
|
||||
# ---------- Java version detection -----------------------------------------
|
||||
|
||||
test_java_version_is_numeric() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
local java_major
|
||||
java_major=$(echo "$body" | jq -r '.javaVersion.majorVersion')
|
||||
|
||||
assert_matches "$java_major" "^[0-9]+$" "Java major version should be numeric"
|
||||
}
|
||||
|
||||
test_java_version_known_values() {
|
||||
local version_url
|
||||
version_url=$(get_version_url)
|
||||
local body
|
||||
body=$(api_get "$version_url")
|
||||
local java_major
|
||||
java_major=$(echo "$body" | jq -r '.javaVersion.majorVersion')
|
||||
|
||||
case "$java_major" in
|
||||
8|16|17|21) return 0 ;;
|
||||
*)
|
||||
echo "ASSERT FAILED: Java version ${java_major} is not a known expected value (8,16,17,21)"
|
||||
echo " This may be valid for a new MC release, but verify it."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------- Specific version spot checks -----------------------------------
|
||||
|
||||
test_known_old_version_java_8() {
|
||||
# MC 1.16.5 should use Java 8
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
local version_url
|
||||
version_url=$(echo "$body" | jq -r '.versions[] | select(.id == "1.16.5") | .url')
|
||||
|
||||
if [[ -z "$version_url" || "$version_url" == "null" ]]; then
|
||||
echo "Could not fetch 1.16.5 version data"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local vdata
|
||||
vdata=$(api_get "$version_url")
|
||||
local java_major
|
||||
java_major=$(echo "$vdata" | jq -r '.javaVersion.majorVersion')
|
||||
assert_equals "$java_major" "8" "MC 1.16.5 should use Java 8"
|
||||
}
|
||||
|
||||
test_known_new_version_java_21() {
|
||||
# MC 1.20.5+ should use Java 21
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
local version_url
|
||||
version_url=$(echo "$body" | jq -r '.versions[] | select(.id == "1.21.1") | .url')
|
||||
|
||||
if [[ -z "$version_url" || "$version_url" == "null" ]]; then
|
||||
echo "Could not fetch 1.21.1 version data"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local vdata
|
||||
vdata=$(api_get "$version_url")
|
||||
local java_major
|
||||
java_major=$(echo "$vdata" | jq -r '.javaVersion.majorVersion')
|
||||
assert_equals "$java_major" "21" "MC 1.21.1 should use Java 21"
|
||||
}
|
||||
|
||||
test_known_1_18_version_java_17() {
|
||||
# MC 1.18.x should use Java 17
|
||||
local body
|
||||
body=$(api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||
local version_url
|
||||
version_url=$(echo "$body" | jq -r '.versions[] | select(.id == "1.18.2") | .url')
|
||||
|
||||
if [[ -z "$version_url" || "$version_url" == "null" ]]; then
|
||||
echo "Could not fetch 1.18.2 version data"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local vdata
|
||||
vdata=$(api_get "$version_url")
|
||||
local java_major
|
||||
java_major=$(echo "$vdata" | jq -r '.javaVersion.majorVersion')
|
||||
assert_equals "$java_major" "17" "MC 1.18.2 should use Java 17"
|
||||
}
|
||||
|
||||
# ---------- Run all tests --------------------------------------------------
|
||||
|
||||
run_test "Mojang manifest is reachable and valid JSON" test_manifest_reachable
|
||||
run_test "Manifest has .latest.release and .latest.snapshot" test_manifest_has_latest
|
||||
run_test "Manifest .versions array is non-empty" test_manifest_has_versions_array
|
||||
run_test "Manifest version entries have required fields" test_manifest_version_entry_format
|
||||
run_test "Manifest contains MC ${MC_VERSION}" test_manifest_contains_mc_version
|
||||
run_test "Version data for ${MC_VERSION} is reachable" test_version_data_reachable
|
||||
run_test "Version data has .downloads.server.url" test_version_data_has_downloads
|
||||
run_test "Version data has .javaVersion.majorVersion" test_version_data_has_java_version
|
||||
run_test "Version data has .downloads.client.url" test_version_data_has_client
|
||||
run_test "Server jar URL matches Mojang CDN pattern" test_server_jar_url_format
|
||||
run_test "Server jar URL returns HTTP 200" test_server_jar_url_reachable
|
||||
run_test "Java version is numeric" test_java_version_is_numeric
|
||||
run_test "Java version is a known value (8/16/17/21)" test_java_version_known_values
|
||||
run_test "MC 1.16.5 uses Java 8" test_known_old_version_java_8
|
||||
run_test "MC 1.21.1 uses Java 21" test_known_new_version_java_21
|
||||
run_test "MC 1.18.2 uses Java 17" test_known_1_18_version_java_17
|
||||
|
||||
print_summary
|
||||
Executable
+244
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for NeoForge API
|
||||
# Covers: NeoForge maven metadata, version resolution, download URL
|
||||
# Usage: ./tests/test_neoforge.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}NeoForge API Tests (MC ${MC_VERSION})${NC}"
|
||||
echo ""
|
||||
|
||||
# ---------- NeoForge Maven metadata ----------------------------------------
|
||||
|
||||
test_neoforge_metadata_reachable() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
assert_not_empty "$body" "NeoForge maven metadata should be reachable"
|
||||
}
|
||||
|
||||
test_neoforge_metadata_is_xml() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
assert_contains "$body" "<metadata>" "Should be valid XML"
|
||||
}
|
||||
|
||||
test_neoforge_metadata_has_versions() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
assert_contains "$body" "<versions>" "Should have <versions> element"
|
||||
}
|
||||
|
||||
test_neoforge_metadata_has_latest() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local latest
|
||||
latest=$(echo "$body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
assert_not_empty "$latest" "Metadata should have a latest version"
|
||||
}
|
||||
|
||||
test_neoforge_metadata_has_release() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local release
|
||||
release=$(echo "$body" | xmllint --xpath "/metadata/versioning/release/text()" - 2>/dev/null)
|
||||
assert_not_empty "$release" "Metadata should have a release version"
|
||||
}
|
||||
|
||||
# ---------- NeoForge version resolution ------------------------------------
|
||||
# NeoForge versions start with MC minor version (e.g., MC 1.20.4 -> 20.4.x)
|
||||
|
||||
test_neoforge_version_prefix() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
# Extract MC minor from version (e.g., "1.21.1" -> "21.1")
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
|
||||
# Check that there are versions matching this MC minor prefix
|
||||
local matching
|
||||
matching=$(echo "$versions" | tr ' ' '\n' | grep "^${mc_minor}\." | head -1)
|
||||
|
||||
assert_not_empty "$matching" \
|
||||
"Should find at least one NeoForge version starting with ${mc_minor}"
|
||||
}
|
||||
|
||||
test_neoforge_stable_versions_exist() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
local stable_count
|
||||
stable_count=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep -c "^${mc_minor}\." || true)
|
||||
|
||||
if [[ "$stable_count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: No stable NeoForge versions found for MC minor ${mc_minor}"
|
||||
echo " This may be valid for a very new MC version"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_neoforge_no_beta_in_stable() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
local stable_versions
|
||||
stable_versions=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep "^${mc_minor}\." | sort -V | tail -5)
|
||||
|
||||
# None of the stable versions should contain beta or alpha
|
||||
local bad_count
|
||||
bad_count=$(echo "$stable_versions" | grep -c -- '-beta\|-alpha' || true)
|
||||
assert_equals "$bad_count" "0" "Stable versions should not contain beta/alpha"
|
||||
}
|
||||
|
||||
test_neoforge_latest_for_mc_is_stable() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
local latest_for_mc
|
||||
latest_for_mc=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep "^${mc_minor}\." | sort -V | tail -1)
|
||||
|
||||
if [[ -z "$latest_for_mc" ]]; then
|
||||
echo "No stable NeoForge version found for MC ${MC_VERSION}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local bad=$(echo "$latest_for_mc" | grep -c -- '-beta\|-alpha' || true)
|
||||
assert_equals "$bad" "0" "Latest stable version for MC ${MC_VERSION} (${latest_for_mc}) should not contain beta/alpha"
|
||||
}
|
||||
|
||||
# ---------- NeoForge download URL ------------------------------------------
|
||||
|
||||
_neoforge_latest_for_mc() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep "^${mc_minor}\." | sort -V | tail -1
|
||||
}
|
||||
|
||||
test_neoforge_download_url_format() {
|
||||
local latest
|
||||
latest=$(_neoforge_latest_for_mc)
|
||||
|
||||
if [[ -z "$latest" ]]; then
|
||||
echo "No stable NeoForge version found for MC ${MC_VERSION}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local expected_url="https://maven.neoforged.net/releases/net/neoforged/neoforge/${latest}/neoforge-${latest}-installer.jar"
|
||||
assert_matches "$expected_url" "maven.neoforged.net.*installer\.jar" \
|
||||
"URL should point to NeoForge maven installer"
|
||||
}
|
||||
|
||||
test_neoforge_download_url_reachable() {
|
||||
local latest
|
||||
latest=$(_neoforge_latest_for_mc)
|
||||
|
||||
if [[ -z "$latest" ]]; then
|
||||
echo "No stable NeoForge version found for MC ${MC_VERSION}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local url="https://maven.neoforged.net/releases/net/neoforged/neoforge/${latest}/neoforge-${latest}-installer.jar"
|
||||
assert_http_ok "$url" "NeoForge installer URL should return HTTP 200"
|
||||
}
|
||||
|
||||
# ---------- NeoForge version sort check ------------------------------------
|
||||
|
||||
test_neoforge_versions_sorted_ascending() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local mc_minor="${MC_VERSION#1.}"
|
||||
local last_version
|
||||
last_version=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep "^${mc_minor}\." | sort -V | tail -1)
|
||||
|
||||
# The last version after sort -V should be the same as the latest from metadata
|
||||
local meta_latest
|
||||
meta_latest=$(echo "$body" | xmllint --xpath "/metadata/versioning/latest/text()" - 2>/dev/null)
|
||||
|
||||
if [[ "$last_version" == "$meta_latest" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: sort -V last version (${last_version}) != metadata latest (${meta_latest})"
|
||||
echo " This is informational - metadata latest may include beta"
|
||||
return 77 # skip
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- Specific MC version support ------------------------------------
|
||||
|
||||
test_neoforge_supports_1_21_1() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local count
|
||||
count=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep -c "^21.1\." || true)
|
||||
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: No NeoForge versions found for MC 1.21.1 (21.1.x)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_neoforge_supports_1_20_4() {
|
||||
local body
|
||||
body=$(api_get "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml")
|
||||
local versions
|
||||
versions=$(echo "$body" | xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null)
|
||||
|
||||
local count
|
||||
count=$(echo "$versions" | tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep -c "^20.4\." || true)
|
||||
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: No NeoForge versions found for MC 1.20.4 (20.4.x)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- Run all tests --------------------------------------------------
|
||||
|
||||
run_test "NeoForge maven metadata is reachable" test_neoforge_metadata_reachable
|
||||
run_test "Metadata is valid XML" test_neoforge_metadata_is_xml
|
||||
run_test "Metadata has <versions> element" test_neoforge_metadata_has_versions
|
||||
run_test "Metadata has <latest> version" test_neoforge_metadata_has_latest
|
||||
run_test "Metadata has <release> version" test_neoforge_metadata_has_release
|
||||
run_test "Versions exist with MC minor prefix" test_neoforge_version_prefix
|
||||
run_test "Stable versions exist for current MC" test_neoforge_stable_versions_exist
|
||||
run_test "Stable versions have no beta/alpha tags" test_neoforge_no_beta_in_stable
|
||||
run_test "Latest stable version for MC is stable" test_neoforge_latest_for_mc_is_stable
|
||||
run_test "Download URL matches NeoForge maven pattern" test_neoforge_download_url_format
|
||||
run_test "Download URL returns HTTP 200" test_neoforge_download_url_reachable
|
||||
run_test "Versions are sorted in ascending order" test_neoforge_versions_sorted_ascending
|
||||
run_test "NeoForge supports MC 1.21.1 (21.1.x)" test_neoforge_supports_1_21_1
|
||||
run_test "NeoForge supports MC 1.20.4 (20.4.x)" test_neoforge_supports_1_20_4
|
||||
|
||||
print_summary
|
||||
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
|
||||
Executable
+281
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared test utilities for Dockercraft API tests
|
||||
# Source this file in individual test scripts: source "$(dirname "$0")/test_utils.sh"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------- colors ---------------------------------------------------------
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
|
||||
fi
|
||||
|
||||
# ---------- counters -------------------------------------------------------
|
||||
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
TESTS_SKIPPED=0
|
||||
FAILED_TESTS=()
|
||||
|
||||
# ---------- test framework -------------------------------------------------
|
||||
|
||||
# Run a test function and track results
|
||||
# Usage: run_test "test name" test_function_name
|
||||
# Exit codes: 0=pass, 1=fail, 77=skip
|
||||
# IMPORTANT: Test functions MUST use `exit 77` (not `return 77`) to signal skip.
|
||||
# `return 77` does not propagate through command substitution with `set -e`.
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local func="$2"
|
||||
local extra_args="${3:-}"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
|
||||
# Run the test function, capturing output
|
||||
local output
|
||||
local exit_code=0
|
||||
if [[ -n "$extra_args" ]]; then
|
||||
output=$("$func" "$extra_args" 2>&1) || exit_code=$?
|
||||
else
|
||||
output=$("$func" 2>&1) || exit_code=$?
|
||||
fi
|
||||
|
||||
if [[ $exit_code -eq 0 ]]; then
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
echo -e " ${GREEN}✓${NC} ${name}"
|
||||
if [[ -n "$output" ]]; then
|
||||
echo -e " ${output}"
|
||||
fi
|
||||
elif [[ $exit_code -eq 77 ]]; then
|
||||
TESTS_SKIPPED=$((TESTS_SKIPPED + 1))
|
||||
echo -e " ${YELLOW}⊘${NC} ${name} ${YELLOW}(skipped)${NC}"
|
||||
if [[ -n "$output" ]]; then
|
||||
echo -e " ${output}"
|
||||
fi
|
||||
else
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
FAILED_TESTS+=("$name")
|
||||
echo -e " ${RED}✗${NC} ${name}"
|
||||
if [[ -n "$output" ]]; then
|
||||
echo -e " ${output}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a string contains a substring
|
||||
# Usage: assert_contains "$haystack" "$needle" ["message"]
|
||||
assert_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local message="${3:-Expected to contain '${needle}'}"
|
||||
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " Got: ${haystack:0:200}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a string matches a regex
|
||||
# Usage: assert_matches "$string" "$regex" ["message"]
|
||||
assert_matches() {
|
||||
local string="$1"
|
||||
local regex="$2"
|
||||
local message="${3:-Expected to match regex '${regex}'}"
|
||||
|
||||
if [[ "$string" =~ $regex ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " Got: ${string:0:200}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that two strings are equal
|
||||
# Usage: assert_equals "$actual" "$expected" ["message"]
|
||||
assert_equals() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local message="${3:-Expected '${expected}', got '${actual}'}"
|
||||
|
||||
if [[ "$actual" == "$expected" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " Expected: ${expected:0:200}"
|
||||
echo " Got: ${actual:0:200}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a string is not empty
|
||||
# Usage: assert_not_empty "$value" ["message"]
|
||||
assert_not_empty() {
|
||||
local value="$1"
|
||||
local message="${2:-Expected non-empty value}"
|
||||
|
||||
if [[ -n "$value" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a string is empty
|
||||
# Usage: assert_empty "$value" ["message"]
|
||||
assert_empty() {
|
||||
local value="$1"
|
||||
local message="${2:-Expected empty value}"
|
||||
|
||||
if [[ -z "$value" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " Got: ${value:0:200}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a JSON field exists and is non-null
|
||||
# Usage: assert_json_field "$json" "$jq_expr" ["message"]
|
||||
assert_json_field() {
|
||||
local json="$1"
|
||||
local jq_expr="$2"
|
||||
local message="${3:-Expected JSON field '${jq_expr}' to exist}"
|
||||
|
||||
local value
|
||||
value=$(echo "$json" | jq -r "$jq_expr" 2>/dev/null) || true
|
||||
|
||||
if [[ -n "$value" && "$value" != "null" && "$value" != "" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " jq expression: ${jq_expr}"
|
||||
echo " Result: ${value:-<empty>}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that a URL returns HTTP 200
|
||||
# Usage: assert_http_ok "$url" ["message"]
|
||||
assert_http_ok() {
|
||||
local url="$1"
|
||||
local message="${2:-Expected HTTP 200 from ${url}}"
|
||||
|
||||
local http_code
|
||||
http_code=$(curl -sL -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 30 "$url") || true
|
||||
|
||||
if [[ "$http_code" == "200" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " URL: ${url}"
|
||||
echo " HTTP status: ${http_code}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that JSON parses successfully and has a given top-level key
|
||||
# Usage: assert_json_valid "$json" ["message"]
|
||||
assert_json_valid() {
|
||||
local json="$1"
|
||||
local message="${2:-Expected valid JSON}"
|
||||
|
||||
if echo "$json" | jq empty 2>/dev/null; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " First 200 chars: ${json:0:200}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Version comparison: assert version A >= version B
|
||||
# Usage: assert_version_gte "$version_a" "$version_b"
|
||||
assert_version_gte() {
|
||||
local v_a="$1"
|
||||
local v_b="$2"
|
||||
local message="${3:-Expected ${v_a} >= ${v_b}}"
|
||||
|
||||
local result
|
||||
result=$(printf '%s\n' "$v_b" "$v_a" | sort -V | head -n1)
|
||||
|
||||
if [[ "$result" == "$v_b" ]]; then
|
||||
return 0
|
||||
else
|
||||
echo "ASSERT FAILED: ${message}"
|
||||
echo " ${v_a} is not >= ${v_b}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Version comparison: assert version A <= version B
|
||||
assert_version_lte() {
|
||||
assert_version_gte "$2" "$1" "${3:-Expected ${1} <= ${2}}"
|
||||
}
|
||||
|
||||
# ---------- API helpers ----------------------------------------------------
|
||||
|
||||
# Fetch a URL and return the body, or empty string on failure
|
||||
# Usage: api_get "$url"
|
||||
api_get() {
|
||||
curl -sf --connect-timeout 10 --max-time 30 "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Fetch a URL with custom headers and return the body
|
||||
# Usage: api_get_ua "$url" ["user_agent"]
|
||||
api_get_ua() {
|
||||
local ua="${2:-Mozilla/5.0 (X11; Linux x86_64)}"
|
||||
curl -sL -A "$ua" --connect-timeout 10 --max-time 30 "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get the latest MC version from Mojang manifest
|
||||
# Usage: get_latest_mc_version
|
||||
get_latest_mc_version() {
|
||||
api_get "https://launchermeta.mojang.com/mc/game/version_manifest.json" |
|
||||
jq -r '.latest.release'
|
||||
}
|
||||
|
||||
# ---------- summary --------------------------------------------------------
|
||||
|
||||
print_summary() {
|
||||
echo ""
|
||||
echo -e "${BOLD}========================================${NC}"
|
||||
echo -e "${BOLD} Test Results${NC}"
|
||||
echo -e "${BOLD}========================================${NC}"
|
||||
echo -e " Total: ${TESTS_RUN}"
|
||||
echo -e " ${GREEN}Passed: ${TESTS_PASSED}${NC}"
|
||||
if [[ $TESTS_FAILED -gt 0 ]]; then
|
||||
echo -e " ${RED}Failed: ${TESTS_FAILED}${NC}"
|
||||
else
|
||||
echo -e " Failed: 0"
|
||||
fi
|
||||
if [[ $TESTS_SKIPPED -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}Skipped: ${TESTS_SKIPPED}${NC}"
|
||||
fi
|
||||
echo -e "${BOLD}========================================${NC}"
|
||||
|
||||
if [[ $TESTS_FAILED -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${RED}Failed tests:${NC}"
|
||||
for t in "${FAILED_TESTS[@]}"; do
|
||||
echo -e " ${RED}✗ ${t}${NC}"
|
||||
done
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "\n${GREEN}All tests passed!${NC}"
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user