eva-agent/scripts/e2e-hub.sh
Andrey Epifancev e38d21fff1 docs, e2e: хаб переименован в sdlc-hub (#5)
Пути ../sdlc-hub, sdlc-hub tokens / agent publish; scripts/e2e-hub.sh —
SDLC_HUB_HOME, CLI sdlc-hub, хаб по умолчанию из основного checkout
sdlc-hub workspace (worktree jira-hub/.wt/wiki больше нет).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 16:10:15 +04:00

111 lines
5.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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" <<JSON
{"hub_url": "$HUB", "hub_token": "$AGENT_TOKEN",
"eva": {"base_url": "http://127.0.0.1:$EVA_PORT", "token": "$EVA_TOKEN"},
"verify_ssl": true, "ca_bundle": null, "timeout": 30, "poll_seconds": 0, "auto_update": false}
JSON
agent() { /usr/bin/python3 "$HERE/src/agent.py" --config "$TMP/agent.json" --journal "$TMP/journal.jsonl" "$@"; }
agent check || fail "agent.py check"
# --- проект с корнем вики -> пакет 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:<id>: $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"