#!/bin/bash # Integrationstest för helmd. Körs i en isolerad container (se # scripts/run-integration.sh) men fungerar var som helst där tmux, # curl och binären finns och där HOME får smutsas ner. set -eu BIN="${HELMD_BIN:-./build/helmd}" export HOME="${TEST_HOME:-$(mktemp -d)}" export HELMD_CONFIG="$HOME/helmd-config.json" API=http://127.0.0.1:8788 PASS=0; FAIL=0 say() { printf '\n== %s\n' "$*"; } ok() { PASS=$((PASS+1)); echo " OK: $*"; } fail() { FAIL=$((FAIL+1)); echo " FAIL: $*" >&2; } expect_contains() { # haystackfil, nål, beskrivning if grep -qF "$2" "$1"; then ok "$3"; else fail "$3 — hittade inte: $2"; cat "$1" | tail -20; fi } curl_api() { curl -sS -H "Authorization: Bearer $TOKEN" "$@"; } wait_for() { # beskrivning, timeout-sek, kommando... local desc="$1" t="$2"; shift 2 for _ in $(seq 1 $((t*2))); do if "$@" >/dev/null 2>&1; then ok "$desc"; return 0; fi sleep 0.5 done fail "$desc (timeout ${t}s)"; return 1 } say "startar helmd" TOKEN=$("$BIN" token) "$BIN" serve --listen 127.0.0.1:8788 & HELMD_PID=$! trap 'kill $HELMD_PID 2>/dev/null || true; tmux kill-server 2>/dev/null || true' EXIT wait_for "servern svarar på /api/health" 10 \ curl -fsS -H "Authorization: Bearer $TOKEN" $API/api/health say "auth" CODE=$(curl -s -o /dev/null -w '%{http_code}' $API/api/sessions) [ "$CODE" = 401 ] && ok "utan token => 401" || fail "utan token gav $CODE" CODE=$(curl -s -o /dev/null -w '%{http_code}' "$API/api/sessions?token=$TOKEN") [ "$CODE" = 200 ] && ok "query-token => 200" || fail "query-token gav $CODE" say "webbklient (inbäddad)" curl -s $API/ > /tmp/ui.html expect_contains /tmp/ui.html 'agent-helm' "index.html serveras utan auth" curl -s $API/app.js | grep -q 'ansiToHtml' && ok "app.js serveras" || fail "app.js" CODE=$(curl -s -o /dev/null -w '%{http_code}' $API/style.css) [ "$CODE" = 200 ] && ok "style.css => 200" || fail "style.css gav $CODE" say "stäng av ntfy för testet (PUT /api/config)" curl_api -X PUT -d '{"ntfy":{"server":"https://ntfy.brasse-pc.eu","topic":"agent-helm","allowed_topics":["agent-helm","claude"],"enabled":false,"notify_on_question":false}}' \ $API/api/config > /tmp/cfg.json expect_contains /tmp/cfg.json '"saved":true' "config sparad" curl_api $API/api/config | grep -q '"enabled":false' && ok "ntfy avstängd" || fail "ntfy fortfarande på" say "session: skapa med mock-agent" MOCK="$(cd "$(dirname "$0")/.." && pwd)/testdata/mock-agent.sh" curl_api -X POST -d "{\"name\":\"prov\",\"cmd\":\"bash $MOCK\",\"cwd\":\"/tmp\"}" \ $API/api/sessions > /tmp/sess.json expect_contains /tmp/sess.json '"name":"prov"' "session skapad" say "fråga 1: onumrerad trust-fråga (pilnavigering)" q_has_two_options() { curl_api $API/api/sessions/prov/question | grep -q '"No, exit"'; } wait_for "trust-frågan detekterad" 10 q_has_two_options curl_api $API/api/sessions/prov/question > /tmp/q1.json expect_contains /tmp/q1.json '"selected":0' "markören står på första valet" curl_api -X POST -d '{"option":2}' $API/api/sessions/prov/answer > /tmp/a1.json expect_contains /tmp/a1.json '"answered":true' "svar skickat" screen_has() { curl_api "$API/api/sessions/prov/screen" | grep -qF "$1"; } wait_for "mocken fick svaret (ned+enter => val 1)" 10 screen_has "TRUST_ANSWER=1" say "prompt" curl_api -X POST -d '{"text":"hej från testet"}' $API/api/sessions/prov/prompt > /dev/null wait_for "prompten nådde agenten" 10 screen_has "ECHO: hej från testet" say "fråga 2: numrerad godkännandefråga (sifferval)" curl_api -X POST -d '{"text":"ask-numbered"}' $API/api/sessions/prov/prompt > /dev/null q2_detected() { curl_api $API/api/sessions/prov/question | grep -q '"numbered":true'; } wait_for "numrerad fråga detekterad" 10 q2_detected curl_api -X POST -d '{"option":3}' $API/api/sessions/prov/answer > /dev/null wait_for "mocken fick siffran 3" 10 screen_has "PICKED=3" say "raw keys + mode" curl_api -X POST -d '{"keys":["Enter"]}' $API/api/sessions/prov/keys | grep -q '"sent":true' \ && ok "keys-endpoint" || fail "keys-endpoint" curl_api -X POST -d '{"action":"cycle"}' $API/api/sessions/prov/mode | grep -q '"sent":true' \ && ok "mode cycle (BTab)" || fail "mode cycle" say "SSE-events" (curl_api -N --max-time 4 "$API/api/events" > /tmp/events.txt 2>/dev/null || true) & SSE_PID=$! sleep 0.5 curl_api -X POST -d '{"text":"sse-test"}' $API/api/sessions/prov/prompt > /dev/null wait $SSE_PID || true expect_contains /tmp/events.txt 'event: hello' "SSE hello" expect_contains /tmp/events.txt 'event: screen' "SSE screen-event vid ändring" say "fildelning" printf '

