diff --git a/expenses_manager/Jenkinsfile b/expenses_manager/Jenkinsfile index 9ccf69c..dc59422 100644 --- a/expenses_manager/Jenkinsfile +++ b/expenses_manager/Jenkinsfile @@ -1,39 +1,49 @@ -pipeline{ +pipeline { agent any environment { - DEBUG = 'True' - DJANGO_SECRET_KEY = 'test-secret-key-for-ci' - DJANGO_SETTINGS_MODULE = "expenses_manager.settings" - PYTHONUNBUFFERED = "1" + // settings.py demands SECRET_KEY unless DEBUG is exactly "True". + // A throwaway value lets CI run with DEBUG unset — that is, False, as + // in production — instead of falling back to the insecure dev key. + SECRET_KEY = 'ci-only-value-never-used-in-production' + DJANGO_SETTINGS_MODULE = 'expenses_manager.settings' + PYTHONUNBUFFERED = '1' + } + + options { + buildDiscarder(logRotator(numToKeepStr: '30')) + timeout(time: 20, unit: 'MINUTES') } stages { - stage('Checkout') { - steps { - checkout scm - } - } - stage('Setup Python') { steps { sh ''' cd expenses_manager python --version python -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install -r requirements.txt + venv/bin/pip install --upgrade pip + venv/bin/pip install -r requirements.txt ''' - } + } + } + + // Catches models changed without generating the migration. Exactly the + // failure mode that reached production as a 500 on 2026-09-29. + stage('Check migrations') { + steps { + sh ''' + cd expenses_manager + venv/bin/python manage.py makemigrations --check --dry-run + ''' + } } stage('Run tests') { steps { sh ''' cd expenses_manager - . venv/bin/activate - venv/bin/pytest --cov + venv/bin/pytest --cov --junitxml=junit.xml ''' } } @@ -41,10 +51,33 @@ pipeline{ post { always { - echo 'Pipeline finished.' + // allowEmptyResults: false is deliberate. A build where pytest died + // before collecting anything produces no XML, and that must fail + // rather than pass quietly — as happened for months in 2026. + junit allowEmptyResults: false, testResults: 'expenses_manager/junit.xml' } failure { - echo 'Tests failed.' + notifyTelegram("❌ FALLO") + } + fixed { + notifyTelegram("✅ Recuperado") } } +} + +// Sends a one-line build summary to Telegram. Credentials are read from the +// Jenkins store so the token never lives in the repository. +void notifyTelegram(String status) { + withCredentials([ + string(credentialsId: 'telegram-bot-token', variable: 'TG_TOKEN'), + string(credentialsId: 'telegram-chat-id', variable: 'TG_CHAT') + ]) { + sh """ + curl -s -X POST "https://api.telegram.org/bot\${TG_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=\${TG_CHAT}" \ + --data-urlencode "text=${status} ${env.JOB_NAME} #${env.BUILD_NUMBER} +Rama: ${env.BRANCH_NAME ?: 'main'} +${env.BUILD_URL}" > /dev/null + """ + } } \ No newline at end of file