pipeline { agent any environment { PYTHON_VERSION = '3.11' NODE_VERSION = '18' SUT_URL = 'http://localhost' } options { timeout(time: 30, unit: 'MINUTES') buildDiscarder(logRotator(numToKeepStr: '10')) disableConcurrentBuilds() } stages { stage('Checkout') { steps { echo 'One does not simply skip the checkout stage...' checkout scm } } stage('Setup Environment') { steps { echo 'Preparing the Fellowship environment...' sh ''' # Create a Python virtual environment (PEP 668 compliant) python3 -m venv venv . venv/bin/activate python3 -m pip install --upgrade pip setuptools wheel echo "✓ Virtual environment ready" ''' } } stage('Install Dependencies') { parallel { stage('Backend Deps') { steps { echo 'Installing backend dependencies for the Fellowship...' sh ''' . venv/bin/activate pip install -r sut/backend/requirements.txt pip install -r tests/requirements.txt ''' } } stage('Frontend Deps') { steps { echo 'Installing frontend dependencies...' sh ''' cd sut/frontend npm ci --prefer-offline || npm install ''' } } } } stage('Lint') { steps { echo 'The Eye of Sauron watches for code style violations...' sh ''' . venv/bin/activate pip install flake8 --quiet # E9/F63/F7/F82 = syntax & fatal errors — these must fail the build. # Warnings (E1xx, W) are informational only for this tutorial pipeline. flake8 sut/backend tests/*.py \ --count --select=E9,F63,F7,F82 \ --show-source --statistics ''' } } stage('Unit Tests') { steps { echo 'Running unit tests across the Fellowship...' sh ''' . venv/bin/activate mkdir -p reports PYTHONPATH=sut/backend python -m pytest \ tests/backend/services/test_bargaining_algorithm.py \ tests/backend/services/test_bargaining_config.py \ tests/backend/services/test_shop_service.py \ tests/backend/services/test_negotiation_logger.py \ -v --tb=short \ --junitxml=reports/unit-tests.xml \ --no-header \ --override-ini="addopts=--strict-markers --tb=short -v" ''' } post { always { junit allowEmptyResults: true, testResults: 'reports/unit-tests.xml' } } } stage('Build Frontend') { steps { echo 'Forging the frontend in the fires of Mount Doom...' sh ''' cd sut/frontend npm run build ''' } } stage('Integration Tests') { steps { echo 'The quest continues — running integration tests...' sh ''' . venv/bin/activate mkdir -p reports PYTHONPATH=sut/backend python -m pytest \ tests/backend/services/test_bargaining_algorithm.py \ tests/backend/services/test_bargaining_config.py \ tests/backend/services/test_shop_service.py \ tests/backend/services/test_negotiation_logger.py \ -v --tb=short \ --junitxml=reports/integration-tests.xml \ --no-header \ --override-ini="addopts=--strict-markers --tb=short -v" \ -m "not realstack" ''' } post { always { junit allowEmptyResults: true, testResults: 'reports/integration-tests.xml' } } } stage('Deploy') { when { branch 'main' } steps { echo 'Deploying the Fellowship SUT to the land of Docker...' sh ''' cp caddy/Caddyfile.local caddy/Caddyfile docker compose down --remove-orphans || true docker compose up -d --build ''' } } } post { always { echo 'The journey is complete — collecting artifacts...' archiveArtifacts artifacts: 'reports/**/*.xml', allowEmptyArchive: true } success { echo 'The Fellowship has succeeded! The quest is complete.' } failure { echo 'The Fellowship has failed! Sauron grows stronger...' mail to: 'fellowship@middle-earth.local', subject: "Build Failed: ${env.JOB_NAME} [${env.BUILD_NUMBER}]", body: "The Ring has been lost. Build ${env.BUILD_URL} has failed." } unstable { echo 'The quest is unstable — some tests did not pass.' } } }