#!/usr/bin/env bash # Сквозной прогон eva-agent с настоящим хабом (модуль wiki) и фейковой Eva. Печатает «e2e: ok». # Хаб — код из HUB_SRC (по умолчанию основной checkout sdlc-hub workspace), ТОЛЬКО с временным SDLC_HUB_HOME и портом # не 8790; живой хаб и его данные не трогаем. set -euo pipefail HERE="$(cd "$(dirname "$0")/.." && pwd)" BRIDGE="$(cd "$(git -C "$HERE" rev-parse --path-format=absolute --git-common-dir)/../.." && pwd)" # workspace jira-bridge (из worktree тоже) HUB_SRC="${HUB_SRC:-$BRIDGE/sdlc-hub}" HUB_SRC="$(cd "$HUB_SRC" && pwd)" PORT="${E2E_PORT:-$(/usr/bin/python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')}" [ "$PORT" != "8790" ] || { echo "e2e: порт 8790 занят живым хабом" >&2; exit 2; } TMP="$(mktemp -d)" export SDLC_HUB_HOME="$TMP/hub-home" mkdir -p "$SDLC_HUB_HOME" PIDS=() stop_tree() { # остановить процесс и его детей (uv run запускает хаб дочерним процессом) local p="$1" c for c in $(pgrep -P "$p" 2>/dev/null || true); do stop_tree "$c"; done kill "$p" 2>/dev/null || true } cleanup() { trap - EXIT INT TERM for p in ${PIDS[@]+"${PIDS[@]}"}; do stop_tree "$p"; done for _ in 1 2 3 4 5 6 7 8 9 10; do alive=0 for p in ${PIDS[@]+"${PIDS[@]}"}; do kill -0 "$p" 2>/dev/null && alive=1; done [ "$alive" = 0 ] && break sleep 0.2 done for p in ${PIDS[@]+"${PIDS[@]}"}; do kill -9 "$p" 2>/dev/null || true; done rm -rf "$TMP" } trap cleanup EXIT INT TERM hub() { uv run --project "$HUB_SRC" --no-sync sdlc-hub "$@"; } fail() { echo "e2e: ОШИБКА — $*" >&2; echo "--- лог хаба ---" >&2; tail -n 30 "$TMP/hub.log" >&2 || true; exit 1; } jget() { /usr/bin/python3 -c 'import json,sys; d=json.load(sys.stdin) for k in sys.argv[1].split("."): d = d[int(k)] if isinstance(d, list) else d[k] print(d)' "$1"; } # --- токены (создают базу во временном каталоге) TOKENS="$(hub tokens)" CLIENT_TOKEN="$(printf '%s\n' "$TOKENS" | sed -n 's/^клиентский токен: //p')" AGENT_TOKEN="$(printf '%s\n' "$TOKENS" | sed -n 's/^агент wiki: //p')" [ -n "$CLIENT_TOKEN" ] && [ -n "$AGENT_TOKEN" ] || fail "токены не выданы: $TOKENS" # --- фейковая Eva EVA_TOKEN="eva-e2e-secret" /usr/bin/python3 "$HERE/scripts/fake_eva_serve.py" "$EVA_TOKEN" >"$TMP/eva.out" 2>"$TMP/eva.err" & PIDS+=($!) for _ in $(seq 1 50); do grep -q '^PORT ' "$TMP/eva.out" 2>/dev/null && break; sleep 0.1; done EVA_PORT="$(sed -n 's/^PORT //p' "$TMP/eva.out")" [ -n "$EVA_PORT" ] || fail "фейковая Eva не поднялась: $(cat "$TMP/eva.err")" # --- хаб hub serve --host 127.0.0.1 --port "$PORT" >"$TMP/hub.log" 2>&1 & PIDS+=($!) HUB="http://127.0.0.1:$PORT" for _ in $(seq 1 100); do nc -z 127.0.0.1 "$PORT" 2>/dev/null && break kill -0 "${PIDS[1]}" 2>/dev/null || fail "хаб завершился при старте" sleep 0.1 done nc -z 127.0.0.1 "$PORT" 2>/dev/null || fail "хаб не открыл порт $PORT" api() { # api METHOD PATH [json] local m="$1" p="$2"; shift 2 if [ $# -gt 0 ]; then curl -fsS -X "$m" -H "Authorization: Bearer $CLIENT_TOKEN" -H 'Content-Type: application/json' -d "$1" "$HUB$p" else curl -fsS -X "$m" -H "Authorization: Bearer $CLIENT_TOKEN" "$HUB$p" fi } # --- конфиг агента (прямой HTTP к тестовому хабу, без самообновления) cat >"$TMP/agent.json" < пакет wiki_sync api POST /projects '{"label":"E2E","name":"Сквозной прогон","wiki_root":"DOC-1"}' >"$TMP/project.json" || fail "POST /projects" BATCH="$(jget batch_id <"$TMP/project.json")" agent run --once || fail "agent.py run --once (wiki_sync)" STATUS="$(api GET "/wiki/status?project=E2E")" [ "$(printf '%s' "$STATUS" | jget pages)" = "5" ] || fail "wiki/status: ожидалось 5 страниц: $STATUS" TREE="$(api GET "/wiki/tree?project=E2E")" [ "$(printf '%s' "$TREE" | /usr/bin/python3 -c 'import json,sys; print(len(json.load(sys.stdin)["pages"]))')" = "5" ] || fail "wiki/tree: $TREE" PAGE="$(api GET "/wiki/pages/DOC-2?project=E2E")" printf '%s' "$PAGE" | jget markdown | grep -q 'attachment:[0-9]' || fail "в Markdown DOC-2 нет ссылки attachment:: $PAGE" ATT_ID="$(printf '%s' "$PAGE" | jget attachments.0.id)" # --- вложение по запросу -> wiki_fetch api POST "/wiki/attachments/$ATT_ID/fetch" '{}' >/dev/null || fail "POST attachments/$ATT_ID/fetch" agent run --once || fail "agent.py run --once (wiki_fetch)" api GET "/wiki/attachments/$ATT_ID/content" >"$TMP/content.bin" || fail "GET attachments/$ATT_ID/content" EXPECT="$(/usr/bin/python3 -c 'import sys; sys.path.insert(0, sys.argv[1]); import fake_eva_serve as f; sys.stdout.buffer.write(f.PNG)' "$HERE/scripts" | shasum -a 256 | cut -d' ' -f1)" GOT="$(shasum -a 256 <"$TMP/content.bin" | cut -d' ' -f1)" [ "$EXPECT" = "$GOT" ] || fail "содержимое вложения не совпало" echo "e2e: ok"