mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-04-07 05:12:19 +00:00
feat: migrate to pyproject.toml, add test suites, split CI workflows
- Replace requirements.txt with pyproject.toml for modern Python packaging - Add unit, integration, lint and security test suites under tests/ - Add utils/export_runtime_requirements.py and utils/check_hadolint_sarif.py - Split monolithic CI into reusable lint.yml, security.yml and tests.yml - Refactor ci.yml to orchestrate reusable workflows; publish on semver tag only - Modernize Dockerfile: pin python:3.12-slim, install via pyproject.toml - Expand Makefile with lint, security, test and CI targets - Add test-e2e via act with portfolio container stop/start around run - Fix navbar_logo_visibility.spec.js: win.fullscreen() → win.enterFullscreen() - Set use_reloader=False in app.run() to prevent double-start in CI - Add app/core.* and build artifacts to .gitignore - Fix apt-get → sudo apt-get in tests.yml e2e job - Fix pip install --ignore-installed to handle stale act cache Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
68
.github/workflows/ci.yml
vendored
68
.github/workflows/ci.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
@@ -9,59 +10,48 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
test-and-publish:
|
||||
security:
|
||||
name: Run security workflow
|
||||
uses: ./.github/workflows/security.yml
|
||||
|
||||
tests:
|
||||
name: Run test workflow
|
||||
uses: ./.github/workflows/tests.yml
|
||||
|
||||
lint:
|
||||
name: Run lint workflow
|
||||
uses: ./.github/workflows/lint.yml
|
||||
|
||||
publish:
|
||||
name: Publish image
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PORT: "5000"
|
||||
needs:
|
||||
- security
|
||||
- tests
|
||||
- lint
|
||||
if: github.event_name == 'push'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: pip install -r app/requirements.txt
|
||||
|
||||
- name: Prepare app config for CI
|
||||
run: cp app/config.sample.yaml app/config.yaml
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
cache-dependency-path: app/package.json
|
||||
|
||||
- name: Install Node dependencies
|
||||
working-directory: app
|
||||
run: npm install
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v6
|
||||
with:
|
||||
working-directory: app
|
||||
install: false
|
||||
start: python app.py
|
||||
wait-on: http://127.0.0.1:5000
|
||||
wait-on-timeout: 120
|
||||
|
||||
- name: Detect semver tag on current commit
|
||||
id: semver
|
||||
run: |
|
||||
SEMVER_TAG="$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)"
|
||||
if [ -n "$SEMVER_TAG" ]; then
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
echo "raw_tag=$SEMVER_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${SEMVER_TAG#v}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "found=true"
|
||||
echo "raw_tag=$SEMVER_TAG"
|
||||
echo "version=${SEMVER_TAG#v}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
77
.github/workflows/lint.yml
vendored
Normal file
77
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-actions:
|
||||
name: Lint GitHub Actions
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run actionlint
|
||||
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest
|
||||
|
||||
lint-python:
|
||||
name: Lint Python
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install lint dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ".[dev]"
|
||||
|
||||
- name: Ruff lint
|
||||
run: ruff check .
|
||||
|
||||
- name: Ruff format check
|
||||
run: ruff format --check .
|
||||
|
||||
lint-docker:
|
||||
name: Lint Dockerfile
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run hadolint
|
||||
id: hadolint
|
||||
continue-on-error: true
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5
|
||||
with:
|
||||
dockerfile: ./Dockerfile
|
||||
format: sarif
|
||||
output-file: hadolint-results.sarif
|
||||
failure-threshold: warning
|
||||
|
||||
- name: Upload hadolint SARIF
|
||||
if: always() && github.event_name == 'push'
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: hadolint-results.sarif
|
||||
wait-for-processing: true
|
||||
category: hadolint
|
||||
|
||||
- name: Fail on hadolint warnings
|
||||
if: always()
|
||||
run: python3 utils/check_hadolint_sarif.py hadolint-results.sarif
|
||||
48
.github/workflows/security.yml
vendored
Normal file
48
.github/workflows/security.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Security
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Run security scan
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
queries: security-extended,security-and-quality
|
||||
|
||||
- name: Run manual build steps
|
||||
if: matrix.build-mode == 'manual'
|
||||
run: |
|
||||
echo "No manual build is configured for this repository."
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
194
.github/workflows/tests.yml
vendored
Normal file
194
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-lint:
|
||||
name: Run lint tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Run lint test suite
|
||||
run: python -m unittest discover -s tests/lint -t .
|
||||
|
||||
test-integration:
|
||||
name: Run integration tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install integration test dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed .
|
||||
|
||||
- name: Run integration test suite
|
||||
run: python -m unittest discover -s tests/integration -t .
|
||||
|
||||
test-unit:
|
||||
name: Run unit tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install unit test dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed .
|
||||
|
||||
- name: Run unit test suite
|
||||
run: python -m unittest discover -s tests/unit -t .
|
||||
|
||||
security-python:
|
||||
name: Run Python security checks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install security dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed ".[dev]"
|
||||
|
||||
- name: Run Bandit
|
||||
run: python -m bandit -q -ll -ii -r app main.py
|
||||
|
||||
- name: Export runtime requirements
|
||||
run: python utils/export_runtime_requirements.py > runtime-requirements.txt
|
||||
|
||||
- name: Audit Python runtime dependencies
|
||||
run: python -m pip_audit -r runtime-requirements.txt
|
||||
|
||||
test-security:
|
||||
name: Run security guardrail tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install security test dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed .
|
||||
|
||||
- name: Run security test suite
|
||||
run: python -m unittest discover -s tests/security -t .
|
||||
|
||||
e2e:
|
||||
name: Run end-to-end tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-lint
|
||||
- test-unit
|
||||
- test-integration
|
||||
- security-python
|
||||
- test-security
|
||||
env:
|
||||
FLASK_HOST: "127.0.0.1"
|
||||
FLASK_PORT: "5001"
|
||||
PORT: "5001"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed .
|
||||
|
||||
- name: Prepare app config for CI
|
||||
run: cp app/config.sample.yaml app/config.yaml
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
cache-dependency-path: app/package.json
|
||||
|
||||
- name: Install Node dependencies
|
||||
working-directory: app
|
||||
run: npm install
|
||||
|
||||
- name: Install Cypress system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libasound2t64 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libatspi2.0-0t64 \
|
||||
libcups2t64 \
|
||||
libdrm2 \
|
||||
libgbm1 \
|
||||
libglib2.0-0t64 \
|
||||
libgtk-3-0t64 \
|
||||
libnotify4 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libxcomposite1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxkbcommon0 \
|
||||
libxrandr2 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
xauth \
|
||||
xvfb
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v6
|
||||
with:
|
||||
working-directory: app
|
||||
install: false
|
||||
start: python app.py
|
||||
wait-on: http://127.0.0.1:5001
|
||||
wait-on-timeout: 120
|
||||
Reference in New Issue
Block a user