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
240 changes: 240 additions & 0 deletions .github/workflows/_python-react-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
name: Reusable Python+React Test Suite

# Internal building block: the backend/frontend/e2e/api-freshness matrix
# shared by python-react-ci.yml and python-react-publish.yml. Consumers
# don't call this directly — the CI and publish workflows wrap it. The
# leading underscore marks it internal.
#
# Local-path callers (./.github/workflows/_python-react-tests.yml) resolve
# to THIS repo at the SAME ref the consumer pinned, so a consumer on
# @v1.4.0 gets the v1.4.0 copy of this file too — no extra pin needed.

on:
workflow_call:
inputs:
python-version:
type: string
default: "3.14"
bun-version-file:
type: string
default: ".bun-version"
bun-version:
# Escape hatch — leave empty to use bun-version-file.
type: string
default: ""
enable-e2e:
type: boolean
default: true
enable-translations:
type: boolean
default: false
enable-bootstrap-token:
type: boolean
default: false
enable-api-freshness-check:
type: boolean
default: true
runner-os:
type: string
default: ubuntu-latest
security-tripwire-script:
type: string
default: ""

permissions:
contents: read

jobs:
test-backend:
name: Backend Tests
runs-on: ${{ inputs.runner-os }}
timeout-minutes: 25

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ inputs.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8
with:
enable-cache: true
cache-dependency-glob: 'backend/uv.lock'

- name: Install dependencies
run: |
cd backend
uv sync --frozen --all-extras

- name: Run ruff linter
run: cd backend && uv run ruff check .

- name: Run ruff formatter check
run: cd backend && uv run ruff format --check .

- name: Run pyright type checking
run: cd backend && PYTHONPATH=. uv run pyright

- name: Security tripwire
if: inputs.security-tripwire-script != ''
run: bash "${{ inputs.security-tripwire-script }}"

- name: Run pytest
run: |
cd backend
timeout 20m uv run pytest tests/ -v --tb=short

test-frontend:
name: Frontend Tests
runs-on: ${{ inputs.runner-os }}
timeout-minutes: 15

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up Bun (from version-file)
if: inputs.bun-version == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: ${{ inputs.bun-version-file }}

- name: Set up Bun (from explicit override)
if: inputs.bun-version != ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: ${{ inputs.bun-version }}

- name: Install dependencies
run: |
cd frontend
if [ "${{ github.actor }}" = "dependabot[bot]" ]; then bun install; else bun install --frozen-lockfile; fi

- name: Run TypeScript type checking
run: cd frontend && bun run type-check

- name: Run linter
run: cd frontend && bun run lint

- name: Validate translation keys
if: inputs.enable-translations
run: cd frontend && bun run validate:translations

- name: Run tests
run: cd frontend && bun run test:run

test-e2e:
name: E2E Tests
if: inputs.enable-e2e
runs-on: ${{ inputs.runner-os }}
timeout-minutes: 20
needs: [test-backend, test-frontend]

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ inputs.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8
with:
enable-cache: true
cache-dependency-glob: 'backend/uv.lock'

- name: Install backend dependencies
run: cd backend && uv sync --frozen --all-extras

- name: Set up Bun (from version-file)
if: inputs.bun-version == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: ${{ inputs.bun-version-file }}

- name: Set up Bun (from explicit override)
if: inputs.bun-version != ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: ${{ inputs.bun-version }}

- name: Install frontend dependencies
run: |
cd frontend
if [ "${{ github.actor }}" = "dependabot[bot]" ]; then bun install; else bun install --frozen-lockfile; fi

- name: Install Playwright browsers
run: cd frontend && bunx playwright install chromium --with-deps

- name: Run E2E tests
run: cd frontend && bun run e2e
env:
CI: true
VULNFORGE_BOOTSTRAP_TOKEN: ${{ inputs.enable-bootstrap-token && 'e2e-ci-test-token' || '' }}

- name: Upload Playwright report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: ${{ !cancelled() }}
with:
name: playwright-report
path: frontend/playwright-report/
retention-days: 14

- name: Upload test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: ${{ !cancelled() }}
with:
name: playwright-results
path: frontend/test-results/
retention-days: 7

api-types-freshness:
name: API Types Freshness
if: inputs.enable-api-freshness-check
runs-on: ${{ inputs.runner-os }}
timeout-minutes: 15
needs: [test-backend]

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ inputs.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8
with:
enable-cache: true
cache-dependency-glob: 'backend/uv.lock'

- name: Install backend dependencies
run: cd backend && uv sync --frozen --all-extras

- name: Set up Bun (from version-file)
if: inputs.bun-version == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: ${{ inputs.bun-version-file }}

- name: Set up Bun (from explicit override)
if: inputs.bun-version != ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: ${{ inputs.bun-version }}

- name: Install frontend dependencies
run: |
cd frontend
if [ "${{ github.actor }}" = "dependabot[bot]" ]; then bun install; else bun install --frozen-lockfile; fi

- name: Check API types freshness
run: cd frontend && bun run check:api-freshness
43 changes: 8 additions & 35 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ${{ inputs.runner-os }}
timeout-minutes: 360
# build-mode: none scans source only; no language toolchain or
# dependency install runs, so these analyses finish well under an hour.
timeout-minutes: 45
permissions:
security-events: write
contents: read
Expand All @@ -52,45 +54,16 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up Python
if: matrix.language == 'python'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ inputs.python-version }}

- name: Install Python dependencies
if: matrix.language == 'python'
run: |
cd backend
pip install --upgrade pip
if [ -f requirements.txt ]; then
pip install -r requirements.txt
elif [ -f pyproject.toml ]; then
pip install -e .
fi

- name: Set up Bun (from version-file)
if: matrix.language == 'javascript' && inputs.bun-version == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: ${{ inputs.bun-version-file }}

- name: Set up Bun (from explicit override)
if: matrix.language == 'javascript' && inputs.bun-version != ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: ${{ inputs.bun-version }}

- name: Install JS dependencies
if: matrix.language == 'javascript'
run: |
cd frontend
if [ "${{ github.actor }}" = "dependabot[bot]" ]; then bun install; else bun install --frozen-lockfile; fi
# No language setup / dependency install: CodeQL for Python and
# JavaScript uses build-mode: none, which scans source directly.
# Dependency installation has had no effect on Python results since
# CodeQL 2.16 (Jan 2024), so installing it only burned runner time.

- name: Initialize CodeQL
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
queries: security-extended,security-and-quality
config-file: ${{ inputs.codeql-config-file }}
source-root: ${{ matrix.language == 'python' && 'backend' || 'frontend' }}
Expand Down
Loading