Пути ../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>
111 lines
5.6 KiB
Bash
Executable File
111 lines
5.6 KiB
Bash
Executable File
#!/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"
|