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
65 changes: 53 additions & 12 deletions .github/workflows/e2e-webapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ jobs:
name: "🧪 E2E Tests: Webapp"
runs-on: warp-ubuntu-latest-x64-16x
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2]
shardTotal: [2]
Comment thread
carderne marked this conversation as resolved.
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
SHARD_INDEX: ${{ matrix.shardIndex }}
SHARD_TOTAL: ${{ matrix.shardTotal }}
steps:
- name: 🔧 Disable IPv6
run: |
Expand Down Expand Up @@ -57,7 +64,7 @@ jobs:
version: 10.33.2

- name: ⎔ Setup node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: WarpBuilds/setup-node@bc639b444d583175926b588962199c247d23e8d3 # v6
with:
node-version: 24.18.0
cache: "pnpm"
Expand All @@ -73,18 +80,52 @@ jobs:
if: ${{ !env.DOCKERHUB_USERNAME }}
run: echo "DockerHub login skipped because secrets are not available."

- name: 🐳 Pre-pull testcontainer images
- name: 📥 Prepare deps and testcontainer images
run: |
echo "Pre-pulling Docker images with authenticated session..."
docker pull postgres:14
docker pull redis:7.2
docker pull testcontainers/ryuk:0.14.0
docker pull ghcr.io/s2-streamstore/s2:0.40.0@sha256:b26249e2ede0949755f5af8028185dc2bcfc3aa2db21eb9610543d144eb6ee9d
docker pull minio/minio:latest
echo "Image pre-pull complete"
# Pull images concurrently with dependency installation. Retry each pull because
# registry timeouts are a recurring transient CI flake.
pull() {
for attempt in 1 2 3; do
docker pull "$1" && return 0
echo "::warning::docker pull $1 failed (attempt ${attempt}/3); retrying in 10s"
sleep 10
done
echo "::error::docker pull $1 failed after 3 attempts"
return 1
}

- name: 📥 Download deps
run: pnpm install --frozen-lockfile
pull_images() {
local pids=()
local failed=0
for image in \
postgres:14 \
redis:7.2 \
testcontainers/ryuk:0.14.0 \
ghcr.io/s2-streamstore/s2:0.40.0@sha256:b26249e2ede0949755f5af8028185dc2bcfc3aa2db21eb9610543d144eb6ee9d \
minio/minio:latest
do
pull "$image" &
pids+=("$!")
done
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
failed=1
fi
done
return "$failed"
}

echo "Installing dependencies and pre-pulling Docker images..."
pull_images &
pull_pid=$!
install_status=0
pnpm install --frozen-lockfile || install_status=$?
pull_status=0
wait "$pull_pid" || pull_status=$?
if (( install_status != 0 || pull_status != 0 )); then
exit 1
fi
echo "Dependency install and image pre-pull complete"

- name: 📀 Generate Prisma Client
run: pnpm run generate
Expand All @@ -96,6 +137,6 @@ jobs:
run: cd apps/webapp && pnpm exec playwright install chromium

- name: 🧪 Run Webapp E2E Tests
run: cd apps/webapp && pnpm exec vitest run --config vitest.e2e.config.ts --reporter=default
run: cd apps/webapp && pnpm exec vitest run --config vitest.e2e.config.ts --reporter=default --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
env:
WEBAPP_TEST_VERBOSE: "1"
68 changes: 46 additions & 22 deletions .github/workflows/unit-tests-webapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ on:
jobs:
unitTests:
name: "🧪 Unit Tests: Webapp"
# 10 shards on 16x machines: webapp test throughput is limited per-machine (one
# docker daemon + disk absorbing all the per-file Postgres/ClickHouse container
# spin-up), so many machines beats few big ones - fewer/bigger (3x32) measured
# SLOWER than 10x8. The 16x (vs 8x) gives the fork pool the CPU headroom the 8x
# runners lacked. Setup overhead per machine is ~1 min on warm runners.
# Webapp test throughput is limited per-machine (one docker daemon + disk absorbing
# all the per-file Postgres/ClickHouse container spin-up), so many machines beats
# few big ones - fewer/bigger (3x32) measured slower than 10x8. The 16x (vs 8x)
# gives the fork pool the CPU headroom the 8x runners lacked.
runs-on: warp-ubuntu-latest-x64-16x
strategy:
# one flaky shard shouldn't cancel its siblings - lets us re-run only the failed shard
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
shardTotal: [12]
shardIndex:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
shardTotal: [24]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
SHARD_INDEX: ${{ matrix.shardIndex }}
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
version: 10.33.2

