#!/usr/bin/env bash # provision-agent-user_test.sh — tests bash para provision-agent-user.sh. # # Mockea la Synapse admin API + /v3/login con un mini servidor python. # # Casos: # T1. Provision exitoso mode=user → exit 0, archivos generados # T2. Provision exitoso mode=sudo → exit 0, plantilla sudo aplicada # T3. Idempotencia: re-run sobre agente existente → exit 0 + "Already provisioned" # T4. agent-id invalido (no match regex) → exit 1 # T5. mode invalido (no user/sudo) → exit 1 # T6. Falta MATRIX_ADMIN_TOKEN → exit 1 # T7. Permisos .env = 0600 # T8. config.yaml contiene tags correctos (user/sudo) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROV="$SCRIPT_DIR/provision-agent-user.sh" [[ -x "$PROV" ]] || { echo "FAIL: $PROV not executable"; exit 1; } # ── isolated test workspace ──────────────────────────────────────────────── TEST_DIR="$(mktemp -d -t fn_prov_test_XXXXXX)" trap 'rm -rf "$TEST_DIR"; kill_mock || true' EXIT cd "$TEST_DIR" # Lay out a minimal repo tree the script needs (REPO_ROOT cd'd by _common.sh). mkdir -p dev-scripts/agent/templates/prompts agents cp -r "$SCRIPT_DIR/templates/." dev-scripts/agent/templates/ cp "$SCRIPT_DIR/../_common.sh" dev-scripts/_common.sh cp "$PROV" dev-scripts/agent/provision-agent-user.sh chmod +x dev-scripts/agent/provision-agent-user.sh PROV_LOCAL="$TEST_DIR/dev-scripts/agent/provision-agent-user.sh" # Mock REPO_ROOT redirection: _common.sh uses BASH_SOURCE to find root; copying # the layout above ensures REPO_ROOT === $TEST_DIR/. # ── mock Synapse admin API + /v3/login ──────────────────────────────────── MOCK_PORT="${FN_PROV_TEST_PORT:-19981}" MOCK_LOG="$TEST_DIR/mock.log" start_mock() { python3 -c " import http.server, json, sys class H(http.server.BaseHTTPRequestHandler): def _read(self): n = int(self.headers.get('Content-Length','0') or 0) return self.rfile.read(n) if n else b'' def do_PUT(self): body = self._read() self.send_response(201) self.send_header('Content-Type','application/json') self.end_headers() self.wfile.write(b'{}') def do_POST(self): body = self._read() self.send_response(200) self.send_header('Content-Type','application/json') self.end_headers() self.wfile.write(json.dumps({ 'access_token':'syt_FAKETOKEN_'+self.path.replace('/','_'), 'device_id':'TESTDEVICE01', 'user_id':'@test:matrix.local' }).encode()) def log_message(self, fmt, *args): sys.stderr.write(fmt % args + '\n') http.server.HTTPServer(('127.0.0.1', $MOCK_PORT), H).serve_forever() " >"$MOCK_LOG" 2>&1 & MOCK_PID=$! echo "$MOCK_PID" > "$TEST_DIR/.mock.pid" # wait for port for _ in $(seq 1 50); do if curl -sS -o /dev/null "http://127.0.0.1:$MOCK_PORT/" 2>/dev/null; then return 0; fi sleep 0.1 done echo "FAIL: mock did not come up" >&2 return 1 } kill_mock() { [[ -f "$TEST_DIR/.mock.pid" ]] || return 0 local pid; pid=$(cat "$TEST_DIR/.mock.pid") kill "$pid" 2>/dev/null || true } start_mock # Env shared by all tests (FN_PROV_TEST=1 skips load_env) export FN_PROV_TEST=1 export MATRIX_HOMESERVER="http://127.0.0.1:$MOCK_PORT" export MATRIX_SERVER_NAME="matrix.local" export MATRIX_ADMIN_TOKEN="syt_FAKE_ADMIN" export OPERATOR_MATRIX_ID="@operator:matrix.local" PASS=0 FAIL=0 declare -a FAILED_TESTS t_pass() { echo " ✓ $1"; PASS=$((PASS+1)); } t_fail() { echo " ✗ $1"; FAIL=$((FAIL+1)); FAILED_TESTS+=("$1"); } # ── T1: provision exitoso mode=user ──────────────────────────────────────── echo "T1: provision exitoso mode=user" : > .env chmod 0600 .env "$PROV_LOCAL" agent-home-wsl home-wsl user >/tmp/t1.out 2>&1 \ && t_pass "exit 0" \ || { cat /tmp/t1.out; t_fail "T1 exit nonzero"; } [[ -f agents/agent-home-wsl/config.yaml ]] && t_pass "T1 config.yaml exists" || t_fail "T1 config.yaml missing" [[ -f agents/agent-home-wsl/agent.go ]] && t_pass "T1 agent.go exists" || t_fail "T1 agent.go missing" [[ -f agents/agent-home-wsl/prompts/system.md ]] && t_pass "T1 system.md exists" || t_fail "T1 system.md missing" [[ -d agents/agent-home-wsl/data ]] && t_pass "T1 data/ exists" || t_fail "T1 data/ missing" # T8: mode=user tag present in config grep -q "tags: \[agent, llm, devicemesh, home-wsl, user\]" agents/agent-home-wsl/config.yaml \ && t_pass "T1 config tags include 'user'" \ || t_fail "T1 config tags wrong: $(grep '^ tags:' agents/agent-home-wsl/config.yaml || echo MISSING)" # T7: .env permission 0600 ENV_PERM=$(stat -c %a .env 2>/dev/null || stat -f %A .env 2>/dev/null) [[ "$ENV_PERM" == "600" ]] && t_pass "T7 .env perm 0600" || t_fail "T7 .env perm = $ENV_PERM (expected 600)" # Vars present in .env grep -q "^MATRIX_TOKEN_AGENT_HOME_WSL=" .env && t_pass "T1 MATRIX_TOKEN_AGENT_HOME_WSL in .env" || t_fail "T1 token missing in .env" grep -q "^PICKLE_KEY_AGENT_HOME_WSL=" .env && t_pass "T1 PICKLE_KEY_AGENT_HOME_WSL in .env" || t_fail "T1 pickle missing in .env" grep -q "^MATRIX_DEVICE_ID_AGENT_HOME_WSL=" .env && t_pass "T1 MATRIX_DEVICE_ID in .env" || t_fail "T1 device id missing in .env" grep -q "^AGENT_HOME_WSL_DEVICE_MESH_URL=" .env && t_pass "T1 DEVICE_MESH_URL in .env" || t_fail "T1 device mesh url missing in .env" # ── T3: idempotencia (re-run sobre el mismo agente) ──────────────────────── echo "T3: idempotencia (re-run sobre agente existente)" OUT2=$("$PROV_LOCAL" agent-home-wsl home-wsl user 2>&1) RC=$? if [[ $RC -eq 0 ]] && echo "$OUT2" | grep -q "Already provisioned"; then t_pass "T3 idempotent re-run" else echo "$OUT2" t_fail "T3 idempotent re-run (rc=$RC)" fi # ── T2: provision exitoso mode=sudo ──────────────────────────────────────── echo "T2: provision exitoso mode=sudo" "$PROV_LOCAL" agent-home-wsl-sudo home-wsl sudo >/tmp/t2.out 2>&1 \ && t_pass "T2 exit 0" \ || { cat /tmp/t2.out; t_fail "T2 exit nonzero"; } [[ -f agents/agent-home-wsl-sudo/config.yaml ]] && t_pass "T2 config.yaml exists" || t_fail "T2 config.yaml missing" grep -q "tags: \[agent, llm, devicemesh, home-wsl, sudo\]" agents/agent-home-wsl-sudo/config.yaml \ && t_pass "T2 config tags include 'sudo'" \ || t_fail "T2 config tags wrong" grep -q "requires_approval: true" agents/agent-home-wsl-sudo/config.yaml \ && t_pass "T2 requires_approval: true" \ || t_fail "T2 requires_approval not set" # system prompt sudo has formal/strict copy grep -q "🔒" agents/agent-home-wsl-sudo/prompts/system.md \ && t_pass "T2 sudo prompt has 🔒 prefix" \ || t_fail "T2 sudo prompt missing 🔒 marker" # ── T4: agent-id invalido ────────────────────────────────────────────────── echo "T4: agent-id invalido" if "$PROV_LOCAL" "BadAgent" home-wsl user >/tmp/t4.out 2>&1; then t_fail "T4 should have failed but didn't" else if grep -q "invalid" /tmp/t4.out; then t_pass "T4 rejected invalid agent-id" else cat /tmp/t4.out t_fail "T4 rejected without 'invalid' message" fi fi # ── T5: mode invalido ────────────────────────────────────────────────────── echo "T5: mode invalido" if "$PROV_LOCAL" agent-test test bogus >/tmp/t5.out 2>&1; then t_fail "T5 should have failed but didn't" else grep -q "mode" /tmp/t5.out && t_pass "T5 rejected invalid mode" || { cat /tmp/t5.out; t_fail "T5 wrong error"; } fi # ── T6: falta MATRIX_ADMIN_TOKEN ─────────────────────────────────────────── echo "T6: falta MATRIX_ADMIN_TOKEN" ( unset MATRIX_ADMIN_TOKEN if "$PROV_LOCAL" agent-test-2 test user >/tmp/t6.out 2>&1; then exit 99 else grep -q "MATRIX_ADMIN_TOKEN" /tmp/t6.out && exit 0 || exit 1 fi ) RC=$? case "$RC" in 0) t_pass "T6 rejected when MATRIX_ADMIN_TOKEN missing" ;; 99) t_fail "T6 should have failed but didn't" ;; *) cat /tmp/t6.out; t_fail "T6 rejected without correct message" ;; esac # ── summary ──────────────────────────────────────────────────────────────── echo "" echo "── results ─────────────────────────────────────────────────" echo " pass: $PASS" echo " fail: $FAIL" if (( FAIL > 0 )); then echo " failed tests:" for t in "${FAILED_TESTS[@]}"; do echo " - $t"; done exit 1 fi echo " All tests passed." exit 0