Skip to content

fix: return AlreadyExists gRPC code for duplicate account creation (#… #3195

fix: return AlreadyExists gRPC code for duplicate account creation (#…

fix: return AlreadyExists gRPC code for duplicate account creation (#… #3195

Workflow file for this run

name: E2E Tests
on:
workflow_dispatch:
push:
branches: [develop, main]
paths:
- 'frontend/**'
- 'services/**'
- '.github/workflows/e2e.yml'
pull_request:
branches: [develop, main]
paths:
- 'frontend/**'
- 'services/**'
- '.github/workflows/e2e.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# ===========================================================================
# Build: compile backend binary and frontend assets once, share via artifacts
# ===========================================================================
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: '1.26.1'
cache: true
- name: Set up buf
uses: bufbuild/buf-action@v1
with:
setup_only: true
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Generate protobuf files
run: buf generate
- name: Build proto FileDescriptorSet
run: buf build api/proto -o cmd/meridian/descriptor.binpb
- name: Build meridian binary
run: CGO_ENABLED=0 go build -o ./dist/meridian ./cmd/meridian/
- name: Build seed-dev binary
run: CGO_ENABLED=0 go build -o ./dist/seed-dev ./cmd/seed-dev/
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Generate frontend protobuf files
working-directory: frontend
run: npm run generate
- name: Build frontend
working-directory: frontend
env:
VITE_E2E_MODE: 'true'
VITE_API_BASE_URL: 'http://localhost:5173'
run: npx vite build
- name: Upload backend binaries
uses: actions/upload-artifact@v7
with:
name: meridian-binary
path: |
dist/meridian
dist/seed-dev
retention-days: 1
- name: Upload frontend build
uses: actions/upload-artifact@v7
with:
name: frontend-dist
path: frontend/dist/
retention-days: 1
# ===========================================================================
# E2E Shards: run Playwright tests in parallel across 4 runners
# ===========================================================================
e2e:
name: 'E2E Shard ${{ matrix.shardIndex }}/4'
runs-on: ubuntu-latest
timeout-minutes: 25
needs: build
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Download backend binary
uses: actions/download-artifact@v8
with:
name: meridian-binary
path: dist/
- name: Make binaries executable
run: chmod +x dist/meridian dist/seed-dev
- name: Download frontend build
uses: actions/download-artifact@v8
with:
name: frontend-dist
path: frontend/dist/
- name: Start CockroachDB
run: |
docker run -d \
--name cockroachdb \
-p 26257:26257 \
cockroachdb/cockroach:latest-v24.1 \
start-single-node --insecure
# Wait for CockroachDB to be ready (up to 60s)
for i in $(seq 1 30); do
if docker exec cockroachdb cockroach sql --insecure -e 'SELECT 1' >/dev/null 2>&1; then
echo "CockroachDB is ready"
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: CockroachDB did not become ready after 60s"
exit 1
fi
sleep 2
done
- name: Run database migrations
run: |
./dist/meridian \
--migrate \
--database-url "postgres://root@localhost:26257/defaultdb?sslmode=disable"
- name: Stage tenant migration files for provisioner
run: |
# The schema provisioner reads per-service migrations from
# MIGRATIONS_BASE_PATH/<service>/*.sql. In the Docker image this is
# /migrations/<service>, but the E2E runner runs the binary directly
# from dist/. Stage a flat layout under /tmp/provisioner-migrations
# so the provisioner can apply each service's migrations to the
# org_<tenant> schema when InitiateTenant fires.
mkdir -p /tmp/provisioner-migrations
for SVC in party current-account position-keeping financial-accounting payment-order market-information reference-data internal-account reconciliation identity control-plane; do
if [ -d "services/${SVC}/migrations" ]; then
cp -r "services/${SVC}/migrations" "/tmp/provisioner-migrations/${SVC}"
fi
done
- name: Start meridian backend
run: |
DATABASE_URL="postgres://root@localhost:26257/defaultdb?sslmode=disable" \
LOCAL_DEV_MODE=true \
AUTH_ENABLED=false \
SAGA_ASSET_DIR="$(pwd)" \
SCHEMA_PROVISIONING_ENABLED=true \
MIGRATIONS_BASE_PATH=/tmp/provisioner-migrations \
./dist/meridian >/tmp/meridian.log 2>&1 &
- name: Seed dev tenant and apply manifest
run: |
./dist/seed-dev \
--gateway-url=http://localhost:8090 \
--grpc-addr=localhost:50051 \
--manifest=examples/manifests/energy.json \
--tenant-id=dev_tenant \
--tenant-slug=dev-tenant
- name: Create additional test tenants
run: |
GATEWAY_URL="http://localhost:8090"
create_tenant() {
local TENANT_ID=$1
local PAYLOAD=$2
HTTP_CODE=$(curl -sS -o /tmp/tenant_resp.txt -w "%{http_code}" \
-X POST "${GATEWAY_URL}/v1/tenants" \
-H "Content-Type: application/json" \
-H "X-Tenant-Slug: dev-tenant" \
-d "$PAYLOAD")
echo "${TENANT_ID}: HTTP ${HTTP_CODE}"
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "409" ]; then
echo "ERROR: Unexpected response for ${TENANT_ID}:"
cat /tmp/tenant_resp.txt
exit 1
fi
}
create_tenant "acme_corp" '{"tenantId":"acme_corp","displayName":"ACME Corporation","settlementAsset":"GBP","slug":"acme-corp"}'
create_tenant "energy_co" '{"tenantId":"energy_co","displayName":"Energy Co Ltd","settlementAsset":"KWH","slug":"energy-co"}'
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install Playwright browsers
working-directory: frontend
run: npx playwright install --with-deps chromium
- name: Run Playwright tests (shard ${{ matrix.shardIndex }}/4)
working-directory: frontend
env:
CI: 'true'
run: npx playwright test --shard=${{ matrix.shardIndex }}/4 --reporter=blob
- name: Upload blob report
uses: actions/upload-artifact@v7
if: always()
with:
name: blob-report-${{ matrix.shardIndex }}
path: frontend/blob-report/
retention-days: 1
- name: Upload Playwright traces
uses: actions/upload-artifact@v7
if: failure()
with:
name: playwright-traces-${{ matrix.shardIndex }}
path: frontend/test-results/
retention-days: 30
# ===========================================================================
# Merge: combine shard reports into a single HTML report
# ===========================================================================
merge-reports:
name: Merge E2E Reports
runs-on: ubuntu-latest
needs: e2e
if: ${{ always() && needs.e2e.result != 'skipped' }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Download blob reports
uses: actions/download-artifact@v8
with:
pattern: blob-report-*
path: frontend/all-blob-reports
merge-multiple: true
- name: Merge reports
working-directory: frontend
run: npx playwright merge-reports --reporter=html all-blob-reports
- name: Upload merged report
uses: actions/upload-artifact@v7
if: always()
with:
name: playwright-report
path: frontend/playwright-report/
retention-days: 30