harden(review): bound the Goodreads curl fetch; guard escapeshellarg #685
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: E2E Test Suite | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| e2e: | |
| name: Full E2E suite (Playwright · Apache · MySQL) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| services: | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: pinakes_test | |
| ports: | |
| - 3306:3306 | |
| options: >- | |
| --health-cmd="mysqladmin ping -h 127.0.0.1" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=10 | |
| env: | |
| E2E_BASE_URL: http://localhost:8081 | |
| E2E_ADMIN_EMAIL: admin@pinakes.test | |
| E2E_ADMIN_PASS: Test1234! | |
| E2E_DB_HOST: 127.0.0.1 | |
| E2E_DB_PORT: 3306 | |
| E2E_DB_USER: root | |
| E2E_DB_PASS: root | |
| E2E_DB_NAME: pinakes_test | |
| E2E_INSTALL_ROOT: ${{ github.workspace }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # ── Apache2 + mod_php ─────────────────────────────────────────────────── | |
| # Install Apache before setup-php adds external package sources; the app | |
| # supports PHP >= 8.1, so the Ubuntu mod_php package is sufficient here. | |
| - name: Install Apache2 + mod_php | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y apache2 libapache2-mod-php | |
| sudo a2enmod rewrite headers env | |
| php_module="$(find /etc/apache2/mods-available -maxdepth 1 -name 'php*.load' -printf '%f\n' | sed 's/\.load$//' | sort -V | tail -n1)" | |
| if [ -n "${php_module}" ]; then | |
| sudo a2enmod "${php_module}" | |
| fi | |
| sudo a2dissite 000-default || true | |
| # ── PHP 8.2 CLI for Composer and tooling ──────────────────────────────── | |
| - name: Setup PHP 8.2 | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: mysqli, mbstring, json, curl, openssl, zip, gd, intl, xml | |
| coverage: none | |
| github-token: '' | |
| - name: Configure virtual host on port 8081 | |
| env: | |
| WORKSPACE: ${{ github.workspace }} | |
| run: | | |
| sudo tee /etc/apache2/sites-available/pinakes.conf > /dev/null <<EOF | |
| Listen 8081 | |
| <VirtualHost *:8081> | |
| ServerName localhost | |
| DocumentRoot ${WORKSPACE}/public | |
| SetEnv PINAKES_E2E_BYPASS_RATE_LIMIT 1 | |
| # E2E only — deterministic, network-free bulk-enrich scraper stub. | |
| # Removes external-API latency/flakiness; never set in production. | |
| SetEnv PINAKES_E2E_SCRAPER_STUB 1 | |
| <Directory "${WORKSPACE}/public"> | |
| Options -Indexes +FollowSymLinks | |
| AllowOverride All | |
| Require all granted | |
| </Directory> | |
| ErrorLog \${APACHE_LOG_DIR}/pinakes-error.log | |
| CustomLog \${APACHE_LOG_DIR}/pinakes-access.log combined | |
| </VirtualHost> | |
| EOF | |
| sudo a2ensite pinakes | |
| sudo apachectl configtest | |
| # ── PHP dependencies ──────────────────────────────────────────────────── | |
| - name: Cache Composer packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: vendor | |
| key: composer-${{ hashFiles('composer.lock') }} | |
| restore-keys: composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader | |
| # ── Node / Playwright ──────────────────────────────────────────────────── | |
| - name: Setup Node 20 | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install Node dependencies | |
| run: npm install --silent | |
| - name: Install Playwright Chromium | |
| run: npx playwright install chromium --with-deps | |
| # ── Permissions: Apache (www-data) must write .env, .installed, storage ─ | |
| - name: Grant write permissions to workspace | |
| env: | |
| WORKSPACE: ${{ github.workspace }} | |
| run: | | |
| path="${WORKSPACE}" | |
| while [ "${path}" != "/" ]; do | |
| sudo chmod o+x "${path}" || true | |
| path="$(dirname "${path}")" | |
| done | |
| sudo chmod 777 "${WORKSPACE}" | |
| sudo chmod -R 777 "${WORKSPACE}/storage" | |
| sudo mkdir -p "${WORKSPACE}/public/uploads" | |
| sudo chmod -R 777 "${WORKSPACE}/public/uploads" | |
| # ── Start Apache ───────────────────────────────────────────────────────── | |
| - name: Start Apache and wait for installer | |
| run: | | |
| sudo systemctl start apache2 | |
| echo "Waiting for Apache on :8081..." | |
| for i in $(seq 1 30); do | |
| if curl -sf -o /dev/null "http://localhost:8081/installer/"; then | |
| echo "ok — Apache up (attempt $i)" | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| echo "Apache did not respond in 30s" | |
| sudo journalctl -u apache2 --no-pager -n 30 >&2 | |
| sudo cat /var/log/apache2/pinakes-error.log >&2 || true | |
| exit 1 | |
| # ── Test suite ─────────────────────────────────────────────────────────── | |
| - name: "[1/13] full-test.spec.js — complete application lifecycle" | |
| run: | | |
| npx playwright test tests/full-test.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[2/13] schema-integrity.spec.js — post-install table verification" | |
| run: | | |
| npx playwright test tests/schema-integrity.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[3/13] plugin-integrity.spec.js — plugin registration regression" | |
| run: | | |
| npx playwright test tests/plugin-integrity.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[4/13] mag-validation.spec.js — OAI-PMH MAG 2.0.1 metadata" | |
| run: | | |
| npx playwright test tests/mag-validation.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[5/13] archives-search.spec.js — ricerca archivi admin + pubblica" | |
| run: | | |
| npx playwright test tests/archives-search.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[6/13] ncip-server.spec.js — NCIP 2.0 endpoints v0.7.4" | |
| run: | | |
| npx playwright test tests/ncip-server.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[7/13] archives-phase5-admin-ui.spec.js — RiC-CM Phase 5 admin UI v0.7.12" | |
| run: | | |
| npx playwright test tests/archives-phase5-admin-ui.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[8/13] archives-phase6-oai-ric-o.spec.js — OAI-PMH metadataPrefix=ric-o v0.7.12" | |
| run: | | |
| npx playwright test tests/archives-phase6-oai-ric-o.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[9/13] archives-ric-jsonld.spec.js — RiC-O JSON-LD endpoints (Phase 1-4)" | |
| run: | | |
| npx playwright test tests/archives-ric-jsonld.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[10/13] archives-crud.spec.js — archival_units CRUD (#103)" | |
| run: | | |
| npx playwright test tests/archives-crud.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[11/13] archives-authorities.spec.js — authority_records CRUD" | |
| run: | | |
| npx playwright test tests/archives-authorities.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[12/13] archives-upload-assets.spec.js — cover/document upload pipeline" | |
| # The upload-assets spec picks 3 archival_units from the | |
| # `E2E_SEED_*` batch — it doesn't seed them itself. The seed | |
| # lives in archives-seed-50.spec.js (50 rows, all 4 levels, | |
| # full option matrix). On a fresh CI install no E2E_SEED_* row | |
| # exists yet, so the upload spec's beforeAll fails with | |
| # "no seeded row available for PDF test". Seed-then-upload | |
| # in the same step keeps the dependency local — no test-order | |
| # coupling with other steps, no separate seed step to renumber. | |
| run: | | |
| npx playwright test tests/archives-seed-50.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| npx playwright test tests/archives-upload-assets.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[13/13] archives-pr-extended.spec.js — extended CRUD + CSRF regression" | |
| # The pr-extended spec creates stub files directly via | |
| # fs.writeFileSync under public/uploads/archives/documents/ to | |
| # drive the IIIF rendering[] assertions. The previous step | |
| # ([12/13] upload-assets) ran real uploads through Apache, | |
| # which created subdirectories owned by www-data — the runner | |
| # process can no longer write into them, so the test EACCES'es | |
| # before the first assertion. Re-chmod the uploads tree right | |
| # before this step (idempotent, no-op when already 777). | |
| run: | | |
| sudo chmod -R 777 "${GITHUB_WORKSPACE}/public/uploads" || true | |
| npx playwright test tests/archives-pr-extended.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[required] issue-237-contributors.spec.js — pseudonym + contributor save contract" | |
| run: | | |
| npx playwright test tests/issue-237-contributors.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[required] issue-255-registration.spec.js — configurable registration fields" | |
| run: | | |
| npx playwright test tests/issue-255-registration.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[required] release-0.7.37-integration.spec.js — merged settings contract" | |
| run: | | |
| npx playwright test tests/release-0.7.37-integration.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| # ── Orphan-spec backfill: tests that were authored against a fresh | |
| # install but never wired into the CI workflow. Adding them as | |
| # `continue-on-error: true` so we get the signal from a clean | |
| # fixture without gating the merge on tests that may have flake- | |
| # or design-level issues unrelated to the merging PR. Once the | |
| # individual specs prove stable (see #144 follow-up triage), drop | |
| # the continue-on-error and promote to a numbered step. ────────── | |
| - name: "[orphan/1] book-form-comprehensive.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/book-form-comprehensive.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/2] code-quality.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/code-quality.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/3] genre-merge-rearrange.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/genre-merge-rearrange.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/4] loan-reservation.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/loan-reservation.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/5] multisource-scraping.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/multisource-scraping.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/6] security-hardening.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/security-hardening.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/7] viaf-authority.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/viaf-authority.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "[orphan/8] interop-specific.spec.js" | |
| continue-on-error: true | |
| run: | | |
| npx playwright test tests/interop-specific.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| - name: "book-club-uwe.spec.js — meeting/member edit + external book proposals (#138)" | |
| run: | | |
| npx playwright test tests/book-club-uwe.spec.js \ | |
| --config=tests/playwright.config.js \ | |
| --workers=1 | |
| # ── Artifacts on failure ───────────────────────────────────────────────── | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: playwright-report-${{ github.run_id }} | |
| path: | | |
| playwright-report/ | |
| test-results/ | |
| retention-days: 14 | |
| - name: Upload Apache error log | |
| uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: apache-error-log-${{ github.run_id }} | |
| path: /var/log/apache2/pinakes-error.log | |
| retention-days: 7 |