#!/usr/bin/env bash # Lifecycle regression for qa/run.sh refusal paths. # Verifies that run.sh refuses to start when a container name or port is # already taken, and that the refusal leaves the pre-existing resources # untouched (no container deletion, no killing of the port holder). # The script only manages resources it created itself. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PG_CONTAINER=fund-lab-qa-pg-3c1 PG_IMAGE=postgres:16-alpine API_PORT=5095 TMP="$(mktemp -d /tmp/opencode/fund-lab-qa-lifecycle-XXXXXX)" HOLDER_PID="" cleanup() { local status=$? trap - EXIT INT TERM [ -n "$HOLDER_PID" ] && kill "$HOLDER_PID" 2>/dev/null || true [ -n "$HOLDER_PID" ] && wait "$HOLDER_PID" 2>/dev/null || true if [ -n "$DUMMY_ID" ]; then owned="$(docker ps -a --filter "id=$DUMMY_ID" --format '{{.Names}}' 2>/dev/null || true)" [ "$owned" = "$PG_CONTAINER" ] && docker rm -f "$DUMMY_ID" >/dev/null 2>&1 || true fi rm -rf "$TMP" exit "$status" } DUMMY_ID="" trap cleanup EXIT INT TERM log() { printf '[lifecycle] %s\n' "$*"; } fail() { printf '[lifecycle] FAIL: %s\n' "$*" >&2; exit 1; } pass() { printf '[lifecycle] PASS: %s\n' "$*"; } command -v docker >/dev/null || fail "docker is required" docker info >/dev/null 2>&1 || fail "docker daemon is not reachable" docker image inspect "$PG_IMAGE" >/dev/null 2>&1 || fail "$PG_IMAGE is not available locally" docker ps -a --format '{{.Names}}' | grep -qx "$PG_CONTAINER" && fail "container $PG_CONTAINER already exists; refusing to proceed" log "scenario 1: pre-existing container name is refused and preserved" DUMMY_ID="$(docker run -d --name "$PG_CONTAINER" "$PG_IMAGE" sleep 120)" if bash "$ROOT/qa/run.sh" >"$TMP/refuse-container.log" 2>&1; then fail "run.sh should refuse when $PG_CONTAINER already exists" fi grep -q "already exists" "$TMP/refuse-container.log" || fail "refusal reason missing from log" [ "$(docker ps -a --filter "id=$DUMMY_ID" --format '{{.Names}}' 2>/dev/null)" = "$PG_CONTAINER" ] \ || fail "pre-existing container was removed or modified by the refused run" pass "refusal preserved the pre-existing container" docker rm -f "$DUMMY_ID" >/dev/null DUMMY_ID="" log "scenario 2: occupied API port is refused and the holder is untouched" python3 -m http.server "$API_PORT" --bind 127.0.0.1 >/dev/null 2>&1 & HOLDER_PID=$! sleep 0.5 kill -0 "$HOLDER_PID" 2>/dev/null || fail "port holder failed to start" if bash "$ROOT/qa/run.sh" >"$TMP/refuse-port.log" 2>&1; then fail "run.sh should refuse when port $API_PORT is occupied" fi grep -q "port $API_PORT is already in use" "$TMP/refuse-port.log" || fail "refusal reason missing from log" kill -0 "$HOLDER_PID" 2>/dev/null || fail "port holder was killed by the refused run" pass "refusal preserved the port holder" log "all lifecycle scenarios passed"