#!/usr/bin/env bash
# Integration: cancellation notification terminates a running worker.
# shellcheck disable=SC2034 # Used by test runner for reporting.
TEST_DESC="Cancellation request aborts a running worker."
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../common/env.sh
# shellcheck disable=SC1091
. "${SCRIPT_DIR}/../common/env.sh"
# shellcheck source=../common/assert.sh
# shellcheck disable=SC1091
. "${SCRIPT_DIR}/../common/assert.sh"
test_require_command jq
test_create_tmpdir
WORKSPACE="${TEST_TMPDIR}/cancel"
test_stage_workspace "${WORKSPACE}"
mkdir -p "${WORKSPACE}/tools/slow"
cat <<'META' >"${WORKSPACE}/tools/slow/tool.meta.json"
{"name":"cancel.slow","description":"slow","arguments":{"type":"object","properties":{}}}
META
cat <<'SH' >"${WORKSPACE}/tools/slow/tool.sh"
#!/usr/bin/env bash
sleep 3
echo "done"
SH
chmod +x "${WORKSPACE}/tools/slow/tool.sh"
# Use a temp dir for pipes; some runners dislike mkfifo directly under /tmp.
FIFO_ROOT="${WORKSPACE}/pipes"
mkdir -p "${FIFO_ROOT}"
PIPE_IN="${FIFO_ROOT}/in"
PIPE_OUT="${FIFO_ROOT}/out"
rm -f "${PIPE_IN}" "${PIPE_OUT}"
mkfifo "${PIPE_IN}" "${PIPE_OUT}"
(
cd "${WORKSPACE}" || exit 1
MCPBASH_SKIP_PROCESS_GROUP_LOOKUP=1 MCPBASH_PROJECT_ROOT="${WORKSPACE}" ./bin/mcp-bash <"${PIPE_IN}" >"${PIPE_OUT}" &
echo $! >"${WORKSPACE}/server.pid"
) || exit 1
exec 3>"${PIPE_IN}"
exec 4<"${PIPE_OUT}"
send() { printf '%s\n' "$1" >&3; }
read_resp() {
local line
read -r -t 5 -u 4 line && printf '%s' "${line}"
}
send '{"jsonrpc":"2.0","id":"init","method":"initialize","params":{}}'
read_resp >/dev/null || true
send '{"jsonrpc":"2.0","method":"notifications/initialized"}'
send '{"jsonrpc":"2.0","id":"slow","method":"tools/call","params":{"name":"cancel.slow","arguments":{}}}'
sleep 1
send '{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"slow"}}'
# Ping to flush events (re-sent periodically below until a response lands)
send '{"jsonrpc":"2.0","id":"ping","method":"ping"}'
got_call=false
got_ping=false
deadline=$((SECONDS + 30))
next_ping=$((SECONDS + 3))
while [ "${SECONDS}" -lt "${deadline}" ]; do
if [ "${got_ping}" != true ] && [ "${SECONDS}" -ge "${next_ping}" ]; then
send '{"jsonrpc":"2.0","id":"ping","method":"ping"}'
next_ping=$((SECONDS + 3))
fi
line="$(read_resp || true)"
[ -z "${line}" ] && continue
id="$(printf '%s' "${line}" | jq -er 'try .id // empty catch ""' 2>/dev/null || true)"
[ -z "${id}" ] && continue
if [ "${id}" = "slow" ]; then
got_call=true
fi
if [ "${id}" = "ping" ]; then
got_ping=true
fi
if [ "${got_ping}" = true ]; then
break
fi
done
if [ "${got_call}" = true ]; then
test_fail "slow call should be cancelled without result"
fi
send '{"jsonrpc":"2.0","id":"shutdown","method":"shutdown"}'
send '{"jsonrpc":"2.0","id":"exit","method":"exit"}'
exec 3>&-
while read -t 2 -r -u 4 _line; do :; done
exec 4<&-
server_status=0
if [ -f "${WORKSPACE}/server.pid" ]; then
server_pid="$(cat "${WORKSPACE}/server.pid")"
wait_deadline=$((SECONDS + 30))
while kill -0 "${server_pid}" 2>/dev/null && [ "${SECONDS}" -lt "${wait_deadline}" ]; do
sleep 1
done
if kill -0 "${server_pid}" 2>/dev/null; then
server_status=1
else
wait "${server_pid}" 2>/dev/null || server_status=$?
fi
# Ensure no stray server remains; best-effort cleanup on CI.
if kill -0 "${server_pid}" 2>/dev/null; then
kill "${server_pid}" 2>/dev/null || true
wait "${server_pid}" 2>/dev/null || true
fi
fi
# Accept success if:
# - We got a ping response (server was responsive after cancellation), OR
# - Server exited cleanly (status 0), OR
# - Server exited due to signal (128+) which is acceptable during cleanup, OR
# - wait returned 127 (process not a child - expected when started in subshell)
if [ "${got_ping}" != true ]; then
# No ping received - check if exit status indicates a problem
# Status 0 = clean exit, 127 = wait can't track (subshell), 128+ = signal
if [ "${server_status}" -ne 0 ] && [ "${server_status}" -ne 127 ] && [ "${server_status}" -lt 128 ]; then
test_fail "ping response missing after cancellation and server exited with error status ${server_status}"
fi
fi
printf 'Cancellation tests passed.\n'