824 lines
33 KiB
Bash
Executable File
824 lines
33 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
run_test() {
|
|
local name="$1"
|
|
shift
|
|
if ("$@"); then
|
|
printf 'PASS %s\n' "$name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
printf 'FAIL %s\n' "$name"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
checksum_file() {
|
|
local file="$1"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$file"
|
|
else
|
|
shasum -a 256 "$file"
|
|
fi
|
|
}
|
|
|
|
test_erp_run_preserves_java_option_arguments() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-run-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state"
|
|
: > "$tmp/current/app/kaidi-erp.jar"
|
|
: > "$tmp/state/install.lock"
|
|
: > "$tmp/-Dpattern=expanded"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'if [[ "${1:-}" == "-version" ]]; then' \
|
|
' printf '\''openjdk version "17.0.12"\n'\'' >&2' \
|
|
' exit 0' \
|
|
'fi' \
|
|
'printf '\''%s\n'\'' "$@"' > "$tmp/java"
|
|
chmod +x "$tmp/java"
|
|
{
|
|
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
|
|
printf 'ERP_JAVA_OPTS=%q\n' '-Dpattern=* -Xmx1g'
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
} > "$tmp/erp.env"
|
|
|
|
local output
|
|
output="$(cd "$tmp" && ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-run")" || return 1
|
|
grep -Fqx -- '-Dpattern=*' <<< "$output" || return 1
|
|
grep -Fqx -- '-Xmx1g' <<< "$output" || return 1
|
|
grep -Fqx -- "$tmp/current/app/kaidi-erp.jar" <<< "$output"
|
|
)
|
|
|
|
test_erp_run_migrates_legacy_update_source() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-source-migration.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state"
|
|
: > "$tmp/current/app/kaidi-erp.jar"
|
|
: > "$tmp/state/install.lock"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'if [[ "${1:-}" == "-version" ]]; then printf '\''openjdk version "17.0.12"\n'\'' >&2; fi' \
|
|
'exit 0' > "$tmp/java"
|
|
chmod +x "$tmp/java"
|
|
{
|
|
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
printf "OA_UPDATE_GITEA_BASE_URL='http://38.76.196.225:10099/'\n"
|
|
printf "OA_UPDATE_ALLOW_INSECURE_HTTP='true'\n"
|
|
} > "$tmp/erp.env"
|
|
chmod 600 "$tmp/erp.env"
|
|
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-run" >/dev/null 2>&1 || return 1
|
|
|
|
grep -Fqx "OA_UPDATE_GITEA_BASE_URL='https://git.awaioi.com'" "$tmp/erp.env" || return 1
|
|
grep -Fqx "OA_UPDATE_ALLOW_INSECURE_HTTP='false'" "$tmp/erp.env" || return 1
|
|
! grep -Fq '38.76.196.225' "$tmp/erp.env"
|
|
)
|
|
|
|
test_erp_run_finalizes_healthy_pending_install() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-handoff-success.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state" "$tmp/installer" "$tmp/bin"
|
|
: > "$tmp/current/app/kaidi-erp.jar"
|
|
: > "$tmp/installer/kaidi-erp-installer.jar"
|
|
printf 'pending\n' > "$tmp/state/install.pending"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'if [[ "${1:-}" == "-version" ]]; then printf '\''openjdk version "17.0.12"\n'\'' >&2; exit 0; fi' \
|
|
'printf '\''formal application output\n'\''' \
|
|
'sleep 0.1' \
|
|
'exit 0' > "$tmp/java"
|
|
printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$tmp/bin/curl"
|
|
chmod +x "$tmp/java" "$tmp/bin/curl"
|
|
{
|
|
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
printf 'ERP_JAR_PATH=%q\n' "$tmp/current/app/kaidi-erp.jar"
|
|
printf 'ERP_INSTALLER_JAR=%q\n' "$tmp/installer/kaidi-erp-installer.jar"
|
|
printf 'ERP_INSTALL_PENDING_FILE=%q\n' "$tmp/state/install.pending"
|
|
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
|
|
printf 'ERP_INSTALL_LOG_FILE=%q\n' "$tmp/state/install-formal.log"
|
|
printf 'ERP_INSTALL_HEALTH_TIMEOUT_SECONDS=3\n'
|
|
printf 'ERP_UPDATE_HEALTH_POLL_SECONDS=1\n'
|
|
} > "$tmp/erp.env"
|
|
|
|
PATH="$tmp/bin:$PATH" ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-run" > "$tmp/run.log" 2>&1 || return 1
|
|
|
|
[[ -f "$tmp/state/install.lock" ]] || return 1
|
|
[[ ! -e "$tmp/state/install.pending" ]] || return 1
|
|
[[ ! -d "$tmp/installer" ]] || return 1
|
|
[[ ! -e "$tmp/run/app.pid" ]] || return 1
|
|
grep -Fq 'formal application output' "$tmp/state/install-formal.log"
|
|
)
|
|
|
|
test_erp_run_preserves_failed_pending_install() (
|
|
local tmp output status=0
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-handoff-failure.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state" "$tmp/installer" "$tmp/bin"
|
|
: > "$tmp/current/app/kaidi-erp.jar"
|
|
: > "$tmp/installer/kaidi-erp-installer.jar"
|
|
printf 'pending\n' > "$tmp/state/install.pending"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'if [[ "${1:-}" == "-version" ]]; then printf '\''openjdk version "17.0.12"\n'\'' >&2; exit 0; fi' \
|
|
'printf '\''formal application exploded\n'\'' >&2' \
|
|
'sleep 0.1' \
|
|
'exit 23' > "$tmp/java"
|
|
printf '%s\n' '#!/usr/bin/env bash' 'exit 1' > "$tmp/bin/curl"
|
|
chmod +x "$tmp/java" "$tmp/bin/curl"
|
|
{
|
|
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
printf 'ERP_JAR_PATH=%q\n' "$tmp/current/app/kaidi-erp.jar"
|
|
printf 'ERP_INSTALLER_JAR=%q\n' "$tmp/installer/kaidi-erp-installer.jar"
|
|
printf 'ERP_INSTALL_PENDING_FILE=%q\n' "$tmp/state/install.pending"
|
|
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
|
|
printf 'ERP_INSTALL_LOG_FILE=%q\n' "$tmp/state/install-formal.log"
|
|
printf 'ERP_INSTALL_HEALTH_TIMEOUT_SECONDS=3\n'
|
|
printf 'ERP_UPDATE_HEALTH_POLL_SECONDS=1\n'
|
|
} > "$tmp/erp.env"
|
|
|
|
output="$(PATH="$tmp/bin:$PATH" ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-run" 2>&1)" || status=$?
|
|
|
|
[[ "$status" -ne 0 ]] || return 1
|
|
[[ -f "$tmp/state/install.pending" ]] || return 1
|
|
[[ ! -e "$tmp/state/install.lock" ]] || return 1
|
|
[[ -d "$tmp/installer" ]] || return 1
|
|
grep -Fq 'formal application exploded' "$tmp/state/install-formal.log" || return 1
|
|
[[ "$output" == *"Formal application diagnostics: $tmp/state/install-formal.log"* ]]
|
|
)
|
|
|
|
prepare_signed_archive() {
|
|
local tmp="$1" unsafe="${2:-0}"
|
|
mkdir -p "$tmp/package/kaidi-erp-1.2.3/app"
|
|
printf 'jar\n' > "$tmp/package/kaidi-erp-1.2.3/app/kaidi-erp.jar"
|
|
if [[ "$unsafe" == "1" ]]; then
|
|
ln -s /etc/passwd "$tmp/package/kaidi-erp-1.2.3/unsafe-link"
|
|
fi
|
|
tar -czf "$tmp/kaidi-erp-1.2.3.tar.gz" -C "$tmp/package" kaidi-erp-1.2.3
|
|
printf 'installer jar\n' > "$tmp/kaidi-erp-installer-1.2.3.jar"
|
|
(cd "$tmp" && checksum_file kaidi-erp-1.2.3.tar.gz && checksum_file kaidi-erp-installer-1.2.3.jar) > "$tmp/SHA256SUMS"
|
|
openssl pkeyutl -sign -rawin -inkey "$tmp/private.pem" \
|
|
-in "$tmp/SHA256SUMS" -out "$tmp/SHA256SUMS.sig"
|
|
}
|
|
|
|
test_installer_verifies_signed_safe_archive() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-signature-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
openssl genpkey -algorithm ED25519 -out "$tmp/private.pem" >/dev/null 2>&1 || return 1
|
|
openssl pkey -in "$tmp/private.pem" -pubout -out "$tmp/public.pem" >/dev/null 2>&1 || return 1
|
|
prepare_signed_archive "$tmp" || return 1
|
|
|
|
source "$PROJECT_ROOT/install.sh"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
TMP_DIR="$tmp"
|
|
PUBLIC_KEY="$(<"$tmp/public.pem")"
|
|
ARCHIVE_NAME=kaidi-erp-1.2.3.tar.gz
|
|
ARCHIVE_PATH="$tmp/$ARCHIVE_NAME"
|
|
INSTALLER_NAME=kaidi-erp-installer-1.2.3.jar
|
|
INSTALLER_PATH="$tmp/$INSTALLER_NAME"
|
|
verify_release
|
|
)
|
|
|
|
test_installer_rejects_archive_symlinks() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-unsafe-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
openssl genpkey -algorithm ED25519 -out "$tmp/private.pem" >/dev/null 2>&1 || return 1
|
|
openssl pkey -in "$tmp/private.pem" -pubout -out "$tmp/public.pem" >/dev/null 2>&1 || return 1
|
|
prepare_signed_archive "$tmp" 1 || return 1
|
|
|
|
source "$PROJECT_ROOT/install.sh"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
TMP_DIR="$tmp"
|
|
PUBLIC_KEY="$(<"$tmp/public.pem")"
|
|
ARCHIVE_NAME=kaidi-erp-1.2.3.tar.gz
|
|
ARCHIVE_PATH="$tmp/$ARCHIVE_NAME"
|
|
INSTALLER_NAME=kaidi-erp-installer-1.2.3.jar
|
|
INSTALLER_PATH="$tmp/$INSTALLER_NAME"
|
|
! (verify_release) >/dev/null 2>&1
|
|
)
|
|
|
|
test_stable_channel_rejects_prerelease_tag() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-prerelease-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
printf 'OA_UPDATE_GITEA_BASE_URL=https://example.test\n' > "$tmp/erp.env"
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" source "$PROJECT_ROOT/distribution/bin/erp-update"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
printf '%s\n' \
|
|
'{"tag_name":"v2.0.0-rc.1","draft":false,"prerelease":false,"assets":[' \
|
|
'{"name":"kaidi-erp-2.0.0-rc.1.tar.gz","browser_download_url":"https://example.test/app"},' \
|
|
'{"name":"SHA256SUMS","browser_download_url":"https://example.test/sums"},' \
|
|
'{"name":"SHA256SUMS.sig","browser_download_url":"https://example.test/sig"}]}' \
|
|
> "$tmp/release.json"
|
|
CHANNEL=stable
|
|
! select_release "$tmp/release.json" '' "$tmp/selection" >/dev/null 2>&1
|
|
)
|
|
|
|
test_update_helper_preserves_release_metadata() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-update-metadata.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/state"
|
|
printf 'OA_UPDATE_GITEA_BASE_URL=https://example.test\nOA_UPDATE_STATE_FILE=%q\n' \
|
|
"$tmp/state/update.json" > "$tmp/erp.env"
|
|
printf '%s\n' \
|
|
'{"phase":"AVAILABLE","releaseNotes":"Important fixes","publishedAt":"2026-08-04T10:00:00Z",' \
|
|
'"assets":[{"name":"SHA256SUMS","downloadUrl":"https://example.test/sums","size":64}]}' \
|
|
> "$tmp/state/update.json"
|
|
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" source "$PROJECT_ROOT/distribution/bin/erp-update"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
write_state DOWNLOADING 25 'downloading' 1.2.3
|
|
|
|
python3 - "$tmp/state/update.json" <<'PY'
|
|
import json, sys
|
|
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
state = json.load(handle)
|
|
assert state["phase"] == "DOWNLOADING"
|
|
assert state["releaseNotes"] == "Important fixes"
|
|
assert state["publishedAt"] == "2026-08-04T10:00:00Z"
|
|
assert state["assets"][0]["name"] == "SHA256SUMS"
|
|
PY
|
|
)
|
|
|
|
test_installer_builds_public_setup_urls() (
|
|
source "$PROJECT_ROOT/install.sh"
|
|
local normalized setup ipv6
|
|
normalized="$(normalize_public_url 'https://erp.example.com:8443/setup?tenant=kaidi#start')" || return 1
|
|
setup="$(append_setup_token "$normalized" 'one-time-token')" || return 1
|
|
ipv6="$(ip_setup_base_url '2001:db8::8' 8091)" || return 1
|
|
|
|
[[ "$setup" == 'https://erp.example.com:8443/setup?tenant=kaidi&token=one-time-token#start' ]] || return 1
|
|
[[ "$ipv6" == 'http://[2001:db8::8]:8091/' ]]
|
|
)
|
|
|
|
prepare_update_release_archive() {
|
|
local tmp="$1" version="$2"
|
|
local root="$tmp/package/kaidi-erp-$version"
|
|
mkdir -p "$root/app" "$root/bin" "$root/config" "$tmp/assets"
|
|
printf 'mock jar %s\n' "$version" > "$root/app/kaidi-erp.jar"
|
|
cp "$PROJECT_ROOT/distribution/bin/erp-run" "$root/bin/erp-run"
|
|
cp "$PROJECT_ROOT/distribution/bin/erp-update" "$root/bin/erp-update"
|
|
chmod +x "$root/bin/erp-run" "$root/bin/erp-update"
|
|
printf '%s\n' "$version" > "$root/VERSION"
|
|
printf '{"version":"%s","database":"postgresql","rollbackCompatible":true}\n' \
|
|
"$version" > "$root/manifest.json"
|
|
printf 'installer jar %s\n' "$version" > "$tmp/assets/kaidi-erp-installer-$version.jar"
|
|
|
|
local archive="kaidi-erp-$version.tar.gz"
|
|
tar -czf "$tmp/assets/$archive" -C "$tmp/package" "kaidi-erp-$version"
|
|
(cd "$tmp/assets" && checksum_file "$archive" && checksum_file "kaidi-erp-installer-$version.jar") > "$tmp/assets/SHA256SUMS"
|
|
: > "$tmp/assets/SHA256SUMS.sig"
|
|
}
|
|
|
|
test_installer_downloads_and_installs_signed_release() (
|
|
local version=3.0.0 tmp server_pid=""
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-install-e2e.XXXXXX")" || return 1
|
|
cleanup_install_scenario() {
|
|
[[ -z "$server_pid" ]] || kill "$server_pid" 2>/dev/null || true
|
|
[[ -z "$server_pid" ]] || wait "$server_pid" 2>/dev/null || true
|
|
rm -rf "$tmp"
|
|
}
|
|
trap cleanup_install_scenario EXIT
|
|
|
|
prepare_update_release_archive "$tmp" "$version" || return 1
|
|
openssl genpkey -algorithm ED25519 -out "$tmp/private.pem" >/dev/null 2>&1 || return 1
|
|
openssl pkey -in "$tmp/private.pem" -pubout -out "$tmp/public.pem" >/dev/null 2>&1 || return 1
|
|
openssl pkeyutl -sign -rawin -inkey "$tmp/private.pem" \
|
|
-in "$tmp/assets/SHA256SUMS" -out "$tmp/assets/SHA256SUMS.sig" || return 1
|
|
|
|
python3 - "$tmp" "$version" <<'PY' > "$tmp/server.log" 2>&1 &
|
|
import json
|
|
import os
|
|
import sys
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
root, version = sys.argv[1:]
|
|
assets = os.path.join(root, "assets")
|
|
archive = f"kaidi-erp-{version}.tar.gz"
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def send_bytes(self, status, body, content_type="application/octet-stream"):
|
|
self.send_response(status)
|
|
self.send_header("Content-Type", content_type)
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
if self.path == "/api/v1/repos/awaioi/ERP/releases/latest":
|
|
legacy = "http://legacy.invalid/assets"
|
|
payload = {
|
|
"tag_name": "v" + version,
|
|
"draft": False,
|
|
"prerelease": False,
|
|
"assets": [
|
|
{"name": archive, "browser_download_url": legacy + "/" + archive},
|
|
{"name": f"kaidi-erp-installer-{version}.jar", "browser_download_url": legacy + f"/kaidi-erp-installer-{version}.jar"},
|
|
{"name": "SHA256SUMS", "browser_download_url": legacy + "/SHA256SUMS"},
|
|
{"name": "SHA256SUMS.sig", "browser_download_url": legacy + "/SHA256SUMS.sig"},
|
|
],
|
|
}
|
|
self.send_bytes(200, json.dumps(payload).encode(), "application/json")
|
|
return
|
|
release_prefix = f"/awaioi/ERP/releases/download/v{version}/"
|
|
if self.path.startswith(release_prefix):
|
|
name = self.path.removeprefix(release_prefix)
|
|
if name not in {archive, f"kaidi-erp-installer-{version}.jar", "SHA256SUMS", "SHA256SUMS.sig"}:
|
|
self.send_bytes(404, b"not found", "text/plain")
|
|
return
|
|
with open(os.path.join(assets, name), "rb") as handle:
|
|
self.send_bytes(200, handle.read())
|
|
return
|
|
self.send_bytes(404, b"not found", "text/plain")
|
|
|
|
def log_message(self, *_):
|
|
pass
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
with open(os.path.join(root, "server.port"), "w", encoding="ascii") as handle:
|
|
handle.write(str(server.server_port))
|
|
server.serve_forever()
|
|
PY
|
|
server_pid=$!
|
|
|
|
local attempt=0
|
|
while [[ ! -s "$tmp/server.port" && "$attempt" -lt 100 ]]; do
|
|
sleep 0.02
|
|
attempt=$((attempt + 1))
|
|
done
|
|
[[ -s "$tmp/server.port" ]] || return 1
|
|
local port
|
|
port="$(<"$tmp/server.port")"
|
|
|
|
source "$PROJECT_ROOT/install.sh"
|
|
trap cleanup_install_scenario EXIT
|
|
GITEA_BASE_URL="http://127.0.0.1:$port"
|
|
REPOSITORY=awaioi/ERP
|
|
ALLOW_INSECURE=1
|
|
INSTALL_ROOT="$tmp/install"
|
|
ERP_CONFIG_ROOT="$tmp/config"
|
|
ERP_STATE_ROOT="$tmp/state"
|
|
ERP_LOG_ROOT="$tmp/log"
|
|
NO_SERVICE=0
|
|
PUBLIC_KEY="$(<"$tmp/public.pem")"
|
|
|
|
detect_platform() { PLATFORM=darwin; ARCH=amd64; }
|
|
check_and_install_dependencies() { JAVA_BIN="$(command -v java)"; return 0; }
|
|
start_service() { return 0; }
|
|
wait_for_installer() { return 0; }
|
|
detect_public_address() { printf '203.0.113.10'; }
|
|
|
|
main > "$tmp/install.log" 2>&1 || return 1
|
|
|
|
[[ "$(readlink "$INSTALL_ROOT/current")" == "releases/$version" ]] || return 1
|
|
[[ -r "$INSTALL_ROOT/current/app/kaidi-erp.jar" ]] || return 1
|
|
(
|
|
source "$ERP_CONFIG_ROOT/erp.env"
|
|
[[ -z "${SPRING_PROFILES_ACTIVE:-}" ]]
|
|
[[ "$OA_UPDATE_ENABLED" == "true" ]]
|
|
[[ "$OA_UPDATE_GITEA_BASE_URL" == "http://127.0.0.1:$port" ]]
|
|
[[ "$ERP_SETUP_TOKEN" =~ ^[0-9a-f]{64}$ ]]
|
|
[[ -z "${OA_DB_URL:-}" ]]
|
|
) || return 1
|
|
grep -Eq '^\[ERP Install\] Setup URL: http://203\.0\.113\.10:8091/\?token=[0-9a-f]{64}$' \
|
|
"$tmp/install.log"
|
|
)
|
|
|
|
run_update_scenario() (
|
|
local mode="$1" version=2.0.0
|
|
local tmp server_pid="" supervisor_pid=""
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-update-e2e.XXXXXX")" || return 1
|
|
cleanup_scenario() {
|
|
[[ -z "$supervisor_pid" ]] || kill "$supervisor_pid" 2>/dev/null || true
|
|
[[ -z "$server_pid" ]] || kill "$server_pid" 2>/dev/null || true
|
|
[[ -z "$supervisor_pid" ]] || wait "$supervisor_pid" 2>/dev/null || true
|
|
[[ -z "$server_pid" ]] || wait "$server_pid" 2>/dev/null || true
|
|
rm -rf "$tmp"
|
|
}
|
|
trap cleanup_scenario EXIT
|
|
|
|
prepare_update_release_archive "$tmp" "$version" || return 1
|
|
mkdir -p "$tmp/releases/1.0.0" "$tmp/run" "$tmp/state"
|
|
printf '1.0.0\n' > "$tmp/releases/1.0.0/VERSION"
|
|
ln -s releases/1.0.0 "$tmp/current"
|
|
|
|
python3 - "$tmp" "$mode" <<'PY' > "$tmp/server.log" 2>&1 &
|
|
import json
|
|
import os
|
|
import sys
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
root, mode = sys.argv[1:]
|
|
assets = os.path.join(root, "assets")
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def send_bytes(self, status, body, content_type="application/octet-stream"):
|
|
self.send_response(status)
|
|
self.send_header("Content-Type", content_type)
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
if self.path == "/api/v1/repos/awaioi/ERP/releases/latest":
|
|
legacy = "http://legacy.invalid"
|
|
payload = {
|
|
"tag_name": "v2.0.0",
|
|
"draft": False,
|
|
"prerelease": False,
|
|
"assets": [
|
|
{"name": "kaidi-erp-2.0.0.tar.gz", "browser_download_url": legacy + "/assets/kaidi-erp-2.0.0.tar.gz"},
|
|
{"name": "SHA256SUMS", "browser_download_url": legacy + "/assets/SHA256SUMS"},
|
|
{"name": "SHA256SUMS.sig", "browser_download_url": legacy + "/assets/SHA256SUMS.sig"},
|
|
],
|
|
}
|
|
self.send_bytes(200, json.dumps(payload).encode(), "application/json")
|
|
return
|
|
if self.path == "/health":
|
|
current = os.path.basename(os.path.realpath(os.path.join(root, "current")))
|
|
healthy = current == "2.0.0" if mode == "success" else current == "1.0.0"
|
|
self.send_bytes(200 if healthy else 503, b'{"status":"UP"}' if healthy else b'{"status":"DOWN"}', "application/json")
|
|
return
|
|
release_prefix = "/awaioi/ERP/releases/download/v2.0.0/"
|
|
if self.path.startswith(release_prefix):
|
|
name = self.path.removeprefix(release_prefix)
|
|
if name not in {"kaidi-erp-2.0.0.tar.gz", "SHA256SUMS", "SHA256SUMS.sig"}:
|
|
self.send_bytes(404, b"not found", "text/plain")
|
|
return
|
|
with open(os.path.join(assets, name), "rb") as handle:
|
|
self.send_bytes(200, handle.read())
|
|
return
|
|
self.send_bytes(404, b"not found", "text/plain")
|
|
|
|
def log_message(self, *_):
|
|
pass
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
with open(os.path.join(root, "server.port"), "w", encoding="ascii") as handle:
|
|
handle.write(str(server.server_port))
|
|
server.serve_forever()
|
|
PY
|
|
server_pid=$!
|
|
|
|
local attempt=0
|
|
while [[ ! -s "$tmp/server.port" && "$attempt" -lt 100 ]]; do
|
|
sleep 0.02
|
|
attempt=$((attempt + 1))
|
|
done
|
|
[[ -s "$tmp/server.port" ]] || return 1
|
|
local port
|
|
port="$(<"$tmp/server.port")"
|
|
|
|
(
|
|
child=""
|
|
trap '[[ -z "$child" ]] || kill "$child" 2>/dev/null || true' EXIT
|
|
trap 'exit 0' TERM INT
|
|
while :; do
|
|
sleep 300 &
|
|
child=$!
|
|
printf '%s\n' "$child" > "$tmp/run/app.pid"
|
|
wait "$child" 2>/dev/null || true
|
|
child=""
|
|
sleep 0.05
|
|
done
|
|
) &
|
|
supervisor_pid=$!
|
|
|
|
attempt=0
|
|
while [[ ! -s "$tmp/run/app.pid" && "$attempt" -lt 100 ]]; do
|
|
sleep 0.02
|
|
attempt=$((attempt + 1))
|
|
done
|
|
[[ -s "$tmp/run/app.pid" ]] || return 1
|
|
|
|
{
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
printf 'OA_UPDATE_STATE_FILE=%q\n' "$tmp/state/update.json"
|
|
printf 'OA_UPDATE_GITEA_BASE_URL=http://127.0.0.1:%s\n' "$port"
|
|
printf 'OA_UPDATE_ALLOW_INSECURE_HTTP=true\n'
|
|
printf 'OA_UPDATE_REPOSITORY=awaioi/ERP\n'
|
|
printf 'ERP_HEALTH_URL=http://127.0.0.1:%s/health\n' "$port"
|
|
printf 'ERP_UPDATE_REQUIRE_SIGNATURE=false\n'
|
|
printf 'ERP_UPDATE_BACKUP_MODE=none\n'
|
|
printf 'ERP_UPDATE_HEALTH_TIMEOUT_SECONDS=3\n'
|
|
printf 'ERP_UPDATE_HEALTH_POLL_SECONDS=1\n'
|
|
} > "$tmp/erp.env"
|
|
|
|
local status=0 phase current
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-update" install "$version" \
|
|
> "$tmp/update.log" 2>&1 || status=$?
|
|
phase="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["phase"])' "$tmp/state/update.json")" \
|
|
|| return 1
|
|
current="$(readlink "$tmp/current")"
|
|
|
|
if [[ "$mode" == "success" ]]; then
|
|
[[ "$status" == "0" && "$phase" == "SUCCEEDED" && "$current" == "releases/2.0.0" ]]
|
|
else
|
|
[[ "$status" != "0" && "$phase" == "ROLLED_BACK" && "$current" == "releases/1.0.0" ]]
|
|
fi
|
|
)
|
|
|
|
test_update_helper_installs_healthy_release() (
|
|
run_update_scenario success
|
|
)
|
|
|
|
test_update_helper_rolls_back_unhealthy_release() (
|
|
run_update_scenario rollback
|
|
)
|
|
|
|
test_update_lock_rejects_concurrent_process() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-lock-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/bin" "$tmp/run" "$tmp/state"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'printf '\''%s\n'\'' "$$" > "$MOCK_CURL_PID_FILE"' \
|
|
'trap '\''exit 1'\'' TERM INT' \
|
|
'sleep 30' \
|
|
'exit 1' > "$tmp/bin/curl"
|
|
chmod +x "$tmp/bin/curl"
|
|
{
|
|
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
|
|
printf 'OA_UPDATE_STATE_FILE=%q\n' "$tmp/state/update.json"
|
|
printf 'OA_UPDATE_GITEA_BASE_URL=%q\n' 'http://127.0.0.1:1'
|
|
printf 'OA_UPDATE_ALLOW_INSECURE_HTTP=true\n'
|
|
printf 'OA_UPDATE_REPOSITORY=awaioi/ERP\n'
|
|
} > "$tmp/erp.env"
|
|
|
|
PATH="$tmp/bin:$PATH" MOCK_CURL_PID_FILE="$tmp/curl.pid" \
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-update" install > "$tmp/first.log" 2>&1 &
|
|
local first=$! attempt=0
|
|
while [[ ! -s "$tmp/run/update.lock" && "$attempt" -lt 100 ]]; do
|
|
sleep 0.02
|
|
attempt=$((attempt + 1))
|
|
done
|
|
[[ -s "$tmp/run/update.lock" ]] || { kill "$first" 2>/dev/null || true; return 1; }
|
|
|
|
local output status=0 rejected=0
|
|
output="$(PATH="$tmp/bin:$PATH" MOCK_CURL_PID_FILE="$tmp/curl-2.pid" \
|
|
ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
|
|
"$PROJECT_ROOT/distribution/bin/erp-update" install 2>&1)" || status=$?
|
|
[[ "$status" -ne 0 && "$output" == *'已有更新任务正在执行'* ]] && rejected=1
|
|
|
|
[[ ! -r "$tmp/curl.pid" ]] || kill "$(<"$tmp/curl.pid")" 2>/dev/null || true
|
|
kill "$first" 2>/dev/null || true
|
|
wait "$first" 2>/dev/null || true
|
|
[[ "$rejected" == "1" ]]
|
|
)
|
|
|
|
test_installer_moves_database_setup_to_web_wizard() (
|
|
local output status=0
|
|
output="$(bash "$PROJECT_ROOT/install.sh" --db-mode existing 2>&1)" || status=$?
|
|
[[ "$status" -ne 0 && "$output" == *'unknown option: --db-mode'* ]]
|
|
)
|
|
|
|
test_installer_defaults_to_official_gitea_url() (
|
|
local output
|
|
output="$(ERP_GITEA_BASE_URL= bash -c '
|
|
root="$1"
|
|
set --
|
|
source "$root/install.sh"
|
|
printf "%s\n" "$GITEA_BASE_URL"
|
|
' _ "$PROJECT_ROOT")" || return 1
|
|
[[ "$output" == 'https://git.awaioi.com' ]]
|
|
)
|
|
|
|
test_linux_service_preflight_requires_systemd() (
|
|
local tmp output status=0
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-systemd-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/bin"
|
|
source "$PROJECT_ROOT/install.sh"
|
|
PLATFORM=linux
|
|
NO_SERVICE=0
|
|
output="$(PATH="$tmp/bin" validate_service_manager 2>&1)" || status=$?
|
|
[[ "$status" -ne 0 && "$output" == *'systemd'* ]] || return 1
|
|
NO_SERVICE=1 PATH="$tmp/bin" validate_service_manager
|
|
)
|
|
|
|
test_systemd_unit_uses_compatible_protection() (
|
|
grep -Fqx 'ProtectSystem=full' <(sed -n '/^ cat > \/etc\/systemd\/system\/kaidi-erp.service/,/^ systemctl daemon-reload/p' "$PROJECT_ROOT/install.sh")
|
|
! grep -Fq 'ProtectSystem=strict' "$PROJECT_ROOT/install.sh"
|
|
)
|
|
|
|
test_systemd_unit_preserves_update_helper_during_restart() (
|
|
local unit
|
|
unit="$(sed -n '/^ cat > \/etc\/systemd\/system\/kaidi-erp.service/,/^ systemctl daemon-reload/p' "$PROJECT_ROOT/install.sh")"
|
|
grep -Fqx 'KillMode=process' <<< "$unit"
|
|
! grep -Fq 'KillMode=control-group' <<< "$unit"
|
|
)
|
|
|
|
test_systemd_unit_uses_unquoted_legacy_paths() (
|
|
local unit
|
|
unit="$(sed -n '/^ cat > \/etc\/systemd\/system\/kaidi-erp.service/,/^ systemctl daemon-reload/p' "$PROJECT_ROOT/install.sh")"
|
|
grep -Fqx 'WorkingDirectory=$INSTALL_ROOT' <<< "$unit"
|
|
grep -Fqx 'ExecStart=$INSTALL_ROOT/current/bin/erp-run' <<< "$unit"
|
|
grep -Fqx 'ReadWritePaths=$INSTALL_ROOT $CONFIG_ROOT $STATE_ROOT $LOG_ROOT' <<< "$unit"
|
|
! grep -Fq 'WorkingDirectory="$INSTALL_ROOT"' <<< "$unit"
|
|
)
|
|
|
|
test_uninstaller_purges_database_before_removing_files() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-uninstall-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/bin" "$tmp/install" "$tmp/config" "$tmp/state" "$tmp/logs"
|
|
: > "$tmp/unit.service"
|
|
{
|
|
printf 'ERP_PGHOST=127.0.0.1\n'
|
|
printf 'ERP_PGPORT=5432\n'
|
|
printf 'ERP_PGDATABASE=kaidi_test\n'
|
|
printf 'ERP_PGSSLMODE=disable\n'
|
|
printf 'OA_DB_USERNAME=kaidi_test\n'
|
|
printf 'OA_DB_PASSWORD='\''test-password'\''\n'
|
|
printf 'ERP_INSTALL_ROOT=%q\n' "$tmp/install"
|
|
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
|
|
} > "$tmp/config/erp.env"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'printf '\''%s\n'\'' "$*" >> "$MOCK_SYSTEMCTL_LOG"' \
|
|
'if [[ "${1:-}" == "is-active" ]]; then exit 3; fi' \
|
|
'exit 0' > "$tmp/bin/systemctl"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'printf '\''ARGS %s\n'\'' "$*" >> "$MOCK_PSQL_LOG"' \
|
|
'if [[ " $* " == *" -c "* ]]; then printf '\''t\n'\''; exit 0; fi' \
|
|
'cat >> "$MOCK_PSQL_LOG"' \
|
|
'exit 0' > "$tmp/bin/psql"
|
|
chmod +x "$tmp/bin/systemctl" "$tmp/bin/psql"
|
|
|
|
PATH="$tmp/bin:$PATH" \
|
|
MOCK_SYSTEMCTL_LOG="$tmp/systemctl.log" \
|
|
MOCK_PSQL_LOG="$tmp/psql.log" \
|
|
ERP_UNINSTALL_TEST_MODE=1 \
|
|
ERP_UNINSTALL_SYSTEMD_UNIT_FILE="$tmp/unit.service" \
|
|
"$PROJECT_ROOT/uninstall.sh" --purge-database --yes \
|
|
--config-file "$tmp/config/erp.env" \
|
|
--install-root "$tmp/install" \
|
|
--config-root "$tmp/config" \
|
|
--state-root "$tmp/state" \
|
|
--log-root "$tmp/logs" > "$tmp/uninstall.log" 2>&1 || return 1
|
|
|
|
[[ ! -e "$tmp/install" && ! -e "$tmp/config" && ! -e "$tmp/state" && ! -e "$tmp/logs" ]] || return 1
|
|
[[ ! -e "$tmp/unit.service" ]] || return 1
|
|
grep -Fq 'DROP SCHEMA IF EXISTS public CASCADE;' "$tmp/psql.log" || return 1
|
|
grep -Fq 'stop kaidi-erp.service' "$tmp/systemctl.log" || return 1
|
|
grep -Fq 'disable kaidi-erp.service' "$tmp/systemctl.log"
|
|
)
|
|
|
|
test_uninstaller_rejects_unsafe_paths() (
|
|
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /etc) >/dev/null 2>&1 || return 1
|
|
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /etc/ssh) >/dev/null 2>&1 || return 1
|
|
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /opt/kaidi-erp/../../etc) >/dev/null 2>&1
|
|
)
|
|
|
|
test_uninstaller_aborts_when_service_remains_active() (
|
|
local tmp output status=0
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-uninstall-active.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
mkdir -p "$tmp/bin" "$tmp/install" "$tmp/config" "$tmp/state" "$tmp/logs"
|
|
{
|
|
printf 'ERP_PGHOST=127.0.0.1\n'
|
|
printf 'ERP_PGPORT=5432\n'
|
|
printf 'ERP_PGDATABASE=kaidi_test\n'
|
|
printf 'ERP_PGSSLMODE=disable\n'
|
|
printf 'OA_DB_USERNAME=kaidi_test\n'
|
|
printf 'OA_DB_PASSWORD=test-password\n'
|
|
printf 'ERP_INSTALL_ROOT=%q\n' "$tmp/install"
|
|
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
|
|
} > "$tmp/config/erp.env"
|
|
printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'if [[ "${1:-}" == "stop" ]]; then exit 1; fi' \
|
|
'if [[ "${1:-}" == "show" ]]; then printf '\''loaded\n'\''; exit 0; fi' \
|
|
'if [[ "${1:-}" == "is-active" ]]; then exit 0; fi' \
|
|
'exit 0' > "$tmp/bin/systemctl"
|
|
printf '%s\n' '#!/usr/bin/env bash' 'touch "$MOCK_PSQL_CALLED"' 'exit 0' > "$tmp/bin/psql"
|
|
chmod +x "$tmp/bin/systemctl" "$tmp/bin/psql"
|
|
|
|
output="$(PATH="$tmp/bin:$PATH" MOCK_PSQL_CALLED="$tmp/psql.called" \
|
|
ERP_UNINSTALL_TEST_MODE=1 ERP_UNINSTALL_SYSTEMD_UNIT_FILE="$tmp/unit.service" \
|
|
"$PROJECT_ROOT/uninstall.sh" --purge-database --yes \
|
|
--config-file "$tmp/config/erp.env" \
|
|
--install-root "$tmp/install" \
|
|
--config-root "$tmp/config" \
|
|
--state-root "$tmp/state" \
|
|
--log-root "$tmp/logs" 2>&1)" || status=$?
|
|
|
|
[[ "$status" -ne 0 && "$output" == *'unable to stop kaidi-erp.service'* ]] || return 1
|
|
[[ ! -e "$tmp/psql.called" ]] || return 1
|
|
[[ -d "$tmp/install" && -d "$tmp/config" && -d "$tmp/state" && -d "$tmp/logs" ]]
|
|
)
|
|
|
|
test_no_service_install_disables_online_update() (
|
|
local tmp
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-no-service-test.XXXXXX")" || return 1
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
source "$PROJECT_ROOT/install.sh"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
CONFIG_ROOT="$tmp/config"
|
|
STATE_ROOT="$tmp/state"
|
|
INSTALL_ROOT="$tmp/install"
|
|
PLATFORM=darwin
|
|
NO_SERVICE=1
|
|
VERSION=1.0.0
|
|
JAVA_BIN="$(command -v java)"
|
|
GITEA_BASE_URL=https://example.test
|
|
REPOSITORY=awaioi/ERP
|
|
mkdir -p "$CONFIG_ROOT" "$STATE_ROOT" "$INSTALL_ROOT"
|
|
|
|
write_bootstrap_configuration || return 1
|
|
|
|
grep -Fqx 'OA_UPDATE_ENABLED=false' "$CONFIG_ROOT/erp.env"
|
|
)
|
|
|
|
test_release_workflow_uses_scoped_job_token() (
|
|
local workflow="$PROJECT_ROOT/.gitea/workflows/release.yml"
|
|
grep -Fqx ' contents: read' "$workflow"
|
|
grep -Fqx ' releases: write' "$workflow"
|
|
grep -Fq 'GITEA_BASE_URL: ${{ github.server_url }}' "$workflow"
|
|
grep -Fq 'GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}' "$workflow"
|
|
grep -Fq 'uses: actions/setup-java@v4' "$workflow"
|
|
grep -Fq 'java-version: "17"' "$workflow"
|
|
grep -Fq "git log --no-merges --format='- %s (\`%h\`)'" "$workflow"
|
|
grep -Fq 'release-notes.md' "$workflow"
|
|
! grep -Fq 'RELEASE_GITEA_TOKEN' "$workflow"
|
|
! grep -Fq 'vars.GITEA_BASE_URL' "$workflow"
|
|
)
|
|
|
|
test_installer_runs_when_piped_to_bash() (
|
|
local output status=0
|
|
output="$(bash -s -- --no-service --gitea-url invalid://example.test \
|
|
< "$PROJECT_ROOT/install.sh" 2>&1)" || status=$?
|
|
[[ "$status" -ne 0 ]]
|
|
[[ "$output" == *'[ERP Install] ERROR:'* ]]
|
|
[[ "$output" != *'BASH_SOURCE'* ]]
|
|
)
|
|
|
|
test_installer_shows_live_health_progress() (
|
|
local page="$PROJECT_ROOT/oa-backend/src/installer/resources/static/index.html"
|
|
grep -Fq 'role="progressbar"' "$page"
|
|
grep -Fq 'id="install-log"' "$page"
|
|
grep -Fq 'Math.min(95, estimated)' "$page"
|
|
grep -Fq '健康检查第 ${attempt} 次' "$page"
|
|
grep -Fq '本页已等待 ${formatElapsed' "$page"
|
|
grep -Fq 'installLocked' "$page"
|
|
)
|
|
|
|
test_app_honors_reverse_proxy_headers() (
|
|
grep -Fqx ' forward-headers-strategy: framework' \
|
|
"$PROJECT_ROOT/oa-backend/src/main/resources/application.yml"
|
|
grep -Fqx ' forward-headers-strategy: framework' \
|
|
"$PROJECT_ROOT/oa-backend/src/installer/resources/application.yml"
|
|
)
|
|
|
|
run_test 'erp-run preserves Java option arguments' test_erp_run_preserves_java_option_arguments
|
|
run_test 'erp-run migrates the legacy Gitea update source' test_erp_run_migrates_legacy_update_source
|
|
run_test 'erp-run finalizes a healthy pending installation' test_erp_run_finalizes_healthy_pending_install
|
|
run_test 'erp-run preserves a failed pending installation' test_erp_run_preserves_failed_pending_install
|
|
run_test 'installer accepts a correctly signed archive' test_installer_verifies_signed_safe_archive
|
|
run_test 'installer rejects symlinks in release archives' test_installer_rejects_archive_symlinks
|
|
run_test 'stable update channel rejects prerelease tags' test_stable_channel_rejects_prerelease_tag
|
|
run_test 'update helper preserves release metadata' test_update_helper_preserves_release_metadata
|
|
run_test 'installer builds public setup URLs safely' test_installer_builds_public_setup_urls
|
|
run_test 'installer downloads and installs a signed release end to end' test_installer_downloads_and_installs_signed_release
|
|
run_test 'update helper installs a healthy release end to end' test_update_helper_installs_healthy_release
|
|
run_test 'update helper rolls back an unhealthy release end to end' test_update_helper_rolls_back_unhealthy_release
|
|
run_test 'update helper rejects a concurrent process' test_update_lock_rejects_concurrent_process
|
|
run_test 'installer moves database setup to the web wizard' test_installer_moves_database_setup_to_web_wizard
|
|
run_test 'installer defaults to the official Gitea URL' test_installer_defaults_to_official_gitea_url
|
|
run_test 'Linux production install requires systemd' test_linux_service_preflight_requires_systemd
|
|
run_test 'systemd unit uses compatible protection' test_systemd_unit_uses_compatible_protection
|
|
run_test 'systemd unit preserves update helper during restart' test_systemd_unit_preserves_update_helper_during_restart
|
|
run_test 'systemd unit uses unquoted legacy paths' test_systemd_unit_uses_unquoted_legacy_paths
|
|
run_test 'uninstaller purges database before removing files' test_uninstaller_purges_database_before_removing_files
|
|
run_test 'uninstaller rejects unsafe paths' test_uninstaller_rejects_unsafe_paths
|
|
run_test 'uninstaller aborts while the service remains active' test_uninstaller_aborts_when_service_remains_active
|
|
run_test 'no-service install disables online update' test_no_service_install_disables_online_update
|
|
run_test 'release workflow uses the scoped Gitea job token' test_release_workflow_uses_scoped_job_token
|
|
run_test 'installer starts correctly when piped to bash' test_installer_runs_when_piped_to_bash
|
|
run_test 'installer shows live formal-service health progress' test_installer_shows_live_health_progress
|
|
run_test 'application honors standard reverse-proxy headers' test_app_honors_reverse_proxy_headers
|
|
|
|
printf 'RESULT pass=%s fail=%s\n' "$PASS" "$FAIL"
|
|
[[ "$FAIL" -eq 0 ]]
|