Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/e2e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# AGFS E2E Gate

Every code or docs change that affects user-visible behavior must name its end-to-end coverage in the PR body.

## Required PR Checklist

Each PR must include an **E2E coverage** line with one of these forms:

- `Covered by scripts/e2e/run-core-e2e.sh` plus any focused e2e command added by the PR.
- `Covered by <specific e2e test/command>` when the core harness is not the relevant path.
- `No true e2e possible because <reason>; closest user-path verification: <command>` for narrow internal-only changes. This should be rare and cross-review may reject it when a realistic user path exists.

Unit tests are still required for detailed behavior, but they do not replace e2e coverage when the change has a user-visible flow.

## Core Harness

Run from the repository root:

```bash
scripts/e2e/run-core-e2e.sh
```

The harness starts a local `agfs-server` on `127.0.0.1:18080`, waits for `/api/v1/health`, checks `/api/v1/ready`, exercises a QueueFS enqueue/dequeue through the HTTP file API, runs a real local `agfs-shell` pipeline while the server is live, and builds the webapp from the committed lockfile.

Useful environment overrides:

- `AGFS_E2E_PORT=18081` changes the local server port.
- `AGFS_E2E_BASE_URL=http://host:port` changes the URL clients use.
- `AGFS_E2E_CONFIG=path/to/config.yaml` changes the server config path relative to `agfs-server`.
- `AGFS_E2E_SKIP_WEBAPP=1` skips the npm/webapp build smoke when Node/npm is unavailable. Do not use this in CI unless a separate webapp e2e job covers the same path.
- `AGFS_E2E_LOG_FILE=/tmp/agfs-e2e.log` changes the server log path.

## Ownership Lanes

- Backend/server e2e cases live under task #25.
- Shell/SDK/pipeline e2e cases live under task #26.
- Docs/webapp/first-run e2e cases live under task #27.

Cross-review should block a patch when a realistic user path exists but the author only provided unit tests.
119 changes: 119 additions & 0 deletions scripts/e2e/run-core-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
SERVER_DIR="$ROOT_DIR/agfs-server"
SHELL_DIR="$ROOT_DIR/agfs-shell"
WEBAPP_DIR="$SHELL_DIR/webapp"

HOST=${AGFS_E2E_HOST:-127.0.0.1}
PORT=${AGFS_E2E_PORT:-18080}
BASE_URL=${AGFS_E2E_BASE_URL:-http://$HOST:$PORT}
CONFIG=${AGFS_E2E_CONFIG:-config.example.yaml}
LOG_FILE=${AGFS_E2E_LOG_FILE:-$ROOT_DIR/.e2e-agfs-server.log}

SERVER_PID=""
cleanup() {
if [[ -n "$SERVER_PID" ]]; then
kill "$SERVER_PID" >/dev/null 2>&1 || true
wait "$SERVER_PID" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT

wait_for_http() {
local url=$1
local attempts=${2:-80}
for _ in $(seq 1 "$attempts"); do
if curl -fsS "$url" >/dev/null 2>&1; then
return 0
fi
sleep 0.25
done
return 1
}

start_server() {
echo "[e2e] starting agfs-server at $BASE_URL"
(
cd "$SERVER_DIR"
go run cmd/server/main.go -c "$CONFIG" -addr ":$PORT"
) >"$LOG_FILE" 2>&1 &
SERVER_PID=$!

if ! wait_for_http "$BASE_URL/api/v1/health"; then
echo "[e2e] server did not become healthy; log follows" >&2
sed -n '1,200p' "$LOG_FILE" >&2 || true
exit 1
fi
}

assert_contains() {
local haystack=$1
local needle=$2
if [[ "$haystack" != *"$needle"* ]]; then
echo "[e2e] expected output to contain: $needle" >&2
echo "[e2e] actual output: $haystack" >&2
exit 1
fi
}

server_smoke() {
echo "[e2e] server health/readiness/file API smoke"
local health ready_code body path
health=$(curl -fsS "$BASE_URL/api/v1/health")
assert_contains "$health" '"status"'
assert_contains "$health" '"ready"'

ready_code=$(curl -sS -o /tmp/agfs-e2e-ready.json -w '%{http_code}' "$BASE_URL/api/v1/ready")
if [[ "$ready_code" != "200" && "$ready_code" != "503" ]]; then
echo "[e2e] /ready returned unexpected status: $ready_code" >&2
cat /tmp/agfs-e2e-ready.json >&2 || true
exit 1
fi

path="/queuefs/e2e-$(date +%s%N)"
body="hello from e2e"
curl -fsS -X PUT --data-binary "$body" "$BASE_URL/api/v1/files?path=$path/enqueue" >/dev/null
local popped
popped=$(curl -fsS "$BASE_URL/api/v1/files?path=$path/dequeue")
assert_contains "$popped" "$body"
}

shell_smoke() {
echo "[e2e] agfs-shell command smoke"
(
cd "$SHELL_DIR"
uv sync --frozen >/dev/null
AGFS_API_URL="$BASE_URL" uv run agfs-shell -c "echo shell-e2e | wc -l" | grep -q "1"
)
}

webapp_smoke() {
if [[ "${AGFS_E2E_SKIP_WEBAPP:-0}" == "1" ]]; then
echo "[e2e] skipping webapp smoke because AGFS_E2E_SKIP_WEBAPP=1"
return 0
fi
if ! command -v npm >/dev/null 2>&1; then
echo "[e2e] npm not found; set AGFS_E2E_SKIP_WEBAPP=1 to skip webapp smoke intentionally" >&2
exit 1
fi

echo "[e2e] webapp lockfile/build smoke"
(
cd "$WEBAPP_DIR"
npm ci >/dev/null
npm run build >/dev/null
test -f dist/index.html
)
}

main() {
start_server
server_smoke
shell_smoke
webapp_smoke
echo "[e2e] core e2e passed"
}

main "$@"