rapport

' > /tmp/rapport.html "$BIN" share --note "testrapport" --session prov /tmp/rapport.html > /tmp/share.out expect_contains /tmp/share.out "delad: rapport.html" "helmd share-CLI" curl_api $API/api/shares > /tmp/shares.json expect_contains /tmp/shares.json '"note":"testrapport"' "delningen listas" SHARE_ID=$(grep -o '"id":"[a-f0-9]*"' /tmp/shares.json | head -1 | cut -d'"' -f4) curl_api "$API/api/shares/$SHARE_ID/raw" > /tmp/share.raw expect_contains /tmp/share.raw '

rapport

' "raw-innehåll serveras" printf 'x' > /tmp/otillaten.sh CODE=$(curl_api -s -o /dev/null -w '%{http_code}' -F file=@/tmp/otillaten.sh $API/api/shares) [ "$CODE" = 400 ] && ok "otillåten filtyp => 400" || fail "otillåten filtyp gav $CODE" curl_api -X DELETE "$API/api/shares/$SHARE_ID" | grep -q '"deleted":true' \ && ok "delning raderad" || fail "radering" say "notify-regler" CODE=$(curl_api -s -o /dev/null -w '%{http_code}' -X POST -d '{"topic":"hemligt","message":"x"}' $API/api/notify) [ "$CODE" = 403 ] && ok "otillåten topic => 403" || fail "otillåten topic gav $CODE" curl_api -X POST -d '{"topic":"claude","message":"tyst (ntfy disabled)"}' $API/api/notify \ | grep -q '"published":true' && ok "tillåten topic => ok (ntfy av => no-op)" || fail "tillåten topic" say "session: avsluta + döda" curl_api -X POST -d '{"text":"exit"}' $API/api/sessions/prov/prompt > /dev/null sess_dead() { curl_api $API/api/sessions/prov | grep -q '"alive":false'; } wait_for "exit upptäcks (alive=false)" 10 sess_dead curl_api -X DELETE $API/api/sessions/prov | grep -q '"killed":true' \ && ok "session dödad" || fail "kill" echo echo "================================" echo "PASS: $PASS FAIL: $FAIL" [ "$FAIL" = 0 ] && echo "ALLA TESTER GRÖNA" || exit 1