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
+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