mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes This change adds cleanup steps to GitHub Actions workflows that create temporary files or sensitive build resources. Changes include: - Removing temporary backend helper files, logs, and PID files. - Cleaning up database migration temporary directories after failure logs are uploaded. - Removing locally generated deployment files and Storybook archives. - Deleting temporary signing certificates, MSI extraction directories, and Apple signing keychains. - Ensuring cleanup runs even when earlier workflow steps fail. The cleanup reduces temporary data retention on runners and ensures sensitive signing material is removed after builds. No functional application code was changed. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
403 lines
19 KiB
YAML
403 lines
19 KiB
YAML
name: Enterprise E2E (Playwright)
|
|
|
|
# Enterprise Playwright suite — exercises premium-key gated features (audit,
|
|
# teams, analytics) plus full OAuth + SAML logins via the Keycloak compose
|
|
# stacks under testing/compose. Slow and secret-gated, so it runs in four
|
|
# situations:
|
|
#
|
|
# - PRs that touch proprietary / premium / SSO compose / enterprise tests
|
|
# (driven by build.yml via workflow_call, path-filtered against
|
|
# .github/config/.files.yaml `proprietary`),
|
|
# - every push to main (post-merge safety net),
|
|
# - on a nightly cron schedule (catches Keycloak image drift, license
|
|
# expiry, upstream proprietary changes),
|
|
# - manual workflow_dispatch.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
use_shared_cache:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
push:
|
|
branches: ["main"]
|
|
schedule:
|
|
- cron: "0 4 * * *"
|
|
workflow_dispatch:
|
|
|
|
# No `concurrency:` block here on purpose. When this workflow is called via
|
|
# workflow_call from build.yml, ${{ github.workflow }}/event_name/pr_number
|
|
# resolve to the *caller's* values, producing the same group key as build.yml
|
|
# and causing the workflow_call instantiation to self-cancel — the job
|
|
# silently fails to spawn while `${{ needs.X.result }}` still reports
|
|
# `failure`. Standalone runs (push-to-main, nightly cron, dispatch) don't
|
|
# overlap often enough to need explicit concurrency control.
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
pick:
|
|
uses: ./.github/workflows/_runner-pick.yml
|
|
|
|
playwright-e2e-enterprise:
|
|
environment:
|
|
name: ci-unsigned
|
|
deployment: false
|
|
needs: pick
|
|
# Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE,
|
|
# so the suite can't boot premium and would fail. See the header comment.
|
|
# GitHub reports the skipped reusable workflow as success.
|
|
if: needs.pick.outputs.is_fork != 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
env:
|
|
PREMIUM_KEY: ${{ secrets.PREMIUM_KEY_ENTERPRISE }}
|
|
PREMIUM_ENABLED: "true"
|
|
SYSTEM_ENABLEANALYTICS: "false"
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Restore cache Gradle User Home
|
|
if: inputs.use_shared_cache
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
|
|
|
|
- name: Restore cache Gradle
|
|
if: inputs.use_shared_cache == false
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-playwright-e2e-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
|
|
|
|
- name: Set up JDK 25
|
|
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
java-version: "25"
|
|
distribution: "temurin"
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- name: Install Task
|
|
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
|
|
- name: Install Playwright (chromium only)
|
|
run: task e2e:install -- chromium
|
|
- name: Build frontend (needed for playwright's vite preview webServer)
|
|
# Enterprise tests target :8080 (Spring Boot's bundled frontend), but
|
|
# playwright's webServer config still launches `vite preview --port 5173`
|
|
# before any test run, which needs dist/ to exist. VITE_BUILD_FOR_PREVIEW
|
|
# forces absolute asset paths so vite preview can serve deep SPA routes.
|
|
env:
|
|
VITE_BUILD_FOR_PREVIEW: "1"
|
|
run: task frontend:build
|
|
|
|
- name: Resolve kubernetes.docker.internal to localhost
|
|
# The compose stacks set KC_HOSTNAME=kubernetes.docker.internal so
|
|
# Keycloak issues redirect URIs against that host. Docker Desktop
|
|
# auto-resolves it; GHA runners don't. Map it to 127.0.0.1 so the
|
|
# browser-driven OAuth flow lands back on Stirling-PDF correctly.
|
|
run: |
|
|
echo "127.0.0.1 kubernetes.docker.internal" | sudo tee -a /etc/hosts
|
|
|
|
# Helper function used by all phases — boots `:stirling-pdf:bootRun`
|
|
# with the React frontend baked in (-PbuildWithFrontend=true) so the
|
|
# SPA serves on :8080 and OAuth/SAML callbacks land on the same host
|
|
# that the browser is interacting with.
|
|
- name: Define helpers
|
|
run: |
|
|
{
|
|
echo 'wait_for_backend() {'
|
|
echo ' start=$SECONDS'
|
|
echo ' for i in $(seq 1 300); do'
|
|
echo ' if curl -fsS http://localhost:8080/api/v1/info/status >/dev/null 2>&1; then'
|
|
echo ' echo "Backend up after $((SECONDS - start))s"; return 0'
|
|
echo ' fi; sleep 2'
|
|
echo ' done'
|
|
echo ' tail -200 /tmp/backend.log || true; return 1'
|
|
echo '}'
|
|
echo 'stop_backend() {'
|
|
echo ' if [ -f /tmp/backend.pid ]; then'
|
|
echo ' kill "$(cat /tmp/backend.pid)" 2>/dev/null || true'
|
|
echo ' rm -f /tmp/backend.pid'
|
|
echo ' fi'
|
|
echo ' pkill -f "gradlew :stirling-pdf:bootRun" 2>/dev/null || true'
|
|
echo ' for i in $(seq 1 30); do'
|
|
echo ' curl -fsS http://localhost:8080/api/v1/info/status >/dev/null 2>&1 || return 0'
|
|
echo ' sleep 1'
|
|
echo ' done'
|
|
echo '}'
|
|
} > /tmp/helpers.sh
|
|
chmod +x /tmp/helpers.sh
|
|
|
|
# ───────── OAuth round-trip ─────────
|
|
- name: Bring up Keycloak (OAuth realm)
|
|
working-directory: testing/compose
|
|
run: docker compose -f docker-compose-keycloak-oauth.yml up -d --no-deps keycloak-oauth-db keycloak-oauth
|
|
- name: Wait for Keycloak (OAuth) ready
|
|
working-directory: testing/compose
|
|
run: |
|
|
for i in $(seq 1 60); do
|
|
bash validate-oauth-test.sh 2>/dev/null && exit 0 || true
|
|
# validate script also pings stirling on :8080 — accept just the
|
|
# keycloak realm as our gate here, stirling boots in the next step
|
|
curl -fsS http://localhost:9080/realms/stirling-oauth >/dev/null 2>&1 && exit 0
|
|
sleep 5
|
|
done
|
|
docker compose -f docker-compose-keycloak-oauth.yml logs --tail=200 keycloak-oauth
|
|
exit 1
|
|
- name: Boot Stirling-PDF (frontend baked in, OAuth env)
|
|
env:
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
SECURITY_OAUTH2_ENABLED: "true"
|
|
SECURITY_OAUTH2_AUTOCREATEUSER: "true"
|
|
# Keycloak issues redirect URIs against KC_HOSTNAME, which the
|
|
# compose default sets to kubernetes.docker.internal. Match here
|
|
# (resolves to localhost via /etc/hosts mapping above).
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_ISSUER: "http://kubernetes.docker.internal:9080/realms/stirling-oauth"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_CLIENTID: "stirling-pdf-client"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_CLIENTSECRET: "test-client-secret-change-in-production"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_USEASUSERNAME: "email"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_SCOPES: "openid,profile,email"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise OAuth Playwright tests
|
|
id: oauth-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-oauth.json
|
|
run: task e2e:enterprise -- --grep "OAuth"
|
|
- name: Stop backend + tear down OAuth Keycloak
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
(cd testing/compose && docker compose -f docker-compose-keycloak-oauth.yml down -v)
|
|
|
|
# ───────── SAML round-trip ─────────
|
|
- name: Bring up Keycloak (SAML realm)
|
|
working-directory: testing/compose
|
|
run: docker compose -f docker-compose-keycloak-saml.yml up -d --no-deps keycloak-saml-db keycloak-saml
|
|
- name: Wait for Keycloak (SAML) ready
|
|
working-directory: testing/compose
|
|
run: |
|
|
for i in $(seq 1 60); do
|
|
curl -fsS http://localhost:9080/realms/stirling-saml >/dev/null 2>&1 && exit 0
|
|
sleep 5
|
|
done
|
|
docker compose -f docker-compose-keycloak-saml.yml logs --tail=200 keycloak-saml
|
|
exit 1
|
|
- name: Generate SAML SP certs + fetch Keycloak IdP cert
|
|
# The .pem/.crt/.key files are gitignored (test-only certs); the
|
|
# docker-based start-saml-test.sh generates them at runtime, so do
|
|
# the same in CI before bootRun reads them.
|
|
working-directory: testing/compose
|
|
run: |
|
|
openssl req -x509 -newkey rsa:2048 \
|
|
-keyout saml-private-key.key \
|
|
-out saml-public-cert.crt \
|
|
-days 3650 -nodes \
|
|
-subj "/CN=stirling-pdf-saml-sp" >/dev/null 2>&1
|
|
# Fetch Keycloak's SAML signing cert from the realm descriptor
|
|
CERT_BODY=$(curl -sf http://localhost:9080/realms/stirling-saml/protocol/saml/descriptor \
|
|
| awk 'BEGIN{RS="<[^>]*X509Certificate>|</[^>]*X509Certificate>"} NR==2{gsub(/[[:space:]]+/,""); print; exit}')
|
|
{
|
|
echo "-----BEGIN CERTIFICATE-----"
|
|
echo "$CERT_BODY"
|
|
echo "-----END CERTIFICATE-----"
|
|
} > keycloak-saml-cert.pem
|
|
test -s saml-private-key.key
|
|
test -s saml-public-cert.crt
|
|
test -s keycloak-saml-cert.pem
|
|
echo "✓ SAML certs prepared"
|
|
- name: Boot Stirling-PDF (frontend baked in, SAML env)
|
|
env:
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
SECURITY_SAML2_ENABLED: "true"
|
|
SECURITY_SAML2_AUTOCREATEUSER: "true"
|
|
SECURITY_SAML2_PROVIDER: "keycloak"
|
|
SECURITY_SAML2_REGISTRATIONID: "keycloak"
|
|
SECURITY_SAML2_IDP_ISSUER: "http://localhost:9080/realms/stirling-saml"
|
|
SECURITY_SAML2_IDP_ENTITYID: "http://localhost:9080/realms/stirling-saml"
|
|
SECURITY_SAML2_IDP_METADATAURI: "http://localhost:9080/realms/stirling-saml/protocol/saml/descriptor"
|
|
SECURITY_SAML2_IDPSINGLELOGINURL: "http://localhost:9080/realms/stirling-saml/protocol/saml"
|
|
SECURITY_SAML2_IDPSINGLELOGOUTURL: "http://localhost:9080/realms/stirling-saml/protocol/saml"
|
|
SECURITY_SAML2_IDP_CERT: "${{ github.workspace }}/testing/compose/keycloak-saml-cert.pem"
|
|
SECURITY_SAML2_PRIVATEKEY: "${{ github.workspace }}/testing/compose/saml-private-key.key"
|
|
SECURITY_SAML2_SP_CERT: "${{ github.workspace }}/testing/compose/saml-public-cert.crt"
|
|
# Realm registers the SP entity as the metadata URL — see
|
|
# keycloak-realm-saml.json `clientId`. Match it here so Keycloak
|
|
# accepts the AuthnRequest issuer.
|
|
SECURITY_SAML2_SP_ENTITYID: "http://localhost:8080/saml2/service-provider-metadata/keycloak"
|
|
SECURITY_SAML2_SP_ACS: "http://localhost:8080/login/saml2/sso/keycloak"
|
|
SECURITY_SAML2_SP_SLS: "http://localhost:8080/logout/saml2/slo"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise SAML Playwright tests
|
|
id: saml-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-saml.json
|
|
run: task e2e:enterprise -- --grep "SAML"
|
|
- name: Stop backend + tear down SAML Keycloak
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
(cd testing/compose && docker compose -f docker-compose-keycloak-saml.yml down -v)
|
|
|
|
# ───────── License-gated feature tests (no IdP needed) ─────────
|
|
- name: Wipe DB so InitialSecuritySetup re-runs with admin/adminadmin
|
|
# Earlier phases (OAuth, SAML) create the default admin/stirling user.
|
|
# InitialSecuritySetup only honours SECURITY_INITIALLOGIN_* when the
|
|
# admin user doesn't already exist, so the persisted DB has to be
|
|
# cleared between phases for the feature env vars to take effect.
|
|
run: |
|
|
rm -f app/core/configs/stirling-pdf-DB*.mv.db
|
|
rm -rf app/core/configs/backup
|
|
- name: Boot Stirling-PDF (frontend baked in, premium only)
|
|
env:
|
|
SECURITY_INITIALLOGIN_USERNAME: admin
|
|
SECURITY_INITIALLOGIN_PASSWORD: adminadmin
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise feature Playwright tests
|
|
id: feature-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-feature.json
|
|
run: task e2e:enterprise -- --grep "Enterprise license"
|
|
- name: Print backend log on failure
|
|
if: failure()
|
|
run: |
|
|
echo "::group::Enterprise backend log"
|
|
tail -500 /tmp/backend.log || true
|
|
echo "::endgroup::"
|
|
- name: Stop backend (final)
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
- name: Flag flaky tests
|
|
# Runs regardless of the test outcomes: a flaky test (passed on retry)
|
|
# leaves its step green, so this is the only place it surfaces. Merges
|
|
# all three phase reports (some may be absent if an earlier phase hard-
|
|
# failed and skipped the rest). Emits ::warning:: annotations + a job
|
|
# summary; never fails the job.
|
|
if: always()
|
|
working-directory: frontend
|
|
run: >
|
|
npx tsx editor/scripts/report-flaky-tests.mts
|
|
"${{ github.workspace }}/frontend/playwright-report/results-oauth.json"
|
|
"${{ github.workspace }}/frontend/playwright-report/results-saml.json"
|
|
"${{ github.workspace }}/frontend/playwright-report/results-feature.json"
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: playwright-report-enterprise-${{ github.run_id }}
|
|
path: frontend/playwright-report/
|
|
retention-days: 7
|
|
|
|
- name: Cleanup temporary files
|
|
if: always()
|
|
run: |
|
|
rm -f /tmp/helpers.sh /tmp/backend.log /tmp/backend.pid
|
|
continue-on-error: true
|
|
|
|
# Multi-node regression: builds + seeds the clustered stack (testing/compose/docker-compose-multinode.yml)
|
|
# and runs behave features/multinode. Licence-gated, so it runs after the Playwright job (not in parallel).
|
|
multinode-e2e:
|
|
environment:
|
|
name: ci-unsigned
|
|
deployment: false
|
|
needs: [pick, playwright-e2e-enterprise]
|
|
# Nightly cron + manual dispatch only (heavy build), fork-gated for the licence secret.
|
|
if: >-
|
|
always() && needs.pick.outputs.is_fork != 'true'
|
|
&& (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
|
runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }}
|
|
timeout-minutes: 60
|
|
env:
|
|
PREMIUM_KEY: ${{ secrets.PREMIUM_KEY_ENTERPRISE }}
|
|
PREMIUM_ENABLED: "true"
|
|
SYSTEM_ENABLEANALYTICS: "false"
|
|
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
|
MN_COMPOSE: docker-compose-multinode.yml
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: |
|
|
engine/pyproject.toml
|
|
engine/uv.lock
|
|
- name: Install behave test deps
|
|
run: |
|
|
uv sync --project engine --locked --group cucumber
|
|
- name: Build the multi-node image
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" build
|
|
- name: Bring up the cluster and wait for both nodes healthy
|
|
working-directory: testing/compose
|
|
run: |
|
|
docker compose -f "$MN_COMPOSE" up -d
|
|
for i in $(seq 1 90); do
|
|
h1=$(docker inspect -f '{{.State.Health.Status}}' multinode-stirling-1 2>/dev/null || echo starting)
|
|
h2=$(docker inspect -f '{{.State.Health.Status}}' multinode-stirling-2 2>/dev/null || echo starting)
|
|
if [ "$h1" = healthy ] && [ "$h2" = healthy ]; then echo "both nodes healthy"; exit 0; fi
|
|
sleep 5
|
|
done
|
|
echo "::error::nodes did not become healthy"
|
|
docker compose -f "$MN_COMPOSE" logs --tail=200 stirling-1 stirling-2
|
|
exit 1
|
|
- name: Seed the cluster (teams, users, S3 connection, policy)
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" --profile seed run --rm seed
|
|
- name: Run multi-node regression (implemented guarantees)
|
|
working-directory: testing/cucumber
|
|
# -e overrides behave.ini's exclusion of features/multinode; ~@known_gap skips any tracked-gap scenarios.
|
|
run: uv run --project ../../engine --locked --group cucumber python -m behave features/multinode -e "features/enterprise" --tags="~@known_gap ~@destructive" --no-capture -f plain
|
|
- name: Run multi-node failover (destructive)
|
|
working-directory: testing/cucumber
|
|
run: uv run --project ../../engine --locked --group cucumber python -m behave features/multinode -e "features/enterprise" --tags="@destructive ~@known_gap" --no-capture -f plain
|
|
- name: Dump node logs on failure
|
|
if: failure()
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" logs --tail=400 stirling-1 stirling-2
|
|
- name: Tear down
|
|
if: always()
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" --profile seed down -v --remove-orphans
|