name: celld (live + e2e-celld)
on:
workflow_call:
env:
CARGO_TERM_COLOR: always
CELLD_HTTP_PORT: "18880"
CELLD_URL: http://127.0.0.1:18880
NATS_PORT: "14222"
NATS_URL: nats://127.0.0.1:14222
DISTRIBUTED_INTERNAL_SECRET: test-only-internal-secret-change-me-2026
AZURE_STORAGE_USE_EMULATOR: "true"
AZURE_STORAGE_ACCOUNT_NAME: devstoreaccount1
AZURE_STORAGE_ACCOUNT_KEY: Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
jobs:
e2e-celld:
name: e2e-celld workspace tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: tests/e2e-celld
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: tests/e2e-celld -> target
shared-key: e2e-celld-workspace
- name: Run e2e-celld workspace tests
run: cargo test --workspace --verbose
live:
name: celld live (Azurite + worker + NATS)
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
toolchain: stable
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
. -> target
tests/celld/worker -> tests/celld/worker/target
shared-key: celld-live
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
js/package-lock.json
tests/e2e-ui/package-lock.json
tests/e2e-ui/ui/package-lock.json
- name: Install host tools
run: sudo apt-get update && sudo apt-get install -y jq openssl curl
- name: Install esbuild
run: npm install -g esbuild
- name: Install wasm-pack and worker-build
run: |
cargo install wasm-pack --locked || cargo install wasm-pack
cargo install worker-build --locked || cargo install worker-build
- name: Install celld CLI
run: |
curl -fsSL https://celld.dev/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Bring up Postgres + Zitadel and bootstrap OIDC
run: make -C tests/e2e-ui up
- name: Bring up Azurite + celld + NATS
run: |
command -v celld
command -v worker-build
make -C tests/e2e-ui up-celld-nats
- name: Live celld HTTP + NATS profile tests
run: make -C tests/e2e-ui test-celld
- name: Build e2e-celld API and UI
run: |
cargo build --manifest-path tests/e2e-celld/Cargo.toml \
-p e2e-celld-runner --bin e2e-celld
make -C tests/e2e-ui ui-install
npm install --prefix tests/e2e-ui
- name: Start e2e-celld API + UI
run: |
set -euo pipefail
set -a
# shellcheck disable=SC1091
. tests/e2e-ui/e2e-ui.env
set +a
export BIND=127.0.0.1:8791
export CELLD_URL=http://127.0.0.1:18880
export NATS_URL=nats://127.0.0.1:14222
export AUTH_URL=http://localhost:5180
export AUTH_USE_SECURE_COOKIES=false
export AUTH_TRUST_HOST=true
tests/e2e-celld/target/debug/e2e-celld \
> tests/e2e-celld/.ci-runner.log 2>&1 &
echo $! > tests/e2e-celld/.ci-runner.pid
ok=0
for i in $(seq 1 120); do
code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
"http://127.0.0.1:8791/graphql" \
-H 'content-type: application/json' \
-d '{"query":"{ __typename }"}' 2>/dev/null || echo 000)
if [ "$code" = "200" ] || [ "$code" = "401" ]; then ok=1; break; fi
sleep 0.5
done
if [ "$ok" != "1" ]; then
echo "e2e-celld API failed to become ready"
tail -120 tests/e2e-celld/.ci-runner.log
exit 1
fi
cd tests/e2e-ui/ui
PUBLIC_E2E_PROFILE=celld-nats \
E2E_API_ORIGIN=http://127.0.0.1:8791 \
npm run dev -- --host localhost --port 5180 \
> ../.ci-celld-ui.log 2>&1 &
echo $! > ../.ci-celld-ui.pid
cd ../../..
ok=0
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' \
"http://localhost:5180/" 2>/dev/null || echo 000)
if [ "$code" = "200" ] || [ "$code" = "302" ] || [ "$code" = "303" ]; then
ok=1
break
fi
sleep 0.5
done
if [ "$ok" != "1" ]; then
echo "e2e-celld UI failed to become ready (last HTTP $code)"
tail -100 tests/e2e-ui/.ci-celld-ui.log
exit 1
fi
- name: Install Playwright + Chromium
working-directory: tests/e2e-ui
run: npx playwright install chromium --with-deps
- name: Red-team private HTTP boundaries
run: |
set -euo pipefail
assert_status() {
expected="$1"
shift
actual=$(curl -sS -o /tmp/celld-red-team-response -w '%{http_code}' "$@")
if [ "$actual" != "$expected" ]; then
echo "expected HTTP $expected, got $actual"
sed -n '1,40p' /tmp/celld-red-team-response
exit 1
fi
}
assert_status 401 -X POST \
http://127.0.0.1:8791/internal/outbox/drain \
-H 'content-type: application/json' \
-d '{"kind":"todo","id":"red-team"}'
assert_status 401 -X POST \
http://127.0.0.1:8791/internal/outbox/drain \
-H 'content-type: application/json' \
-H 'x-distributed-internal-secret: forged-red-team-secret-000000' \
-d '{"kind":"todo","id":"red-team"}'
assert_status 400 -X POST \
http://127.0.0.1:8791/internal/outbox/drain \
-H 'content-type: application/json' \
-H "x-distributed-internal-secret: $DISTRIBUTED_INTERNAL_SECRET" \
-d '{"kind":"todo","id":"red-team","outbox":[]}'
assert_status 401 -X POST \
http://127.0.0.1:8791/zitadel.scrape.v1 \
-H 'content-type: application/json' \
-d '{}'
assert_status 401 \
http://127.0.0.1:18880/todo/red-team
assert_status 401 -X POST \
http://127.0.0.1:18880/todo/red-team/outbox.complete \
-H 'content-type: application/json' \
-d '{"ids":["forged"]}'
- name: Todo + Chat browser lifecycle through celld
working-directory: tests/e2e-ui
run: >-
npx playwright test todos.user.spec.ts chat.user.spec.ts
--project chromium-user
env:
E2E_UI_ORIGIN: http://localhost:5180
E2E_API_ORIGIN: http://127.0.0.1:8791
CI: true
- name: Upload celld Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: celld-playwright-report
path: |
tests/e2e-ui/playwright-report
tests/e2e-ui/test-results
if-no-files-found: ignore
retention-days: 7
- name: Dump logs on failure
if: failure()
run: |
echo '=== celld compose ==='
docker compose -f tests/celld/docker-compose.yml ps -a || true
docker compose -f tests/celld/docker-compose.yml logs --tail=200 || true
echo '=== NATS profile compose ==='
docker compose -f tests/e2e-ui/celld-nats-profile/docker-compose.yml ps -a || true
docker compose -f tests/e2e-ui/celld-nats-profile/docker-compose.yml logs --tail=80 || true
echo '=== e2e-celld API ==='
tail -180 tests/e2e-celld/.ci-runner.log || true
echo '=== e2e-celld UI ==='
tail -100 tests/e2e-ui/.ci-celld-ui.log || true
echo '=== Postgres + Zitadel ==='
docker compose -f tests/e2e-ui/docker/docker-compose.yml ps -a || true
docker compose -f tests/e2e-ui/docker/docker-compose.yml logs --tail=100 || true
- name: Tear down
if: always()
run: |
[ -f tests/e2e-ui/.ci-celld-ui.pid ] && \
kill "$(cat tests/e2e-ui/.ci-celld-ui.pid)" 2>/dev/null || true
[ -f tests/e2e-celld/.ci-runner.pid ] && \
kill "$(cat tests/e2e-celld/.ci-runner.pid)" 2>/dev/null || true
lsof -ti:5180 2>/dev/null | xargs -r kill -9 2>/dev/null || true
lsof -ti:8791 2>/dev/null | xargs -r kill -9 2>/dev/null || true
make -C tests/e2e-ui down-celld-nats || true
make -C tests/e2e-ui down-celld || true
docker compose -f tests/e2e-ui/docker/docker-compose.yml down -v || true