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
68 changes: 57 additions & 11 deletions .github/workflows/cd-release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
name: CD - Release Build

# This workflow builds and publishes official releases
#
# Testing Strategy:
# - Full test suite runs on PRs via ci-comprehensive.yml
# - This workflow focuses on building and publishing
# - Security audit still runs to catch any last-minute issues
# - Tests can optionally be run via workflow_dispatch for manual releases
#
# Workflow is triggered by:
# - Push to main/master (after PR merge)
# - Git tags (v*.*.*)
# - Release events
# - Manual workflow_dispatch

on:
push:
branches: [ main, master ]
Expand All @@ -13,10 +27,10 @@ on:
required: true
type: string
skip_tests:
description: 'Skip tests (not recommended)'
description: 'Skip tests (tests already ran on PR, only for workflow_dispatch)'
required: false
type: boolean
default: false
default: true

env:
REGISTRY: ghcr.io
Expand All @@ -25,12 +39,14 @@ env:

jobs:
# ============================================================================
# Full Test Suite
# Full Test Suite (Optional - tests already ran on PR)
# ============================================================================
full-test-suite:
name: Full Test Suite
name: Full Test Suite (Optional)
runs-on: ubuntu-latest
if: github.event.inputs.skip_tests != 'true'
# Skip by default since tests already ran on PR
# Only run if explicitly requested via workflow_dispatch
if: github.event_name == 'workflow_dispatch' && github.event.inputs.skip_tests != 'true'
timeout-minutes: 30

services:
Expand Down Expand Up @@ -162,7 +178,7 @@ jobs:
check_name: Release Test Results

# ============================================================================
# Security Audit
# Security Audit (always runs for releases)
# ============================================================================
security-audit:
name: Security Audit
Expand Down Expand Up @@ -288,8 +304,9 @@ jobs:
build-and-push:
name: Build and Push Release Image
runs-on: ubuntu-latest
needs: [full-test-suite, security-audit, determine-version]
if: always() && (needs.full-test-suite.result == 'success' || needs.full-test-suite.result == 'skipped')
needs: [security-audit, determine-version]
# Note: full-test-suite is optional, so we don't depend on it
# Tests already ran on PR before merge
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -332,12 +349,40 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
echo "Injecting analytics configuration into build..."

# Verify secrets are available
if [ -z "$POSTHOG_API_KEY" ]; then
echo "❌ ERROR: POSTHOG_API_KEY secret is not set!"
echo "Please set it in: Settings → Secrets and variables → Actions"
exit 1
fi

if [ -z "$SENTRY_DSN" ]; then
echo "⚠️ WARNING: SENTRY_DSN secret is not set (optional)"
fi

# Perform replacement
sed -i "s|%%POSTHOG_API_KEY_PLACEHOLDER%%|${POSTHOG_API_KEY}|g" app/config/analytics_defaults.py
sed -i "s|%%SENTRY_DSN_PLACEHOLDER%%|${SENTRY_DSN}|g" app/config/analytics_defaults.py

# Verify placeholders were replaced
if grep -q "%%POSTHOG_API_KEY_PLACEHOLDER%%" app/config/analytics_defaults.py; then
echo "❌ ERROR: PostHog API key placeholder not replaced!"; exit 1;
fi
echo "✅ Analytics configuration injected"

if grep -q "%%SENTRY_DSN_PLACEHOLDER%%" app/config/analytics_defaults.py; then
echo "❌ ERROR: Sentry DSN placeholder not replaced!"; exit 1;
fi

# Verify the actual key format (should start with 'phc_')
if ! grep -q "POSTHOG_API_KEY_DEFAULT = \"phc_" app/config/analytics_defaults.py; then
echo "❌ ERROR: PostHog API key doesn't appear to be in correct format (should start with 'phc_')"
exit 1
fi

echo "✅ Analytics configuration injected and verified"
echo "✅ PostHog API key: phc_***${POSTHOG_API_KEY: -4}"
echo "✅ Sentry DSN: ${SENTRY_DSN:0:20}..."

- name: Build and push Docker image
uses: docker/build-push-action@v5
Expand Down Expand Up @@ -574,7 +619,7 @@ jobs:
release-summary:
name: Release Summary
runs-on: ubuntu-latest
needs: [full-test-suite, security-audit, build-and-push, determine-version, create-release]
needs: [security-audit, build-and-push, determine-version, create-release]
if: always()