- name: ⎔ Setup node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: WarpBuilds/setup-node@bc639b444d583175926b588962199c247d23e8d3 # v6
with:
node-version: 24.18.0
cache: "pnpm"
Expand All @@ -85,9 +85,10 @@ jobs:
if: ${{ !env.DOCKERHUB_USERNAME }}
run: echo "DockerHub login skipped because secrets are not available."

- name: 🐳 Pre-pull testcontainer images
- name: 📥 Prepare deps and testcontainer images
run: |
# Retry each pull - DockerHub registry timeouts are a recurring transient CI flake.
# Pull images concurrently with dependency installation. Retry each pull because
# DockerHub registry timeouts are a recurring transient CI flake.
pull() {
for attempt in 1 2 3; do
docker pull "$1" && return 0
Expand All @@ -97,18 +98,41 @@ jobs:
echo "::error::docker pull $1 failed after 3 attempts"
return 1
}
echo "Pre-pulling Docker images with authenticated session..."
pull postgres:14
pull postgres:17
pull clickhouse/clickhouse-server:26.2.19.43-alpine@sha256:c6ad6a7eb2fb5999df3adfb8b69a0c7222c68fa9b8f6b04a088564ebbc959251
pull redis:7.2
pull testcontainers/ryuk:0.14.0
pull electricsql/electric:1.2.4@sha256:20da3d0b0e74926c5623392db67fd56698b9e374c4aeb6cb5cadeb8fea171c36
pull minio/minio:latest
echo "Image pre-pull complete"

- name: 📥 Download deps
run: pnpm install --frozen-lockfile

pull_images() {
local pids=()
local failed=0
for image in \
postgres:14 \
postgres:17 \
clickhouse/clickhouse-server:26.2.19.43-alpine@sha256:c6ad6a7eb2fb5999df3adfb8b69a0c7222c68fa9b8f6b04a088564ebbc959251 \
redis:7.2 \
testcontainers/ryuk:0.14.0 \
electricsql/electric:1.2.4@sha256:20da3d0b0e74926c5623392db67fd56698b9e374c4aeb6cb5cadeb8fea171c36 \
minio/minio:latest
do
pull "$image" &
pids+=("$!")
done
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
failed=1
fi
done
return "$failed"
}

echo "Installing dependencies and pre-pulling Docker images..."
pull_images &
pull_pid=$!
install_status=0
pnpm install --frozen-lockfile || install_status=$?
pull_status=0
wait "$pull_pid" || pull_status=$?
if (( install_status != 0 || pull_status != 0 )); then
exit 1
fi
echo "Dependency install and image pre-pull complete"

- name: 📀 Generate Prisma Client
run: pnpm run generate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function buildService(engine: any, prisma: any) {

describe("RunEngineTriggerTaskService null-byte sanitization", () => {
containerTest(
"strips a NUL from idempotencyKeyOptions.key so the jsonb insert does not 22P05",
"sanitizes NUL-containing idempotency and debounce keys before the jsonb insert",
async ({ prisma, redisOptions }) => {
const engine = buildEngine(prisma, redisOptions);

Expand All @@ -59,49 +59,22 @@ describe("RunEngineTriggerTaskService null-byte sanitization", () => {
const service = buildService(engine, prisma);

const result = await service.call({
taskId: "nul-idem-task",
taskId: "nul-keys-task",
environment,
body: {
payload: { kind: "idem" },
payload: { kind: "nul-keys" },
options: {
idempotencyKey: "a".repeat(64),
idempotencyKeyOptions: { key: `acme${NUL}inc`, scope: "run" },
},
},
});
assertNonNullable(result);

const row = await prisma.taskRun.findUniqueOrThrow({ where: { id: result.run.id } });
expect(row.idempotencyKeyOptions).toEqual({ key: "acmeinc", scope: "run" });
} finally {
await engine.quit();
}
}
);

containerTest(
"strips a NUL from debounce.key so the jsonb insert does not 22P05",
async ({ prisma, redisOptions }) => {
const engine = buildEngine(prisma, redisOptions);

try {
const environment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const service = buildService(engine, prisma);

const result = await service.call({
taskId: "nul-debounce-task",
environment,
body: {
payload: { kind: "debounce" },
options: {
debounce: { key: `grp${NUL}1`, delay: "1s" },
},
},
});
assertNonNullable(result);

const row = await prisma.taskRun.findUniqueOrThrow({ where: { id: result.run.id } });
expect((row.debounce as { key: string }).key).toBe("grp1");
expect(row.idempotencyKeyOptions).toEqual({ key: "acmeinc", scope: "run" });
expect(row.debounce).toMatchObject({ key: "grp1", delay: "1s" });
} finally {
await engine.quit();
}
Expand Down
Loading