Compare commits
15
Commits
ec9c48ac68
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5688b30892
|
||
|
|
a6f610de28
|
||
|
|
6e13a0aa15
|
||
|
|
2d46f91c60
|
||
|
|
0df24cb64a
|
||
|
|
f871915c08
|
||
|
|
9c8bc802a8
|
||
|
|
d78cfd39c1
|
||
|
|
2223ea9969
|
||
|
|
527dd5b4e7
|
||
|
|
4c155584af
|
||
|
|
5383662f9f
|
||
|
|
feca6cfa83
|
||
|
|
b0c1b07499
|
||
|
|
744bd61ee6
|
@@ -0,0 +1,22 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
[*.{sh,bash}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Dockerfile]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Build dev image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to local Gitea registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REGISTRY }}
|
||||
username: ${{ vars.ACTIONS_USER }}
|
||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||
- name: Docker Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ vars.REGISTRY }}/${{ gitea.repository }}:dev
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -25,3 +25,5 @@ jobs:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ vars.REGISTRY }}/${{ gitea.repository }}:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
+16
@@ -1 +1,17 @@
|
||||
# Runtime data
|
||||
data/
|
||||
|
||||
# Editor/IDE
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vscode/
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Test artifacts
|
||||
*.log
|
||||
|
||||
+20
-10
@@ -1,13 +1,16 @@
|
||||
FROM ubuntu:noble
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
|
||||
LABEL org.opencontainers.image.title="Dockercraft" \
|
||||
org.opencontainers.image.description="Docker image for running Minecraft servers with support for multiple loaders" \
|
||||
org.opencontainers.image.source="https://github.com/jeremymcclure/dockercraft"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# install prerequisites
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
@@ -15,23 +18,30 @@ RUN apt-get update && \
|
||||
jq \
|
||||
libxml2-utils \
|
||||
sudo \
|
||||
tini && \
|
||||
apt-get autoremove && apt-get clean && \
|
||||
tini \
|
||||
unzip \
|
||||
netcat-openbsd && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Remove default ubuntu user
|
||||
RUN userdel -r ubuntu && \
|
||||
useradd --create-home -u 1000 minecraft && \
|
||||
RUN groupadd -g ${GID} minecraft && \
|
||||
useradd --create-home -u ${UID} -g ${GID} minecraft && \
|
||||
echo 'minecraft ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \
|
||||
mkdir -p /data && \
|
||||
chown -R ${UID}:${GID} /data
|
||||
|
||||
# Install Jabba (Java version manager) as minecraft user
|
||||
RUN su -c "curl -sL https://github.com/Jabba-Team/jabba/raw/main/install.sh | bash && . ~/.jabba/jabba.sh" minecraft
|
||||
|
||||
COPY ./scripts/init /
|
||||
RUN chmod +x /init
|
||||
COPY ./scripts/ /scripts/
|
||||
RUN chmod +x /scripts/init
|
||||
|
||||
EXPOSE 25565/tcp 19132/udp 19133/udp
|
||||
STOPSIGNAL SIGTERM
|
||||
|
||||
USER minecraft
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/init"]
|
||||
HEALTHCHECK --start-period=2m --interval=30s --timeout=10s --retries=3 \
|
||||
CMD bash -c 'if [ "${MC_LOADER}" = "bedrock" ]; then nc -zu 127.0.0.1 19132; else nc -z 127.0.0.1 25565; fi'
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init"]
|
||||
|
||||
@@ -1 +1,215 @@
|
||||
TODO
|
||||
# Dockercraft
|
||||
|
||||
A Docker image for running Minecraft servers with support for multiple loaders.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-loader support** — Vanilla, Paper, Fabric, Forge, NeoForge, and Bedrock
|
||||
- **Auto-detection** — Java version and loader versions are auto-detected from Mojang manifests
|
||||
- **Persistent data** — Server files, worlds, and Java installations are stored in `/data`
|
||||
- **Health checks** — Built-in container health monitoring
|
||||
- **Flexible Java** — Pin or auto-detect Java version (Temurin, Zulu, GraalVM, etc.)
|
||||
- **Bedrock support** — Native Bedrock Dedicated Server without Java
|
||||
|
||||
## Quick Start
|
||||
|
||||
```yaml
|
||||
services:
|
||||
minecraft:
|
||||
image: git.jeremymcclure.com/jeremy/dockercraft:latest
|
||||
container_name: minecraft
|
||||
stdin_open: true
|
||||
tty: true
|
||||
environment:
|
||||
- EULA=true
|
||||
- MC_VERSION=1.21.1
|
||||
- MC_LOADER=neoforge
|
||||
- MC_LOADER_VERSION=21.1.185
|
||||
- JAVA_VERSION=temurin@21
|
||||
- XMS=2G
|
||||
- XMX=4G
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
volumes:
|
||||
- ./data:/data
|
||||
ports:
|
||||
- 25565:25565
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `EULA` | — | **Required.** Set to `true` to accept the Minecraft EULA |
|
||||
| `MC_VERSION` | — | Minecraft version (e.g., `1.21.1`). Required for Java loaders; auto-detected for Bedrock |
|
||||
| `MC_LOADER` | — | **Required.** Server loader: `vanilla`, `paper`, `fabric`, `forge`, `neoforge`, or `bedrock` |
|
||||
| `MC_LOADER_VERSION` | — | Fallback version for any loader |
|
||||
| `FABRIC_LOADER_VERSION` | `$MC_LOADER_VERSION` | Fabric loader version |
|
||||
| `FORGE_VERSION` | `$MC_LOADER_VERSION` | Forge version (e.g., `52.1.14`) |
|
||||
| `NEOFORGE_VERSION` | `$MC_LOADER_VERSION` | NeoForge version (e.g., `21.1.185`) |
|
||||
| `PAPER_BUILD` | latest | Paper build number (auto-detects latest stable if unset) |
|
||||
| `JAVA_VERSION` | auto-detected | Java version (via Jabba, e.g., `temurin@21`, `zulu@17`). Auto-detects from Mojang manifest if unset: 1.20.5+→21, 1.17-1.20.4→17, 1.17→16, pre-1.17→8 |
|
||||
| `JAR` | `server.jar` | Server jar filename |
|
||||
| `XMS` | `2G` | Initial Java heap size |
|
||||
| `XMX` | `4G` | Maximum Java heap size |
|
||||
| `ADD_ARGS` | — | Additional Java arguments |
|
||||
| `PUID` | `1000` | User ID for /data ownership |
|
||||
| `PGID` | `1000` | Group ID for /data ownership |
|
||||
|
||||
## Supported Loaders
|
||||
|
||||
| Loader | Description | Ports |
|
||||
|---|---|---|
|
||||
| **Vanilla** | Official Mojang server | TCP 25565 |
|
||||
| **Paper** | High-performance fork from papermc.io | TCP 25565 |
|
||||
| **Fabric** | Lightweight mod loader via fabricmc.net | TCP 25565 |
|
||||
| **Forge** | Mod loader via minecraftforge.net | TCP 25565 |
|
||||
| **NeoForge** | Community fork of Forge via neoforged.net | TCP 25565 |
|
||||
| **Bedrock** | Official Bedrock Dedicated Server (no Java) | UDP 19132 |
|
||||
|
||||
## Volumes
|
||||
|
||||
- `/data` — Persists server data, worlds, config, and Java installations
|
||||
|
||||
## Loader-Specific Examples
|
||||
|
||||
### Paper
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EULA=true
|
||||
- MC_VERSION=1.21.1
|
||||
- MC_LOADER=paper
|
||||
- PAPER_BUILD=132 # optional, auto-detects latest stable
|
||||
```
|
||||
|
||||
### Fabric
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EULA=true
|
||||
- MC_VERSION=1.21.1
|
||||
- MC_LOADER=fabric
|
||||
- FABRIC_LOADER_VERSION=0.19.3 # optional, auto-detects latest
|
||||
```
|
||||
|
||||
### Forge / NeoForge
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EULA=true
|
||||
- MC_VERSION=1.21.1
|
||||
- MC_LOADER=forge # or neoforge
|
||||
- FORGE_VERSION=52.1.14 # optional, auto-detects latest
|
||||
```
|
||||
|
||||
### Bedrock
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EULA=true
|
||||
- MC_LOADER=bedrock
|
||||
- MC_VERSION=1.21.50 # Bedrock server version
|
||||
ports:
|
||||
- 19132:19132/udp
|
||||
- 19133:19133/udp # IPv6 (optional)
|
||||
```
|
||||
|
||||
## Java Version Management
|
||||
|
||||
Java is managed via [Jabba](https://github.com/Jabba-Team/jabba). Java installations are cached in `/data/java/` so they persist across container restarts.
|
||||
|
||||
Supported distributions include `temurin`, `zulu`, `adopt`, `openjdk`, `graalvm`, and more. Use the format `distribution@version`:
|
||||
|
||||
```yaml
|
||||
- JAVA_VERSION=temurin@21
|
||||
- JAVA_VERSION=zulu@17
|
||||
- JAVA_VERSION=graalvm@21
|
||||
```
|
||||
|
||||
If unset, the Java version is auto-detected from the Mojang version manifest based on `MC_VERSION`.
|
||||
|
||||
## Health Checks
|
||||
|
||||
The container includes a built-in health check:
|
||||
|
||||
- **Java servers** — TCP check on port 25565
|
||||
- **Bedrock servers** — UDP check on port 19132
|
||||
|
||||
The health check has a 2-minute start period to allow the server time to initialize.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server fails to start
|
||||
|
||||
Check the container logs:
|
||||
|
||||
```bash
|
||||
docker logs minecraft
|
||||
```
|
||||
|
||||
### EULA not accepted
|
||||
|
||||
If you see an EULA error, make sure `EULA=true` is set in your environment variables.
|
||||
|
||||
### Java not found
|
||||
|
||||
If Java installation fails, check that the `/data` volume is writable. Java is installed to `/data/java/` on first run.
|
||||
|
||||
### Wrong UID/GID
|
||||
|
||||
If you get permission errors on server files, check that `PUID` and `PGID` match your host user:
|
||||
|
||||
```bash
|
||||
id -u # your UID
|
||||
id -g # your GID
|
||||
```
|
||||
|
||||
### Port conflicts
|
||||
|
||||
Make sure the Minecraft port (25565 for Java, 19132 for Bedrock) is not already in use:
|
||||
|
||||
```bash
|
||||
ss -tlnp | grep 25565
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Building locally
|
||||
|
||||
```bash
|
||||
docker build -t dockercraft:local .
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
**API tests** — verify all external APIs are healthy and returning correct data (no Docker needed):
|
||||
|
||||
```bash
|
||||
# All suites
|
||||
./tests/run_tests.sh
|
||||
|
||||
# Specific MC version
|
||||
./tests/run_tests.sh -m 1.21.1
|
||||
|
||||
# Specific suites only
|
||||
./tests/run_tests.sh -s mojang,fabric,neoforge
|
||||
```
|
||||
|
||||
**Integration tests** — spin up Docker containers and verify full download → install → server start:
|
||||
|
||||
```bash
|
||||
# Test all loaders
|
||||
./tests/integration.sh
|
||||
|
||||
# Test a specific loader
|
||||
./tests/integration.sh -m 1.21.1 -l fabric
|
||||
|
||||
# Custom timeout
|
||||
./tests/integration.sh -t 300
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
+58
-5
@@ -1,25 +1,78 @@
|
||||
services:
|
||||
dockercraft:
|
||||
image: git.jeremymcclure.com/jeremy/dockercraft:latest
|
||||
# Use :dev to test builds from the dev branch
|
||||
#image: git.jeremymcclure.com/jeremy/dockercraft:dev
|
||||
container_name: dockercraft
|
||||
stdin_open: true
|
||||
tty: true
|
||||
environment:
|
||||
- MC_LOADER=paper # paper, fabric or forge
|
||||
- MC_VERSION=1.21.4
|
||||
- JAVA_VERSION=temurin@21
|
||||
- EULA=true
|
||||
- MC_VERSION=1.21.1
|
||||
|
||||
# Server loader type: paper, fabric, forge, neoforge, or bedrock
|
||||
- MC_LOADER=neoforge
|
||||
|
||||
# --- Loader versions -----------------------------------------------
|
||||
# Set the var matching your MC_LOADER above. Unset vars are ignored
|
||||
# for other loaders. Leave empty to auto-detect latest stable version.
|
||||
- FABRIC_LOADER_VERSION=
|
||||
- FORGE_VERSION=
|
||||
- NEOFORGE_VERSION=
|
||||
- PAPER_BUILD=
|
||||
#
|
||||
# MC_LOADER_VERSION is a fallback for any loader that doesn't have
|
||||
# its own var set. You usually don't need this.
|
||||
- MC_LOADER_VERSION=
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# For bedrock, MC_VERSION is the Bedrock server version (e.g. 1.21.50)
|
||||
# and no Java is needed.
|
||||
#
|
||||
# --- Java Version --------------------------------------------------
|
||||
# If unset, auto-detected from Mojang version manifest:
|
||||
# MC 1.20.5+ -> temurin@21, 1.17-1.20.4 -> temurin@17,
|
||||
# 1.17 -> temurin@16, pre-1.17 -> temurin@8
|
||||
# Set explicitly to pin a version. Uses Jabba
|
||||
# (https://github.com/Jabba-Team/jabba) — supports temurin, zulu,
|
||||
# adopt, openjdk, graalvm, and more.
|
||||
#- JAVA_VERSION=temurin@21
|
||||
# -------------------------------------------------------------------
|
||||
- JAR=server.jar
|
||||
- MEMORY=2G
|
||||
|
||||
- XMS=2G
|
||||
- XMX=4G
|
||||
- ADD_ARGS=
|
||||
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
volumes:
|
||||
- ./data:/data
|
||||
ports:
|
||||
# Java (vanilla, paper, fabric, forge, neoforge): TCP 25565
|
||||
# Bedrock: UDP 19132 (and UDP 19133 for IPv6)
|
||||
# Expose the port matching your MC_LOADER:
|
||||
- 25565:25565
|
||||
#- 19132:19132/udp
|
||||
#- 19133:19133/udp
|
||||
healthcheck:
|
||||
test: >
|
||||
bash -c '
|
||||
if [ "${MC_LOADER}" = "bedrock" ]; then
|
||||
nc -zu 127.0.0.1 19132;
|
||||
else
|
||||
nc -z 127.0.0.1 25565;
|
||||
fi
|
||||
'
|
||||
start_period: 2m
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
memory: 4G
|
||||
restart: unless-stopped
|
||||
|
||||
Regular → Executable
+64
-173
@@ -1,190 +1,81 @@
|
||||
#!/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
JAVA_VERSION=${JAVA_VERSION:-"temurin@21"}
|
||||
JAVA_HOME=/data/java/${JAVA_VERSION}
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
JAR=server.jar
|
||||
ADD_ARGS=${ADD_ARGS:-""}
|
||||
MEM=${MEMORY:-2G}
|
||||
# ---------- source helpers first (so log/die are available) ----------------
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
# ---------- environment variables ------------------------------------------
|
||||
|
||||
MC_VERSION="${MC_VERSION:-}"
|
||||
MC_LOADER="${MC_LOADER:-}"
|
||||
MC_LOADER_VERSION="${MC_LOADER_VERSION:-}"
|
||||
|
||||
FABRIC_LOADER_VERSION="${FABRIC_LOADER_VERSION:-${MC_LOADER_VERSION:-}}"
|
||||
FORGE_VERSION="${FORGE_VERSION:-${MC_LOADER_VERSION:-}}"
|
||||
NEOFORGE_VERSION="${NEOFORGE_VERSION:-${MC_LOADER_VERSION:-}}"
|
||||
PAPER_BUILD="${PAPER_BUILD:-}"
|
||||
|
||||
JAVA_VERSION="${JAVA_VERSION:-}"
|
||||
JAVA_HOME="/data/java/${JAVA_VERSION}"
|
||||
JAR="${JAR:-server.jar}"
|
||||
|
||||
XMS="${XMS:-2G}"
|
||||
XMX="${XMX:-4G}"
|
||||
|
||||
ADD_ARGS="${ADD_ARGS:-}"
|
||||
|
||||
PUID="${PUID:-1000}"
|
||||
PGID="${PGID:-1000}"
|
||||
|
||||
PATH=${JAVA_HOME}/bin:$PATH
|
||||
PATH="${JAVA_HOME}/bin:$PATH"
|
||||
|
||||
# Set Permissions for data folder
|
||||
setperms() {
|
||||
echo -e "|_______________________________________________________|"
|
||||
echo -e "|##--------------- Setting Permissions ---------------##|"
|
||||
echo -e "|#| Setting permissions to UID:${PUID} and GID:${PGID}"
|
||||
sudo usermod -u ${PUID} minecraft >/dev/null 2>&1
|
||||
sudo groupmod -g ${PGID} minecraft >/dev/null 2>&1
|
||||
sudo chown -R ${PUID}:${PGID} /data >/dev/null 2>&1
|
||||
echo -e "|#|---------------------------------------------------|#|"
|
||||
sleep 2
|
||||
echo -e "|#| DONE"
|
||||
echo -e "|##---------------------------------------------------##|"
|
||||
sleep 2
|
||||
}
|
||||
# ---------- validate required vars early -----------------------------------
|
||||
|
||||
# Crash function
|
||||
crash() {
|
||||
sleep 5
|
||||
echo "Exit Complete"
|
||||
exit 1
|
||||
}
|
||||
if [[ -z "$MC_LOADER" ]]; then
|
||||
die "MC_LOADER is required. Valid: vanilla, paper, fabric, forge, neoforge, bedrock"
|
||||
fi
|
||||
|
||||
# Set Eula
|
||||
setEula() {
|
||||
echo "#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA)." >eula.txt
|
||||
echo "eula=true" >>eula.txt
|
||||
}
|
||||
|
||||
# Check for Java, and install if neccesary
|
||||
javaCheck() {
|
||||
JAVA_HOME=/data/java/${JAVA_VERSION}
|
||||
if [[ -f "$HOME/.jabba/jabba.sh" ]]; then
|
||||
source $HOME/.jabba/jabba.sh
|
||||
else
|
||||
echo "Something went wrong with locating jabba(the java installer)"
|
||||
exit 0
|
||||
if [[ "$MC_LOADER" != "bedrock" ]]; then
|
||||
if [[ -z "$MC_VERSION" ]]; then
|
||||
die "MC_VERSION is required for ${MC_LOADER} loader"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -n "${JAVA_VERSION}" ]]; then
|
||||
echo "NO JAVA_VERSION ENVIRONMENT VARIABLE SET SET IT"
|
||||
crash
|
||||
fi
|
||||
# ---------- source libraries ------------------------------------------------
|
||||
|
||||
if [[ ! -f "/data/java/${JAVA_VERSION}/bin/java" ]]; then
|
||||
rm -rf /data/java/${JAVA_VERSION}
|
||||
jabba install ${JAVA_VERSION} -o /data/java/${JAVA_VERSION}
|
||||
fi
|
||||
PATH=${JAVA_HOME}/bin:$PATH
|
||||
JAVA="java"
|
||||
}
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/java.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/server.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/vanilla.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/paper.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/fabric.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/forge.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/neoforge.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/lib/loaders/bedrock.sh"
|
||||
|
||||
# Paper Server
|
||||
getPaper() {
|
||||
SERVER_TYPE="Paper Minecraft"
|
||||
LATEST_BUILD=$(curl -s https://api.papermc.io/v2/projects/${MC_LOADER}/versions/${MC_VERSION}/builds |
|
||||
jq -r '.builds | map(select(.channel == "default") | .build) | .[-1]')
|
||||
|
||||
if [ "$LATEST_BUILD" != "null" ]; then
|
||||
JAR_NAME=${MC_LOADER}-${MC_VERSION}-${LATEST_BUILD}.jar
|
||||
PAPERMC_URL="https://api.papermc.io/v2/projects/${MC_LOADER}/versions/${MC_VERSION}/builds/${LATEST_BUILD}/downloads/${JAR_NAME}"
|
||||
|
||||
# Download the latest Paper version
|
||||
curl -o server.jar $PAPERMC_URL
|
||||
echo "Download completed"
|
||||
echo "Minecraft Version: ${MC_VERSION}"
|
||||
echo "Paper Version: ${LATEST_BUILD}"
|
||||
else
|
||||
echo "No stable build for version $MC_VERSION found :("
|
||||
fi
|
||||
}
|
||||
|
||||
# Fabric Server
|
||||
getFabric() {
|
||||
SERVER_TYPE="Fabric Minecraft"
|
||||
LATEST_BUILD=$(curl -s https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION} | jq -s '.[] .[0] .loader .version' | tr -d '"')
|
||||
LATEST_INSTALLER=$(curl -s https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml | xmllint --xpath "/metadata/versioning/latest/text()" -)
|
||||
DL_URL=https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${LATEST_BUILD}/${LATEST_INSTALLER}/server/jar
|
||||
if [ "$LATEST_BUILD" != "null" ]; then
|
||||
echo "$DL_URL"
|
||||
curl -o server.jar ${DL_URL}
|
||||
else
|
||||
echo "TODO ERROR TEXT"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Forge Server
|
||||
getForge() {
|
||||
SERVER_TYPE="Forge Server"
|
||||
LATEST_BUILD=$(curl -s https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json | jq -r ".promos[\"${MC_VERSION}-latest\"]")
|
||||
DL_BUILD=${MC_VERSION}-${LATEST_BUILD}
|
||||
DL_URL=https://maven.minecraftforge.net/net/minecraftforge/forge/$DL_BUILD/forge-$DL_BUILD-installer.jar
|
||||
if [ "$LATEST_BUILD" != "null" ]; then
|
||||
echo "$DL_URL"
|
||||
curl -o forge-install.jar ${DL_URL}
|
||||
echo "$JAVA -jar forge-install.jar --installServer"
|
||||
$JAVA -jar forge-install.jar --installServer
|
||||
ln -s forge-${MC_VERSION}-${LATEST_BUILD}-shim.jar server.jar
|
||||
touch server.properties
|
||||
|
||||
rm -f user_jvm_args.txt
|
||||
rm -f forge-install.jar
|
||||
rm -f forge-install.jar.log
|
||||
rm -f README.txt
|
||||
rm -f run.bat
|
||||
rm -f run.sh
|
||||
else
|
||||
echo "TODO ERROR TEXT"
|
||||
fi
|
||||
}
|
||||
|
||||
# Server route choice
|
||||
serverSelect() {
|
||||
mkdir -p /data/server
|
||||
cd /data/server
|
||||
if [[ ! -f server.jar ]]; then
|
||||
case $MC_LOADER in
|
||||
vanilla)
|
||||
getVanilla
|
||||
;;
|
||||
paper)
|
||||
getPaper
|
||||
;;
|
||||
fabric)
|
||||
getFabric
|
||||
;;
|
||||
forge)
|
||||
getForge
|
||||
;;
|
||||
*)
|
||||
echo "Server of type ${MC_LOADER} is not recognized."
|
||||
crash
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
# Build out java command
|
||||
buildCommand() {
|
||||
JAVA_ARGS="-Xms${MEM} -Xmx${MEM}"
|
||||
if [[ -z ${ADD_ARGS+x} ]]; then
|
||||
RUN_STRING="${ADD_ARGS} ${JAVA_ARGS} -jar ${JAR} nogui"
|
||||
else
|
||||
RUN_STRING="${JAVA_ARGS} -jar ${JAR} nogui"
|
||||
fi
|
||||
}
|
||||
# ---------- main flow -------------------------------------------------------
|
||||
|
||||
setperms
|
||||
javaCheck
|
||||
serverSelect
|
||||
buildCommand
|
||||
|
||||
setEula
|
||||
|
||||
## ----------------- SEND IT ----------------- ##
|
||||
while true; do
|
||||
cd /data/server
|
||||
if [[ -f $JAR ]]; then
|
||||
echo -e "|_______________________________________________________|"
|
||||
echo -e "|##----------------- Server Starting -----------------##|"
|
||||
echo -e "|#| Server Type: $SERVER_TYPE"
|
||||
echo -e "|#| Java Version: $JAVA_VERSION"
|
||||
echo -e "|#| Additional Arg: $ADD_ARGS"
|
||||
echo -e "|#| Memory Allocated: $MEM"
|
||||
echo -e "|#| Running as:"
|
||||
echo -e "|#| User: $PUID"
|
||||
echo -e "|#| Group: $PGID"
|
||||
echo -e "|#| Run Command:"
|
||||
echo -e "|#| java $RUN_STRING"
|
||||
echo -e "|##---------------------------------------------------##|"
|
||||
echo -e "|-------------------------------------------------------|"
|
||||
exec java $RUN_STRING
|
||||
else
|
||||
echo "Server Not Found."
|
||||
crash
|
||||
fi
|
||||
done
|
||||
if [[ "$MC_LOADER" != "bedrock" ]]; then
|
||||
javaCheck
|
||||
fi
|
||||
|
||||
serverSelect
|
||||
|
||||
if [[ "$MC_LOADER" != "bedrock" ]]; then
|
||||
buildCommand
|
||||
fi
|
||||
|
||||
serverStart
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# ---------- helpers --------------------------------------------------------
|
||||
|
||||
log() {
|
||||
echo -e "[dockercraft] $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "[dockercraft] WARNING: $*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
echo -e "[dockercraft] ERROR: $*" >&2
|
||||
sleep 2
|
||||
exit 1
|
||||
}
|
||||
|
||||
crash() {
|
||||
die "$@"
|
||||
}
|
||||
|
||||
# ---------- permission setup -----------------------------------------------
|
||||
|
||||
setperms() {
|
||||
log "--- Setting permissions to UID:${PUID} GID:${PGID} ---"
|
||||
sudo usermod -u "${PUID}" minecraft
|
||||
sudo groupmod -g "${PGID}" minecraft
|
||||
sudo chown -R "${PUID}:${PGID}" /data
|
||||
log "Permissions set."
|
||||
}
|
||||
|
||||
# ---------- EULA -----------------------------------------------------------
|
||||
|
||||
setEula() {
|
||||
if [[ "${EULA:-false}" != "true" ]]; then
|
||||
die "EULA not accepted. Set EULA=true to accept the Minecraft EULA (https://aka.ms/MinecraftEULA)"
|
||||
fi
|
||||
mkdir -p /data/server
|
||||
cat <<'EOF' > /data/server/eula.txt
|
||||
#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).
|
||||
eula=true
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------- misc -----------------------------------------------------------
|
||||
|
||||
newForge() {
|
||||
if [[ "$(printf '%s\n' "1.17.1" "$MC_VERSION" | sort -V | head -n1)" = "1.17.1" ]]; then
|
||||
NEW_FORGE=true
|
||||
else
|
||||
NEW_FORGE=false
|
||||
fi
|
||||
}
|
||||
|
||||
serverInfoWrite() {
|
||||
cat <<EOF > /data/server/server.info
|
||||
MC_VERSION=${MC_VERSION}
|
||||
|
||||
MC_LOADER=${MC_LOADER}
|
||||
MC_LOADER_VERSION=${MC_LOADER_VERSION}
|
||||
|
||||
JAVA_VERSION=${JAVA_VERSION:-"N/A"}
|
||||
JAR=${JAR:-"N/A"}
|
||||
|
||||
XMS=${XMS:-"N/A"}
|
||||
XMX=${XMX:-"N/A"}
|
||||
ADD_ARGS=${ADD_ARGS:-"NONE"}
|
||||
|
||||
PUID=${PUID}
|
||||
PGID=${PGID}
|
||||
EOF
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
javaVersionDetect() {
|
||||
if [[ -n "${JAVA_VERSION:-}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log "Auto-detecting Java version from Mojang manifest..."
|
||||
|
||||
local MANIFEST
|
||||
MANIFEST=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://launchermeta.mojang.com/mc/game/version_manifest.json") || true
|
||||
if [[ -z "$MANIFEST" ]]; then
|
||||
warn "Could not fetch Mojang manifest, defaulting to temurin@21"
|
||||
JAVA_VERSION="temurin@21"
|
||||
return
|
||||
fi
|
||||
|
||||
local VERSION_URL
|
||||
VERSION_URL=$(echo "$MANIFEST" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url")
|
||||
if [[ -z "$VERSION_URL" || "$VERSION_URL" == "null" ]]; then
|
||||
warn "Version ${MC_VERSION} not found in manifest, defaulting to temurin@21"
|
||||
JAVA_VERSION="temurin@21"
|
||||
return
|
||||
fi
|
||||
|
||||
local VERSION_DATA
|
||||
VERSION_DATA=$(curl -sf --connect-timeout 10 --max-time 30 "$VERSION_URL") || true
|
||||
if [[ -z "$VERSION_DATA" ]]; then
|
||||
warn "Could not fetch version data for ${MC_VERSION}, defaulting to temurin@21"
|
||||
JAVA_VERSION="temurin@21"
|
||||
return
|
||||
fi
|
||||
|
||||
local JAVA_MAJOR
|
||||
JAVA_MAJOR=$(echo "$VERSION_DATA" | jq -r ".javaVersion.majorVersion // empty")
|
||||
if [[ -z "$JAVA_MAJOR" ]]; then
|
||||
JAVA_VERSION="temurin@8"
|
||||
else
|
||||
JAVA_VERSION="temurin@${JAVA_MAJOR}"
|
||||
fi
|
||||
log "Detected Java version: ${JAVA_VERSION}"
|
||||
}
|
||||
|
||||
javaCheck() {
|
||||
javaVersionDetect
|
||||
|
||||
JAVA_HOME="/data/java/${JAVA_VERSION}"
|
||||
if [[ -f "$HOME/.jabba/jabba.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$HOME/.jabba/jabba.sh"
|
||||
else
|
||||
die "Jabba not found at ~/.jabba/jabba.sh — installation may have failed"
|
||||
fi
|
||||
|
||||
if [[ ! -f "/data/java/${JAVA_VERSION}/bin/java" ]]; then
|
||||
log "Installing Java ${JAVA_VERSION}..."
|
||||
rm -rf "/data/java/${JAVA_VERSION}"
|
||||
jabba install "${JAVA_VERSION}" -o "/data/java/${JAVA_VERSION}"
|
||||
log "Java ${JAVA_VERSION} installed."
|
||||
else
|
||||
log "Java ${JAVA_VERSION} already cached."
|
||||
fi
|
||||
PATH="${JAVA_HOME}/bin:$PATH"
|
||||
JAVA="java"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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"
|
||||
|
||||
_bedrockDownload() {
|
||||
local platform="$1"
|
||||
local version="${2:-}"
|
||||
|
||||
log "Fetching Bedrock download URL (platform: ${platform})..."
|
||||
local DL_URL
|
||||
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" --connect-timeout 10 --max-time 30 \
|
||||
"$BEDROCK_API_URL" |
|
||||
jq -r ".result.links[] | select(.downloadType == \"${platform}\") | .downloadUrl // empty")
|
||||
|
||||
if [[ -z "$DL_URL" ]]; then
|
||||
warn "Primary API failed, trying fallback source..."
|
||||
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" --connect-timeout 10 --max-time 30 \
|
||||
"$BEDROCK_FALLBACK_URL" |
|
||||
jq --arg type "$( [[ "$platform" == *Preview* ]] && echo preview || echo release )" -r '
|
||||
.[$type] | to_entries | sort_by(.key | split(".") | map(tonumber)) | last | .value.linux.url // empty
|
||||
')
|
||||
fi
|
||||
|
||||
if [[ -z "$DL_URL" ]]; then
|
||||
die "Failed to look up Bedrock download URL"
|
||||
fi
|
||||
|
||||
if [[ -n "$version" ]]; then
|
||||
DL_URL=$(echo "$DL_URL" | sed -E "s/(bedrock-server-)[^/]+(\.zip)/\1${version}\2/")
|
||||
fi
|
||||
|
||||
local VER
|
||||
VER=$(echo "$DL_URL" | grep -oP 'bedrock-server-\K[\d.]+(?=\.zip)')
|
||||
log "Downloading Bedrock server version ${VER}..."
|
||||
curl -fL -o bedrock-server.zip -A "Mozilla/5.0 (X11; Linux x86_64)" "$DL_URL"
|
||||
unzip -o bedrock-server.zip
|
||||
rm -f bedrock-server.zip
|
||||
chmod +x bedrock_server
|
||||
log "Bedrock ${VER} downloaded."
|
||||
}
|
||||
|
||||
getBedrock() {
|
||||
SERVER_TYPE="Bedrock Server"
|
||||
_bedrockDownload "serverBedrockLinux" "${MC_VERSION:-}"
|
||||
}
|
||||
|
||||
getBedrockPreview() {
|
||||
SERVER_TYPE="Bedrock Server (Preview)"
|
||||
_bedrockDownload "serverBedrockPreviewLinux" "${MC_VERSION:-}"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
getFabric() {
|
||||
SERVER_TYPE="Fabric Minecraft"
|
||||
|
||||
if [[ -z "${MC_VERSION}" ]]; then
|
||||
die "MC_VERSION is required for Fabric loader"
|
||||
fi
|
||||
|
||||
log "Fetching Fabric loader version for MC ${MC_VERSION}..."
|
||||
local LATEST_BUILD LATEST_INSTALLER
|
||||
LATEST_BUILD=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}" |
|
||||
jq -r '.[0].loader.version')
|
||||
|
||||
LATEST_INSTALLER=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml" |
|
||||
xmllint --xpath "/metadata/versioning/latest/text()" -)
|
||||
|
||||
if [[ -z "$LATEST_BUILD" || "$LATEST_BUILD" == "null" ]]; then
|
||||
die "No Fabric loader found for MC ${MC_VERSION}"
|
||||
fi
|
||||
if [[ -z "$LATEST_INSTALLER" ]]; then
|
||||
die "Could not determine latest Fabric installer version"
|
||||
fi
|
||||
|
||||
local VER_LOADER
|
||||
VER_LOADER="${FABRIC_LOADER_VERSION:-${LATEST_BUILD}}"
|
||||
local DL_URL
|
||||
DL_URL="https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${VER_LOADER}/${LATEST_INSTALLER}/server/jar"
|
||||
|
||||
log "Downloading Fabric (loader ${VER_LOADER}, installer ${LATEST_INSTALLER})..."
|
||||
curl -fL -o server.jar "${DL_URL}"
|
||||
log "Fabric ${MC_VERSION} (loader ${VER_LOADER}) downloaded."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
getForge() {
|
||||
SERVER_TYPE="Forge Server"
|
||||
|
||||
if [[ -z "${MC_VERSION}" ]]; then
|
||||
die "MC_VERSION is required for Forge loader"
|
||||
fi
|
||||
|
||||
local DL_URL
|
||||
if [[ -n "${FORGE_VERSION}" ]]; then
|
||||
VER_LOADER="${FORGE_VERSION}"
|
||||
DL_URL="https://maven.minecraftforge.net/net/minecraftforge/forge/${MC_VERSION}-${VER_LOADER}/forge-${MC_VERSION}-${VER_LOADER}-installer.jar"
|
||||
else
|
||||
log "Fetching latest Forge version for MC ${MC_VERSION}..."
|
||||
local LATEST_BUILD
|
||||
LATEST_BUILD=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json" |
|
||||
jq -r ".promos[\"${MC_VERSION}-latest\"]")
|
||||
|
||||
if [[ -z "$LATEST_BUILD" || "$LATEST_BUILD" == "null" ]]; then
|
||||
die "No Forge version found for MC ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
VER_LOADER="$LATEST_BUILD"
|
||||
DL_URL="https://maven.minecraftforge.net/net/minecraftforge/forge/${MC_VERSION}-${VER_LOADER}/forge-${MC_VERSION}-${VER_LOADER}-installer.jar"
|
||||
fi
|
||||
|
||||
log "Downloading Forge ${MC_VERSION}-${VER_LOADER} installer..."
|
||||
rm -f server.jar
|
||||
curl -fL -o forge-install.jar "${DL_URL}"
|
||||
|
||||
log "Running Forge installer..."
|
||||
$JAVA -jar forge-install.jar --installServer
|
||||
|
||||
newForge
|
||||
rm -f forge-install.jar forge-install.jar.log
|
||||
|
||||
if [[ "${NEW_FORGE}" == "true" ]]; then
|
||||
log "Forge ${MC_VERSION} installed (new-style launcher)."
|
||||
else
|
||||
ln -sf "forge-${MC_VERSION}-${VER_LOADER}.jar" server.jar
|
||||
log "Forge ${MC_VERSION}-${VER_LOADER} installed."
|
||||
fi
|
||||
|
||||
touch server.properties
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
getNeoForge() {
|
||||
SERVER_TYPE="NeoForge Server"
|
||||
|
||||
if [[ -z "${MC_VERSION}" ]]; then
|
||||
die "MC_VERSION is required for NeoForge loader"
|
||||
fi
|
||||
|
||||
local VER_MINECRAFT DL_URL
|
||||
VER_MINECRAFT="${MC_VERSION}"
|
||||
|
||||
if [[ -n "${NEOFORGE_VERSION}" ]]; then
|
||||
VER_LOADER="${NEOFORGE_VERSION}"
|
||||
else
|
||||
log "Auto-detecting latest NeoForge version for MC ${MC_VERSION}..."
|
||||
local MC_MINOR ALL_VERSIONS
|
||||
MC_MINOR="${MC_VERSION#1.}"
|
||||
ALL_VERSIONS=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml" |
|
||||
xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null |
|
||||
tr ' ' '\n' | grep "^${MC_MINOR}\." | sort -V || true)
|
||||
|
||||
# Prefer stable versions, fall back to beta/alpha if none exist
|
||||
VER_LOADER=$(echo "$ALL_VERSIONS" | grep -v -- '-beta\|-alpha' | tail -1 || true)
|
||||
if [[ -z "$VER_LOADER" ]]; then
|
||||
VER_LOADER=$(echo "$ALL_VERSIONS" | tail -1)
|
||||
if [[ -n "$VER_LOADER" ]]; then
|
||||
warn "No stable NeoForge version for MC ${MC_VERSION}, using ${VER_LOADER}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$VER_LOADER" ]]; then
|
||||
die "Could not auto-detect NeoForge version for MC ${MC_VERSION}"
|
||||
fi
|
||||
fi
|
||||
|
||||
DL_URL="https://maven.neoforged.net/releases/net/neoforged/neoforge/${VER_LOADER}/neoforge-${VER_LOADER}-installer.jar"
|
||||
|
||||
log "Downloading NeoForge ${VER_LOADER} installer..."
|
||||
rm -f server.jar
|
||||
curl -fL -o "neoforge-${VER_LOADER}-installer.jar" "${DL_URL}"
|
||||
|
||||
log "Running NeoForge installer..."
|
||||
$JAVA -jar "neoforge-${VER_LOADER}-installer.jar" --installServer --server.jar
|
||||
|
||||
mv server.jar "neoforge-${VER_LOADER}.jar"
|
||||
ln -sf "neoforge-${VER_LOADER}.jar" server.jar
|
||||
|
||||
rm -f "neoforge-${VER_LOADER}-installer.jar" \
|
||||
"neoforge-${VER_LOADER}-installer.jar.log" \
|
||||
README.txt \
|
||||
run.bat
|
||||
|
||||
touch server.properties
|
||||
log "NeoForge ${MC_VERSION}-${VER_LOADER} installed."
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
getPaper() {
|
||||
SERVER_TYPE="Paper Minecraft"
|
||||
|
||||
if [[ -z "${MC_VERSION}" ]]; then
|
||||
die "MC_VERSION is required for Paper loader"
|
||||
fi
|
||||
|
||||
if [[ -f server.jar ]]; then
|
||||
log "Paper server.jar already exists, skipping download."
|
||||
return
|
||||
fi
|
||||
|
||||
local PAPERMC_URL
|
||||
|
||||
if [[ -n "${PAPER_BUILD}" ]]; then
|
||||
log "Fetching Paper build ${PAPER_BUILD} for MC ${MC_VERSION}..."
|
||||
local BUILD_DATA
|
||||
BUILD_DATA=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
-H "User-Agent: dockercraft/1.0" \
|
||||
"https://fill.papermc.io/v3/projects/paper/versions/${MC_VERSION}/builds") || true
|
||||
|
||||
if [[ -n "$BUILD_DATA" ]]; then
|
||||
PAPERMC_URL=$(echo "$BUILD_DATA" | jq -r \
|
||||
"map(select(.id == ${PAPER_BUILD})) | .[0].downloads.\"server:default\".url // empty")
|
||||
fi
|
||||
|
||||
if [[ -z "$PAPERMC_URL" ]]; then
|
||||
die "Paper build ${PAPER_BUILD} not found for MC ${MC_VERSION}"
|
||||
fi
|
||||
else
|
||||
log "Querying PaperMC API for latest stable build of ${MC_VERSION}..."
|
||||
local BUILDS_DATA
|
||||
BUILDS_DATA=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
-H "User-Agent: dockercraft/1.0" \
|
||||
"https://fill.papermc.io/v3/projects/paper/versions/${MC_VERSION}/builds") || true
|
||||
|
||||
if [[ -z "$BUILDS_DATA" ]]; then
|
||||
die "Could not fetch Paper builds for MC ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
# Check for API error
|
||||
if echo "$BUILDS_DATA" | jq -e '.ok == false' >/dev/null 2>&1; then
|
||||
local ERR_MSG
|
||||
ERR_MSG=$(echo "$BUILDS_DATA" | jq -r '.message // "Unknown error"')
|
||||
die "PaperMC API error: ${ERR_MSG}"
|
||||
fi
|
||||
|
||||
PAPERMC_URL=$(echo "$BUILDS_DATA" | jq -r \
|
||||
'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty')
|
||||
|
||||
if [[ -z "$PAPERMC_URL" ]]; then
|
||||
die "No stable Paper build found for MC ${MC_VERSION}"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Downloading Paper for MC ${MC_VERSION}..."
|
||||
curl -fL -o server.jar "$PAPERMC_URL"
|
||||
log "Paper for MC ${MC_VERSION} downloaded."
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
getVanilla() {
|
||||
SERVER_TYPE="Vanilla Minecraft"
|
||||
|
||||
if [[ -z "${MC_VERSION}" ]]; then
|
||||
die "MC_VERSION is required for Vanilla loader"
|
||||
fi
|
||||
|
||||
log "Fetching Mojang version manifest..."
|
||||
local MANIFEST
|
||||
MANIFEST=$(curl -sf --connect-timeout 10 --max-time 30 \
|
||||
"https://launchermeta.mojang.com/mc/game/version_manifest.json") || true
|
||||
|
||||
if [[ -z "$MANIFEST" ]]; then
|
||||
die "Could not fetch Mojang version manifest"
|
||||
fi
|
||||
|
||||
local VERSION_URL
|
||||
VERSION_URL=$(echo "$MANIFEST" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url")
|
||||
|
||||
if [[ -z "$VERSION_URL" || "$VERSION_URL" == "null" ]]; then
|
||||
die "Version ${MC_VERSION} not found in Mojang manifest"
|
||||
fi
|
||||
|
||||
local VERSION_DATA DOWNLOAD_URL
|
||||
VERSION_DATA=$(curl -sf --connect-timeout 10 --max-time 30 "$VERSION_URL") || true
|
||||
if [[ -z "$VERSION_DATA" ]]; then
|
||||
die "Could not fetch version data for ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
DOWNLOAD_URL=$(echo "$VERSION_DATA" | jq -r ".downloads.server.url")
|
||||
if [[ -z "$DOWNLOAD_URL" || "$DOWNLOAD_URL" == "null" ]]; then
|
||||
die "No server download URL found for ${MC_VERSION}"
|
||||
fi
|
||||
|
||||
log "Downloading Vanilla ${MC_VERSION} server..."
|
||||
curl -fL -o server.jar "$DOWNLOAD_URL"
|
||||
log "Vanilla ${MC_VERSION} downloaded."
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
serverSelect() {
|
||||
mkdir -p /data/server
|
||||
cd /data/server || die "Cannot access /data/server"
|
||||
if [[ -f "server.jar" ]] || [[ -f "run.sh" ]] || [[ -f "bedrock_server" ]]; then
|
||||
log "Found existing server files, skipping download."
|
||||
else
|
||||
case "$MC_LOADER" in
|
||||
vanilla)
|
||||
getVanilla
|
||||
;;
|
||||
paper)
|
||||
getPaper
|
||||
;;
|
||||
fabric)
|
||||
getFabric
|
||||
;;
|
||||
forge)
|
||||
getForge
|
||||
;;
|
||||
neoforge)
|
||||
getNeoForge
|
||||
;;
|
||||
bedrock)
|
||||
getBedrock
|
||||
;;
|
||||
*)
|
||||
die "Server type '${MC_LOADER}' is not recognized. Valid: vanilla, paper, fabric, forge, neoforge, bedrock"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
buildCommand() {
|
||||
JAVA_ARGS="-Xms${XMS} -Xmx${XMX}"
|
||||
if [[ -n "${ADD_ARGS}" ]]; then
|
||||
RUN_STRING="${ADD_ARGS} ${JAVA_ARGS} -jar ${JAR} nogui"
|
||||
else
|
||||
RUN_STRING="${JAVA_ARGS} -jar ${JAR} nogui"
|
||||
fi
|
||||
}
|
||||
|
||||
detectForgeVersion() {
|
||||
# Detect the forge/neoforge version from the installed jar names
|
||||
VER_LOADER="${VER_LOADER:-}"
|
||||
if [[ -n "$VER_LOADER" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$MC_LOADER" == "neoforge" ]] && [[ -L "/data/server/server.jar" ]]; then
|
||||
VER_LOADER=$(readlink "/data/server/server.jar" | sed 's/^neoforge-//;s/\.jar$//')
|
||||
elif [[ "$MC_LOADER" == "forge" ]]; then
|
||||
local forge_jar
|
||||
forge_jar=$(ls /data/server/forge-"${MC_VERSION}"-*.jar 2>/dev/null | head -1)
|
||||
if [[ -n "$forge_jar" ]]; then
|
||||
VER_LOADER="${forge_jar#/data/server/forge-${MC_VERSION}-}"
|
||||
VER_LOADER="${VER_LOADER%.jar}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
serverStart() {
|
||||
detectForgeVersion
|
||||
newForge
|
||||
|
||||
cd /data/server || die "Cannot access /data/server"
|
||||
if [[ ! -f "server.properties" ]]; then
|
||||
touch "server.properties"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$JAR" ]] && [[ ! -f "run.sh" ]] && [[ ! -f "bedrock_server" ]]; then
|
||||
die "Server binary not found in /data/server"
|
||||
fi
|
||||
|
||||
log "--- Server Starting ---"
|
||||
log "Minecraft Version: ${MC_VERSION}"
|
||||
log "Loader Type: ${SERVER_TYPE:-$MC_LOADER}"
|
||||
log "Loader Version: ${MC_LOADER_VERSION:-auto}"
|
||||
if [[ "$MC_LOADER" != "bedrock" ]]; then
|
||||
log "Java Version: ${JAVA_VERSION}"
|
||||
log "Jar: ${JAR}"
|
||||
log "Memory: Xms=${XMS} Xmx=${XMX}"
|
||||
if [[ -n "${ADD_ARGS}" ]]; then
|
||||
log "Additional Args: ${ADD_ARGS}"
|
||||
fi
|
||||
fi
|
||||
log "Running as UID:${PUID} GID:${PGID}"
|
||||
|
||||
serverInfoWrite
|
||||
|
||||
if [[ "$MC_LOADER" == "bedrock" ]]; then
|
||||
log "Starting Bedrock server..."
|
||||
exec env LD_LIBRARY_PATH=. ./bedrock_server
|
||||
elif [[ "$MC_LOADER" == "neoforge" ]]; then
|
||||
log "Starting NeoForge server..."
|
||||
exec java @user_jvm_args.txt @libraries/net/neoforged/neoforge/"${VER_LOADER}"/unix_args.txt "$@"
|
||||
elif [[ "$MC_LOADER" == "forge" ]] && [[ "${NEW_FORGE:-false}" == "true" ]]; then
|
||||
log "Starting Forge server (new-style)..."
|
||||
exec java @user_jvm_args.txt @libraries/net/minecraftforge/forge/"${MC_VERSION}"-"${VER_LOADER}"/unix_args.txt "$@"
|
||||
else
|
||||
log "Starting ${SERVER_TYPE:-$MC_LOADER} server..."
|
||||
exec java $RUN_STRING
|
||||
fi
|
||||
}
|
||||
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