LinkAclTest: invalidate resources_acl_bo's static cache after every s… #833
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: EGroupware Testing | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - master | |
| workflow_dispatch: | |
| jobs: | |
| phpunit: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| env: | |
| EGW_DB_ROOT_PW: ciroot | |
| COMPOSE_PROJECT_NAME: egwci | |
| EGW_CI_ROOT: ${{ github.workspace }}/sources | |
| EGW_CI_DOCKER_DIR: ${{ github.workspace }}/sources/doc/docker | |
| EGW_SRC_DIR: /var/www | |
| EGW_CONFIG_USER: "" | |
| EGW_CONFIG_PASSWD: "" | |
| SYSOP_USER: "" | |
| SYSOP_PASS: "" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| path: sources | |
| - name: Cache Composer downloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.composer/cache | |
| key: composer-cache-${{ runner.os }}-${{ hashFiles('sources/composer.lock') }} | |
| restore-keys: | | |
| composer-cache-${{ runner.os }}- | |
| - name: Install PHP dependencies in sources | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| COMPOSER_CACHE_DIR="${HOME}/.composer/cache" | |
| docker run --rm --user "$(id -u):$(id -g)" \ | |
| -v "${COMPOSER_CACHE_DIR}":/tmp/composer-cache \ | |
| -v "${GITHUB_WORKSPACE}/sources":/app -w /app \ | |
| -e COMPOSER_CACHE_DIR=/tmp/composer-cache \ | |
| composer install --no-interaction --ignore-platform-reqs | |
| php "${GITHUB_WORKSPACE}/sources/install-cli.php" --git-apps pull | |
| - name: Prepare compose project layout | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$GITHUB_WORKSPACE/sources/doc/docker/data" "$GITHUB_WORKSPACE/sources/doc/docker/sessions" | |
| rm -f "$GITHUB_WORKSPACE/sources/doc/docker/data/header.inc.php" | |
| - name: Start CI containers | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| $COMPOSE up -d --force-recreate db egroupware nginx | |
| - name: Wait for install | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| MAX_WAIT=60 | |
| WAIT_INTERVAL=5 | |
| ELAPSED=0 | |
| SUCCESS_MSG="EGroupware successful installed" | |
| echo "Waiting for database to be ready..." | |
| until $COMPOSE exec -T db mariadb-admin ping -h "db" --silent --user=root --password="$EGW_DB_ROOT_PW"; do | |
| if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then | |
| echo "::error::Timed out waiting for EGroupware installation" | |
| echo "==== egroupware logs ====" | |
| $COMPOSE logs egroupware || true | |
| echo "==== database logs ====" | |
| $COMPOSE logs db || true | |
| exit 1 | |
| fi | |
| echo " ... waiting for DB" | |
| sleep "$WAIT_INTERVAL" | |
| ELAPSED=$((ELAPSED + WAIT_INTERVAL)) | |
| done | |
| echo "Waiting for EGroupware installation log message..." | |
| INSTALL_LOG="$(mktemp)" | |
| until $COMPOSE logs egroupware > "$INSTALL_LOG" && grep -q "$SUCCESS_MSG" "$INSTALL_LOG"; do | |
| if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then | |
| echo "::error::Timed out waiting for EGroupware installation" | |
| echo "==== egroupware logs ====" | |
| $COMPOSE logs egroupware || true | |
| echo "==== database logs ====" | |
| $COMPOSE logs db || true | |
| exit 1 | |
| fi | |
| sleep "$WAIT_INTERVAL" | |
| ELAPSED=$((ELAPSED + WAIT_INTERVAL)) | |
| echo " ... still waiting (${ELAPSED}s)" | |
| done | |
| $COMPOSE exec -T egroupware ln -sf /var/lib/egroupware/header.inc.php "$EGW_SRC_DIR/header.inc.php" | |
| - name: Extract install credentials | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| INSTALL_LOG="$(mktemp)" | |
| $COMPOSE logs egroupware > "$INSTALL_LOG" | |
| # Get sysop admin user from install log | |
| SYSOP_USER="sysop" | |
| SYSOP_PASS="$( | |
| awk ' | |
| /EGroupware username: sysop/ {found=1; next} | |
| found && /password:/ { | |
| sub(/^[^:]*:[[:space:]]*/, "", $0) | |
| exit | |
| } | |
| ' "$INSTALL_LOG" | |
| )" | |
| # Get setup user/password from install log. | |
| CONFIG_USER="$( | |
| awk ' | |
| /Setup username:/ {sub(/.*Setup username:[[:space:]]*/, "", $0); print; exit} | |
| ' "$INSTALL_LOG" | |
| )" | |
| CONFIG_PASS="$( | |
| awk ' | |
| /Setup username:/ {found=1; next} | |
| found && /password:/ { | |
| sub(/^[^:]*:[[:space:]]*/, "", $0) | |
| exit | |
| } | |
| ' "$INSTALL_LOG" | |
| )" | |
| for v in SYSOP_USER SYSOP_PASS CONFIG_USER CONFIG_PASS; do | |
| if [ -z "${!v}" ]; then | |
| echo "::error::Missing $v" | |
| exit 1 | |
| fi | |
| done | |
| { | |
| echo "SYSOP_USER=$SYSOP_USER" | |
| echo "SYSOP_PASS=$SYSOP_PASS" | |
| echo "EGW_CONFIG_USER=$CONFIG_USER" | |
| echo "EGW_CONFIG_PASSWD=$CONFIG_PASS" | |
| } >> "$GITHUB_ENV" | |
| - name: Debug admin-cli path mapping | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| echo "== Host checks ==" | |
| pwd | |
| ls -la "$GITHUB_WORKSPACE/sources/admin/admin-cli.php" || true | |
| ls -la "$GITHUB_WORKSPACE/sources/admin" | sed -n '1,80p' || true | |
| echo "== Compose resolved mounts (egroupware service) ==" | |
| docker compose --project-directory "$GITHUB_WORKSPACE/sources" \ | |
| -f "$GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml" \ | |
| -f "$GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" \ | |
| config | sed -n '/egroupware:/,/db:/p' | |
| echo "== Container checks ==" | |
| $COMPOSE exec -T egroupware sh -lc 'pwd; ls -la /var/www | sed -n "1,80p"; ls -la /var/www/admin | sed -n "1,80p" || true; ls -la /var/www/egroupware/admin | sed -n "1,80p" || true' | |
| $COMPOSE exec -T egroupware sh -lc 'test -f /var/www/admin/admin-cli.php && echo "admin-cli in /var/www" || true; test -f /var/www/egroupware/admin/admin-cli.php && echo "admin-cli in /var/www/egroupware" || true' | |
| - name: Detect app root in container | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| APP_DIR="$( | |
| $COMPOSE exec -T egroupware sh -lc ' | |
| if [ -f /var/www/admin/admin-cli.php ]; then | |
| echo /var/www | |
| elif [ -f /var/www/egroupware/admin/admin-cli.php ]; then | |
| echo /var/www/egroupware | |
| else | |
| exit 1 | |
| fi | |
| ' | |
| )" | |
| echo "EGW_SRC_DIR=$APP_DIR" >> "$GITHUB_ENV" | |
| echo "Detected EGW_SRC_DIR=$APP_DIR" | |
| - name: Configure for phpunit test (apps, user) | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| # Get test user from phpunit.xml | |
| EGW_TEST_USER="$( | |
| sed -n 's/.*<var name="EGW_USER" value="\([^"]*\)".*/\1/p' "$GITHUB_WORKSPACE/sources/doc/phpunit.xml" | |
| )" | |
| EGW_TEST_PASS="$( | |
| sed -n 's/.*<var name="EGW_PASSWORD" value="\([^"]*\)".*/\1/p' "$GITHUB_WORKSPACE/sources/doc/phpunit.xml" | |
| )" | |
| for v in SYSOP_USER SYSOP_PASS EGW_TEST_USER EGW_TEST_PASS; do | |
| if [ -z "${!v}" ]; then | |
| echo "::error::Missing $v" | |
| exit 1 | |
| fi | |
| done | |
| { | |
| echo "SYSOP_USER=$SYSOP_USER" | |
| echo "SYSOP_PASS=$SYSOP_PASS" | |
| } >> "$GITHUB_ENV" | |
| $COMPOSE exec -T -w "$EGW_SRC_DIR" egroupware php admin/admin-cli.php \ | |
| --edit-user "$SYSOP_USER,$SYSOP_PASS,$EGW_TEST_USER,Test,User,$EGW_TEST_PASS,$EGW_TEST_USER@example.invalid" | |
| $COMPOSE exec -T -w "$EGW_SRC_DIR" egroupware php admin/admin-cli.php \ | |
| --allow-app "$SYSOP_USER,$SYSOP_PASS,$EGW_TEST_USER,projectmanager" | |
| # Ensure share links resolve locally in CI | |
| $COMPOSE exec -T -w "$EGW_SRC_DIR" egroupware php setup/setup-cli.php \ | |
| --config default,"$EGW_CONFIG_USER","$EGW_CONFIG_PASSWD",hostname=localhost,webserver_url=/egroupware | |
| - name: Debug web access | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| docker compose exec nginx nginx -T || true | |
| docker compose exec nginx ss -lntp || true | |
| $COMPOSE exec -T egroupware sh -lc ' | |
| which curl || apt-get update && apt-get install -y curl | |
| echo "Testing 'nginx' URL:" | |
| curl -I -v http://nginx/ || true | |
| echo | |
| echo "Listening ports:" | |
| ss -lntp || netstat -lntp || true | |
| ' | |
| - name: Install fixture test app | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| # Ensure fixture test app is linked and installed before tests | |
| $COMPOSE exec -T \ | |
| -e EGW_CONFIG_USER="$EGW_CONFIG_USER" \ | |
| -e EGW_CONFIG_PASSWD="$EGW_CONFIG_PASSWD" \ | |
| -w "$EGW_SRC_DIR" egroupware sh -lc ' | |
| if [ -d api/tests/fixtures/apps/test ]; then | |
| ln -sfn api/tests/fixtures/apps/test test | |
| set +e | |
| php setup/setup-cli.php --update default,$EGW_CONFIG_USER,$EGW_CONFIG_PASSWD,no,test | |
| rc=$? | |
| set -e | |
| echo "setup-cli exit code: $rc" | |
| fi | |
| ' | |
| if ! $COMPOSE exec -T db mariadb -u root -p"$EGW_DB_ROOT_PW" -D "egroupware" -e "SHOW TABLES LIKE 'egw_test%';" | grep -q "egw_test"; then | |
| echo "egw_test table not found after test app install, some tests will be skipped" | |
| fi | |
| - name: Install webauthn app | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| # webauthn is a newer first-party app (composer.json require egroupware/webauthn); the CI | |
| # image's baked-in install predates it, so its schema (egw_webauthn_pubkeys) was never | |
| # created - install it explicitly, same as the "test" fixture app above. | |
| # | |
| # header.inc.php still points EGW_SERVER_ROOT at the image's baked-in /usr/share/egroupware | |
| # at this point in the pipeline (that only gets patched to $EGW_SRC_DIR later, in "Run | |
| # PHPUnit") - without this, setup-cli.php scans the old baked-in app list, never sees | |
| # webauthn as new, and silently no-ops with "No update necessary". Patch it here too | |
| # (idempotent, harmless if "Run PHPUnit" repeats it) so the scan sees the real checkout. | |
| $COMPOSE exec -T egroupware sed -i "s#/usr/share/egroupware#$EGW_SRC_DIR#g" /var/lib/egroupware/header.inc.php | |
| $COMPOSE exec -T \ | |
| -e EGW_CONFIG_USER="$EGW_CONFIG_USER" \ | |
| -e EGW_CONFIG_PASSWD="$EGW_CONFIG_PASSWD" \ | |
| -w "$EGW_SRC_DIR" egroupware sh -lc ' | |
| set +e | |
| php setup/setup-cli.php --update default,$EGW_CONFIG_USER,$EGW_CONFIG_PASSWD,no,webauthn | |
| rc=$? | |
| set -e | |
| echo "setup-cli exit code: $rc" | |
| ' | |
| if ! $COMPOSE exec -T db mariadb -u root -p"$EGW_DB_ROOT_PW" -D "egroupware" -e "SHOW TABLES LIKE 'egw_webauthn_pubkeys';" | grep -q "egw_webauthn_pubkeys"; then | |
| echo "::error::egw_webauthn_pubkeys table not found after webauthn app install" | |
| exit 1 | |
| fi | |
| - name: Disable swoolepush for CI tests | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| $COMPOSE exec -T db mariadb -u root -p"$EGW_DB_ROOT_PW" -D "egroupware" -e \ | |
| "UPDATE egw_applications SET app_enabled=-1 WHERE app_name='swoolepush';" | |
| $COMPOSE exec -T -w "$EGW_SRC_DIR" egroupware php -r ' | |
| require "api/src/loader/common.php"; | |
| \EGroupware\Api\Cache::unsetInstance(\EGroupware\Api\Egw\Applications::class, "apps"); | |
| \EGroupware\Api\Cache::unsetInstance(\EGroupware\Api\Hooks::class, "locations"); | |
| ' | |
| - name: Run PHPUnit | |
| timeout-minutes: 30 | |
| run: | | |
| set -euo pipefail | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| EGW_CONFIG_USER="${EGW_CONFIG_USER:-}" | |
| EGW_CONFIG_PASSWD="${EGW_CONFIG_PASSWD:-}" | |
| for v in EGW_CONFIG_USER EGW_CONFIG_PASSWD; do | |
| if [ -z "${!v}" ]; then | |
| echo "::error::Missing $v" | |
| exit 1 | |
| fi | |
| done | |
| # header.inc.php is generated against the image's own baked-in /usr/share/egroupware | |
| # install (rsynced from the image at container start), but the "Run PHPUnit" step below | |
| # serves the actual checked-out branch from $EGW_SRC_DIR instead - patch the mismatched | |
| # EGW_SERVER_ROOT so the app-specific autoloader (api/src/autoload.php, which resolves | |
| # classes via EGW_INCLUDE_ROOT rather than composer's own correctly-resolved base dir) | |
| # looks in the right place. Done inside the container (not via the host bind-mount path) | |
| # since the file is owned by the container's own user, not the Actions runner. | |
| $COMPOSE exec -T egroupware \ | |
| sed -i "s#/usr/share/egroupware#$EGW_SRC_DIR#g" /var/lib/egroupware/header.inc.php | |
| # Run tests with local HTTP endpoint inside egroupware container | |
| $COMPOSE exec -T \ | |
| -e HTTP_HOST=127.0.0.1:8081 \ | |
| -e HTTP_X_FORWARDED_PROTO=http \ | |
| -e EGW_URL=http://127.0.0.1:8081 \ | |
| -e EGW_CONFIG_USER="$EGW_CONFIG_USER" \ | |
| -e EGW_CONFIG_PASSWD="$EGW_CONFIG_PASSWD" \ | |
| -w "$EGW_SRC_DIR" egroupware \ | |
| bash -lc ' | |
| set -euo pipefail | |
| php -S 127.0.0.1:8081 -t "$PWD" "$PWD/.github/ci-router.php" > /tmp/egw-phpunit-http.log 2>&1 & | |
| server_pid=$! | |
| trap "kill $server_pid 2>/dev/null || true" EXIT | |
| for i in $(seq 1 20); do | |
| php -r '\''$fp=@fsockopen("127.0.0.1",8081,$errno,$errstr,0.2); if($fp){fclose($fp); exit(0);} exit(1);'\'' && break | |
| sleep 1 | |
| done | |
| set +e | |
| php vendor/bin/phpunit --configuration doc/phpunit.xml | |
| rc=$? | |
| set -e | |
| echo "=== php built-in server log (last 300 lines) ===" | |
| tail -n 300 /tmp/egw-phpunit-http.log || true | |
| exit $rc | |
| ' | |
| - name: Tear down | |
| if: always() | |
| run: | | |
| COMPOSE="docker compose --project-directory $GITHUB_WORKSPACE/sources \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.yml \ | |
| -f $GITHUB_WORKSPACE/sources/doc/docker/docker-compose.ci.yml" | |
| $COMPOSE down -v --remove-orphans | |
| jstest: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| env: | |
| EGW_DB_ROOT_PW: ciroot | |
| COMPOSE_PROJECT_NAME: egwci | |
| EGW_CI_ROOT: ${{ github.workspace }}/sources | |
| EGW_CI_DOCKER_DIR: ${{ github.workspace }}/sources/doc/docker | |
| EGW_SRC_DIR: /var/www | |
| EGW_CONFIG_USER: "" | |
| EGW_CONFIG_PASSWD: "" | |
| SYSOP_USER: "" | |
| SYSOP_PASS: "" | |
| defaults: | |
| run: | |
| shell: bash | |
| working-directory: sources | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| path: sources | |
| - name: Cache Composer downloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.composer/cache | |
| key: composer-cache-${{ runner.os }}-${{ hashFiles('sources/composer.lock') }} | |
| restore-keys: | | |
| composer-cache-${{ runner.os }}- | |
| - name: Install PHP dependencies in sources | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| COMPOSER_CACHE_DIR="${HOME}/.composer/cache" | |
| docker run --rm --user "$(id -u):$(id -g)" \ | |
| -v "${COMPOSER_CACHE_DIR}":/tmp/composer-cache \ | |
| -v "${GITHUB_WORKSPACE}/sources":/app -w /app \ | |
| -e COMPOSER_CACHE_DIR=/tmp/composer-cache \ | |
| composer install --no-interaction --ignore-platform-reqs | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: sources/package-lock.json | |
| - name: Install npm dependencies | |
| run: npm ci | |
| - name: Install Playwright system dependencies | |
| run: npx playwright install-deps chromium firefox | |
| - name: Install Playwright Chromium | |
| timeout-minutes: 10 | |
| env: | |
| DEBUG: pw:install | |
| run: npx playwright install chromium | |
| - name: Install Playwright Firefox | |
| timeout-minutes: 10 | |
| env: | |
| DEBUG: pw:install | |
| run: npx playwright install firefox | |
| - name: Run jstest | |
| run: npm run jstest |