steps:
Expand All @@ -583,11 +628,12 @@ jobs:
echo "## 🚀 Release ${{ needs.determine-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Build Status" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Tests: ${{ needs.full-test-suite.result }}" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Security: ${{ needs.security-audit.result }}" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Build: ${{ needs.build-and-push.result }}" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Release: ${{ needs.create-release.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "ℹ️ *Full test suite already ran on PR before merge*" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🐳 Docker Images" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.determine-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
Expand Down
85 changes: 79 additions & 6 deletions .github/workflows/ci-comprehensive.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
name: Comprehensive CI Pipeline

# This workflow runs comprehensive tests on pull requests
#
# Test Strategy:
# - Smoke tests (fast, critical) run first
# - Unit, integration, security, and code quality tests run in parallel
# - Full test suite with PostgreSQL runs for PRs to main/master
# - Docker build test ensures the image builds correctly
# - Test summary posted as PR comment
#
# All tests must pass before a PR can be merged to main

on:
pull_request:
branches: [ main, develop ]
Expand Down Expand Up @@ -289,12 +300,13 @@ jobs:
docker rm test-container

# ============================================================================
# Full Test Suite (for releases)
# Full Test Suite (runs on all PRs to main/master)
# ============================================================================
full-test-suite:
name: Full Test Suite
name: Full Test Suite with PostgreSQL
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [smoke-tests, unit-tests, integration-tests]
if: github.event_name == 'pull_request' && (github.base_ref == 'main' || github.base_ref == 'master')
timeout-minutes: 30

services:
Expand Down Expand Up @@ -328,14 +340,66 @@ jobs:
pip install -r requirements-test.txt
pip install -e .

- name: Validate database migrations
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
FLASK_APP: app.py
FLASK_ENV: testing
run: |
echo "🔍 Validating database migrations..."

# Check if there are migration-related changes
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E "(app/models/|migrations/)" > /dev/null; then
echo "📋 Migration-related changes detected"

# Initialize fresh database
flask db upgrade

# Test migration rollback
CURRENT_MIGRATION=$(flask db current)
echo "Current migration: $CURRENT_MIGRATION"

if [ -n "$CURRENT_MIGRATION" ] && [ "$CURRENT_MIGRATION" != "None" ]; then
echo "Testing migration operations..."
flask db upgrade head
echo "✅ Migration validation passed"
fi

# Test with sample data
python -c "
from app import create_app, db
from app.models.user import User
from app.models.project import Project
from app.models.client import Client

app = create_app()
with app.app_context():
user = User(username='test_user', role='user')
db.session.add(user)
db.session.commit()

client = Client(name='Test Client', description='Test client')
db.session.add(client)
db.session.commit()

project = Project(name='Test Project', client_id=client.id, description='Test project')
db.session.add(project)
db.session.commit()
print('✅ Sample data created and validated successfully')
"
else
echo "ℹ️ No migration-related changes detected"
fi

- name: Run full test suite
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
FLASK_APP: app.py
FLASK_ENV: testing
PYTHONPATH: ${{ github.workspace }}
run: |
pytest -v --cov=app --cov-report=xml --cov-report=html --cov-report=term
pytest -v --cov=app --cov-report=xml --cov-report=html --cov-report=term \
--junitxml=junit.xml

- name: Upload full coverage
uses: codecov/codecov-action@v4
Expand All @@ -352,14 +416,22 @@ jobs:
path: |
htmlcov/
coverage.xml
junit.xml

- name: Publish full test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: junit.xml
check_name: Full Test Suite Results

# ============================================================================
# Test Summary and PR Comment
# ============================================================================
test-summary:
name: Test Summary
runs-on: ubuntu-latest
needs: [smoke-tests, unit-tests, integration-tests, security-tests, code-quality, docker-build]
needs: [smoke-tests, unit-tests, integration-tests, security-tests, code-quality, docker-build, full-test-suite]
if: always() && github.event_name == 'pull_request'
permissions:
contents: read
Expand All @@ -377,7 +449,8 @@ jobs:
{ name: 'Integration Tests', result: '${{ needs.integration-tests.result }}' },
{ name: 'Security Tests', result: '${{ needs.security-tests.result }}' },
{ name: 'Code Quality', result: '${{ needs.code-quality.result }}' },
{ name: 'Docker Build', result: '${{ needs.docker-build.result }}' }
{ name: 'Docker Build', result: '${{ needs.docker-build.result }}' },
{ name: 'Full Test Suite', result: '${{ needs.full-test-suite.result }}' }
];

const passed = jobs.filter(j => j.result === 'success').length;
Expand Down
Loading
Loading