240 lines
6.5 KiB
Bash
240 lines
6.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
SCRIPT_PATH="${ROOT_DIR}/scripts/github-actions/validate-deploy.sh"
|
|
IMAGE_REF="ghcr.io/x-evor/xworkmate-bridge:425a38f1e8076899400d4a858d4678dffd876afb"
|
|
|
|
RUN_OUTPUT=""
|
|
RUN_STATUS=0
|
|
RUN_TMP_DIR=""
|
|
RUN_STATE_DIR=""
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_contains() {
|
|
local haystack="$1"
|
|
local needle="$2"
|
|
if [[ "${haystack}" != *"${needle}"* ]]; then
|
|
fail "expected output to contain: ${needle}"
|
|
fi
|
|
}
|
|
|
|
assert_not_contains() {
|
|
local haystack="$1"
|
|
local needle="$2"
|
|
if [[ "${haystack}" == *"${needle}"* ]]; then
|
|
fail "expected output to not contain: ${needle}"
|
|
fi
|
|
}
|
|
|
|
cleanup_run() {
|
|
if [[ -n "${RUN_TMP_DIR}" && -d "${RUN_TMP_DIR}" ]]; then
|
|
rm -rf "${RUN_TMP_DIR}"
|
|
fi
|
|
RUN_OUTPUT=""
|
|
RUN_STATUS=0
|
|
RUN_TMP_DIR=""
|
|
RUN_STATE_DIR=""
|
|
}
|
|
|
|
create_fake_tools() {
|
|
local scenario="$1"
|
|
local tmp_dir="$2"
|
|
|
|
mkdir -p "${tmp_dir}/bin" "${tmp_dir}/state"
|
|
|
|
cat >"${tmp_dir}/bin/curl" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
state_dir="${FAKE_CURL_STATE_DIR:?}"
|
|
scenario="${FAKE_CURL_SCENARIO:?}"
|
|
|
|
url="${@: -1}"
|
|
data=""
|
|
write_out=""
|
|
|
|
for ((i = 1; i <= $#; i += 1)); do
|
|
arg="${!i}"
|
|
case "${arg}" in
|
|
--data)
|
|
next_index=$((i + 1))
|
|
data="${!next_index}"
|
|
;;
|
|
--write-out)
|
|
next_index=$((i + 1))
|
|
write_out="${!next_index}"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
counter_file() {
|
|
printf '%s/%s.count\n' "${state_dir}" "$1"
|
|
}
|
|
|
|
read_count() {
|
|
local file
|
|
file="$(counter_file "$1")"
|
|
if [[ -f "${file}" ]]; then
|
|
cat "${file}"
|
|
return
|
|
fi
|
|
printf '0\n'
|
|
}
|
|
|
|
bump_count() {
|
|
local name="$1"
|
|
local file value
|
|
file="$(counter_file "${name}")"
|
|
value=$(( $(read_count "${name}") + 1 ))
|
|
printf '%s\n' "${value}" >"${file}"
|
|
printf '%s\n' "${value}"
|
|
}
|
|
|
|
if [[ -n "${write_out}" ]]; then
|
|
printf '200'
|
|
exit 0
|
|
fi
|
|
|
|
case "${scenario}" in
|
|
bridge-timeout)
|
|
case "${url}" in
|
|
https://xworkmate-bridge.svc.plus/api/ping)
|
|
printf '{"status":"ok","image":"ghcr.io/x-evor/xworkmate-bridge:425a38f1e8076899400d4a858d4678dffd876afb","tag":"425a38f1e8076899400d4a858d4678dffd876afb","commit":"425a38f1e8076899400d4a858d4678dffd876afb","version":"425a38f1e8076899400d4a858d4678dffd876afb"}\n'
|
|
;;
|
|
https://xworkmate-bridge.svc.plus/)
|
|
printf 'xworkmate-bridge is running\n'
|
|
;;
|
|
https://acp-server.svc.plus/*/acp/rpc)
|
|
printf '{"jsonrpc":"2.0","result":{"providers":["ok"]}}\n'
|
|
;;
|
|
https://xworkmate-bridge.svc.plus/acp/rpc)
|
|
printf 'curl: (28) Operation timed out after 20001 milliseconds with 0 bytes received\n' >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
printf 'unexpected url in bridge-timeout scenario: %s\n' "${url}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
retry-success)
|
|
case "${url}" in
|
|
https://xworkmate-bridge.svc.plus/api/ping)
|
|
ping_attempt="$(bump_count ping)"
|
|
if (( ping_attempt < 3 )); then
|
|
printf 'curl: (28) Operation timed out after 20001 milliseconds with 0 bytes received\n' >&2
|
|
exit 1
|
|
fi
|
|
printf '{"status":"ok","image":"ghcr.io/x-evor/xworkmate-bridge:425a38f1e8076899400d4a858d4678dffd876afb","tag":"425a38f1e8076899400d4a858d4678dffd876afb","commit":"425a38f1e8076899400d4a858d4678dffd876afb","version":"425a38f1e8076899400d4a858d4678dffd876afb"}\n'
|
|
;;
|
|
https://xworkmate-bridge.svc.plus/)
|
|
printf 'xworkmate-bridge is running\n'
|
|
;;
|
|
https://acp-server.svc.plus/*/acp/rpc)
|
|
printf '{"jsonrpc":"2.0","result":{"providers":["ok"]}}\n'
|
|
;;
|
|
https://xworkmate-bridge.svc.plus/acp/rpc)
|
|
if [[ "${data}" == *'"providerId":"codex"'* ]]; then
|
|
printf '{"jsonrpc":"2.0","result":{"success":true,"providerId":"codex","capabilities":{"providers":["codex"]}}}\n'
|
|
exit 0
|
|
fi
|
|
if [[ "${data}" == *'"providerId":"opencode"'* ]]; then
|
|
printf '{"jsonrpc":"2.0","result":{"success":true,"providerId":"opencode","capabilities":{"providers":["opencode"]}}}\n'
|
|
exit 0
|
|
fi
|
|
if [[ "${data}" == *'"providerId":"gemini"'* ]]; then
|
|
printf '{"jsonrpc":"2.0","result":{"success":true,"providerId":"gemini","capabilities":{"providers":["gemini"]}}}\n'
|
|
exit 0
|
|
fi
|
|
printf 'unexpected bridge probe payload in retry-success scenario: %s\n' "${data}" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
printf 'unexpected url in retry-success scenario: %s\n' "${url}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
printf 'unsupported fake curl scenario: %s\n' "${scenario}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
EOF
|
|
|
|
cat >"${tmp_dir}/bin/sleep" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
exit 0
|
|
EOF
|
|
|
|
chmod +x "${tmp_dir}/bin/curl" "${tmp_dir}/bin/sleep"
|
|
}
|
|
|
|
run_validate_capture() {
|
|
local scenario="$1"
|
|
cleanup_run
|
|
|
|
RUN_TMP_DIR="$(mktemp -d)"
|
|
RUN_STATE_DIR="${RUN_TMP_DIR}/state"
|
|
create_fake_tools "${scenario}" "${RUN_TMP_DIR}"
|
|
|
|
set +e
|
|
RUN_OUTPUT="$(
|
|
PATH="${RUN_TMP_DIR}/bin:${PATH}" \
|
|
FAKE_CURL_SCENARIO="${scenario}" \
|
|
FAKE_CURL_STATE_DIR="${RUN_STATE_DIR}" \
|
|
BRIDGE_SERVER_URL="https://xworkmate-bridge.svc.plus" \
|
|
OPENCLAW_URL="wss://openclaw.svc.plus" \
|
|
CODEX_RPC_URL="https://acp-server.svc.plus/codex/acp/rpc" \
|
|
OPENCODE_RPC_URL="https://acp-server.svc.plus/opencode/acp/rpc" \
|
|
GEMINI_RPC_URL="https://acp-server.svc.plus/gemini/acp/rpc" \
|
|
INTERNAL_SERVICE_TOKEN="test-token" \
|
|
bash "${SCRIPT_PATH}" "${IMAGE_REF}" 2>&1
|
|
)"
|
|
RUN_STATUS=$?
|
|
set -e
|
|
}
|
|
|
|
test_bridge_timeout_stops_without_json_decode_noise() {
|
|
run_validate_capture "bridge-timeout"
|
|
|
|
if [[ "${RUN_STATUS}" -eq 0 ]]; then
|
|
fail "expected bridge-timeout scenario to fail"
|
|
fi
|
|
|
|
assert_contains "${RUN_OUTPUT}" "bridge rpc https://xworkmate-bridge.svc.plus/acp/rpc request failed"
|
|
assert_not_contains "${RUN_OUTPUT}" "JSONDecodeError"
|
|
cleanup_run
|
|
}
|
|
|
|
test_ping_retry_reaches_successful_release_validation() {
|
|
run_validate_capture "retry-success"
|
|
|
|
if [[ "${RUN_STATUS}" -ne 0 ]]; then
|
|
printf '%s\n' "${RUN_OUTPUT}" >&2
|
|
fail "expected retry-success scenario to pass"
|
|
fi
|
|
|
|
if [[ ! -f "${RUN_STATE_DIR}/ping.count" ]]; then
|
|
fail "expected ping retry counter to be recorded"
|
|
fi
|
|
|
|
ping_attempts="$(tr -d '\n' <"${RUN_STATE_DIR}/ping.count")"
|
|
if [[ "${ping_attempts}" != "3" ]]; then
|
|
fail "expected ping to succeed on third attempt, got ${ping_attempts}"
|
|
fi
|
|
|
|
cleanup_run
|
|
}
|
|
|
|
test_bridge_timeout_stops_without_json_decode_noise
|
|
test_ping_retry_reaches_successful_release_validation
|
|
|
|
printf 'validate-deploy regression tests passed\n'
|