CI/CD Pipeline #120
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [ main, develop, feature/* ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| schedule: | |
| # Run tests daily at 2 AM UTC to catch external API changes | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| run_integration_tests: | |
| description: 'Run integration tests with live APIs' | |
| required: false | |
| default: true | |
| type: boolean | |
| env: | |
| COMPOSER_CACHE_DIR: ~/.composer/cache | |
| jobs: | |
| validate: | |
| name: Code Validation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: dom, curl, libxml, mbstring, zip, json | |
| tools: composer:v2 | |
| - name: Validate composer.json and composer.lock | |
| run: composer validate --strict | |
| - name: Check file permissions | |
| run: | | |
| find . -name "*.php" -exec ls -la {} \; | |
| chmod +x doi2jats.php | |
| test: | |
| name: Tests | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.1', '8.2', '8.3'] | |
| dependency-version: [prefer-stable] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, json | |
| coverage: xdebug | |
| tools: composer:v2 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT | |
| - name: Cache Composer packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.composer-cache.outputs.dir }} | |
| key: ubuntu-composer-${{ matrix.php }}-${{ matrix.dependency-version }}-${{ hashFiles('**/composer.lock') }} | |
| restore-keys: | | |
| ubuntu-composer-${{ matrix.php }}-${{ matrix.dependency-version }}- | |
| ubuntu-composer-${{ matrix.php }}- | |
| ubuntu-composer- | |
| - name: Install dependencies | |
| run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-progress | |
| - name: Run Unit Tests | |
| run: | | |
| if [ -f "vendor/bin/phpunit" ]; then | |
| vendor/bin/phpunit --configuration phpunit.xml | |
| else | |
| composer test | |
| fi | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: test-results-ubuntu-php${{ matrix.php }}-${{ matrix.dependency-version }} | |
| path: | | |
| test-results.xml | |
| coverage.xml | |
| code-quality: | |
| name: Code Quality | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: dom, curl, libxml, mbstring, zip, json | |
| tools: composer:v2, cs2pr | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT | |
| - name: Cache Composer packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.composer/cache | |
| key: ubuntu-composer-code-quality-${{ hashFiles('**/composer.lock') }} | |
| restore-keys: | | |
| ubuntu-composer-code-quality- | |
| ubuntu-composer- | |
| - name: Install dependencies | |
| run: composer install --prefer-dist --no-interaction --no-progress | |
| - name: Install PHP CS Fixer | |
| run: composer require --dev friendsofphp/php-cs-fixer --no-interaction | |
| - name: Create PHP CS Fixer config | |
| run: | | |
| cat > .php-cs-fixer.php << 'EOF' | |
| <?php | |
| return (new PhpCsFixer\Config()) | |
| ->setRules([ | |
| '@PSR12' => true, | |
| 'array_syntax' => ['syntax' => 'short'], | |
| 'ordered_imports' => ['sort_algorithm' => 'alpha'], | |
| 'no_unused_imports' => true, | |
| 'not_operator_with_successor_space' => true, | |
| 'trailing_comma_in_multiline' => true, | |
| 'phpdoc_scalar' => true, | |
| 'unary_operator_spaces' => true, | |
| 'binary_operator_spaces' => true, | |
| 'blank_line_before_statement' => [ | |
| 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], | |
| ], | |
| 'phpdoc_single_line_var_spacing' => true, | |
| 'phpdoc_var_without_name' => true, | |
| 'method_argument_space' => [ | |
| 'on_multiline' => 'ensure_fully_multiline', | |
| 'keep_multiple_spaces_after_comma' => true, | |
| ], | |
| ]) | |
| ->setFinder( | |
| PhpCsFixer\Finder::create() | |
| ->in(__DIR__ . '/src') | |
| ->in(__DIR__) | |
| ->name('doi2jats.php') | |
| ->exclude('vendor') | |
| ); | |
| EOF | |
| - name: Run PHP CS Fixer (dry run) | |
| run: vendor/bin/php-cs-fixer fix --dry-run --diff --format=checkstyle | cs2pr | |
| - name: Run PHPStan | |
| run: | | |
| if [ -f "vendor/bin/phpstan" ]; then | |
| vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr | |
| else | |
| composer analyse | |
| fi | |
| integration-tests: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: [validate, test] | |
| if: github.event.inputs.run_integration_tests != 'false' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: dom, curl, libxml, mbstring, zip, json | |
| tools: composer:v2 | |
| - name: Install dependencies | |
| run: composer install --prefer-dist --no-interaction --no-progress | |
| - name: Make script executable | |
| run: chmod +x doi2jats.php | |
| - name: Test help command | |
| run: | | |
| php doi2jats.php --help > help_output.txt | |
| if ! grep -q "Citation Generator" help_output.txt; then | |
| echo "❌ Help command test failed" | |
| cat help_output.txt | |
| exit 1 | |
| fi | |
| echo "✅ Help command test passed" | |
| - name: Test single DOI processing | |
| run: | | |
| timeout 30s php doi2jats.php "10.30430/gjae.2023.0350" > single_test.xml || { | |
| echo "❌ Single DOI test timed out or failed" | |
| exit 1 | |
| } | |
| if [ ! -s single_test.xml ]; then | |
| echo "❌ Single DOI test failed: No output generated" | |
| exit 1 | |
| fi | |
| if ! grep -q "element-citation" single_test.xml; then | |
| echo "❌ Single DOI test failed: Invalid XML structure" | |
| cat single_test.xml | |
| exit 1 | |
| fi | |
| echo "✅ Single DOI test passed" | |
| - name: Test multiple DOIs processing | |
| run: | | |
| timeout 60s php doi2jats.php "10.30430/gjae.2023.0350" "10.52825/bis.v1i.42" > multi_test.xml 2>multi_stderr.txt || { | |
| echo "❌ Multi-DOI test timed out or failed" | |
| cat multi_stderr.txt | |
| exit 1 | |
| } | |
| if [ ! -s multi_test.xml ]; then | |
| echo "❌ Multi-DOI test failed: No output generated" | |
| cat multi_stderr.txt | |
| exit 1 | |
| fi | |
| citation_count=$(grep -c "element-citation" multi_test.xml || echo "0") | |
| if [ "$citation_count" -lt 1 ]; then | |
| echo "❌ Multi-DOI test failed: Expected at least 1 citation, got $citation_count" | |
| cat multi_test.xml | |
| exit 1 | |
| fi | |
| echo "✅ Multi-DOI test passed: Generated $citation_count citations" | |
| - name: Test reflist format | |
| run: | | |
| timeout 60s php doi2jats.php -f reflist "10.30430/gjae.2023.0350" "10.52825/bis.v1i.42" > combined_test.xml || { | |
| echo "❌ Combined format test timed out or failed" | |
| exit 1 | |
| } | |
| if ! grep -q "<ref-list>" combined_test.xml; then | |
| echo "❌ Combined format test failed: Missing ref-list element" | |
| cat combined_test.xml | |
| exit 1 | |
| fi | |
| if ! grep -q '<?xml version="1.0" encoding="UTF-8"?>' combined_test.xml; then | |
| echo "❌ Combined format test failed: Missing XML declaration" | |
| exit 1 | |
| fi | |
| echo "✅ Combined format test passed" | |
| - name: Test back format | |
| run: | | |
| timeout 45s php doi2jats.php -f back "10.30430/gjae.2023.0350" > bib_test.xml || { | |
| echo "❌ Bibliography format test timed out or failed" | |
| exit 1 | |
| } | |
| if ! grep -q "<back>" bib_test.xml; then | |
| echo "❌ Bibliography format test failed: Missing back element" | |
| cat bib_test.xml | |
| exit 1 | |
| fi | |
| if ! grep -q "<label>" bib_test.xml; then | |
| echo "❌ Bibliography format test failed: Missing label element" | |
| cat bib_test.xml | |
| exit 1 | |
| fi | |
| echo "✅ Bibliography format test passed" | |
| - name: Install XML validation tools | |
| run: sudo apt-get update && sudo apt-get install -y libxml2-utils | |
| - name: Validate XML output | |
| run: | | |
| # Test each generated XML file | |
| for xml_file in *.xml; do | |
| if [[ "$xml_file" == "multi_test.xml" ]]; then | |
| echo "Skipping $xml_file" | |
| continue | |
| fi | |
| if [ -f "$xml_file" ]; then | |
| echo "Validating $xml_file..." | |
| xmllint --noout "$xml_file" || { | |
| echo "❌ XML validation failed for $xml_file" | |
| cat "$xml_file" | |
| exit 1 | |
| } | |
| fi | |
| done | |
| echo "✅ All XML files are well-formed" | |
| - name: Test performance with timeout | |
| run: | | |
| echo "🚀 Testing performance..." | |
| start_time=$(date +%s) | |
| timeout 120s php doi2jats.php "10.30430/gjae.2023.0350" > perf_test.xml | |
| end_time=$(date +%s) | |
| duration=$((end_time - start_time)) | |
| echo "⏱️ Single DOI processing took ${duration} seconds" | |
| if [ $duration -gt 30 ]; then | |
| echo "⚠️ Warning: Processing took longer than expected (${duration}s > 30s)" | |
| fi | |
| - name: Upload integration test artifacts | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: integration-test-outputs | |
| path: | | |
| *.xml | |
| *.txt | |
| multi_stderr.txt | |
| security: | |
| name: Security Analysis | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: dom, curl, libxml, mbstring, zip, json | |
| tools: composer:v2 | |
| - name: Install dependencies | |
| run: composer install --prefer-dist --no-interaction --no-progress | |
| - name: Run Composer audit | |
| run: composer audit --format=plain | |
| - name: Check for known security vulnerabilities | |
| run: | | |
| composer require --dev roave/security-advisories:dev-latest --no-interaction || { | |
| echo "⚠️ Security advisories check completed with warnings" | |
| } | |
| - name: Scan for hardcoded secrets | |
| run: | | |
| # Simple check for common secret patterns | |
| if grep -r -i "password\|secret\|key\|token" src/ --include="*.php" | grep -v "interface\|function\|class\|comment"; then | |
| echo "⚠️ Potential hardcoded secrets found" | |
| else | |
| echo "✅ No obvious hardcoded secrets detected" | |
| fi | |
| coverage: | |
| name: Code Coverage | |
| runs-on: ubuntu-latest | |
| needs: test | |
| if: github.event_name == 'push' || github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| extensions: dom, curl, libxml, mbstring, zip, json, xdebug | |
| coverage: xdebug | |
| tools: composer:v2 | |
| - name: Install dependencies | |
| run: composer install --prefer-dist --no-interaction --no-progress | |
| - name: Run tests with coverage | |
| run: | | |
| if [ -f "vendor/bin/phpunit" ]; then | |
| vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-html=coverage-html | |
| else | |
| composer test | |
| fi | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v3 | |
| with: | |
| file: ./coverage.xml | |
| flags: unittests | |
| name: codecov-umbrella | |
| fail_ci_if_error: false | |
| - name: Upload coverage artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: | | |
| coverage.xml | |
| coverage-html/ | |
| build-status: | |
| name: Build Status | |
| runs-on: ubuntu-latest | |
| needs: [test, code-quality, integration-tests, security, coverage] | |
| if: always() | |
| steps: | |
| - name: Check all jobs status | |
| run: | | |
| echo "Test job status: ${{ needs.test.result }}" | |
| echo "Code quality job status: ${{ needs.code-quality.result }}" | |
| echo "Integration tests job status: ${{ needs.integration-tests.result }}" | |
| echo "Security job status: ${{ needs.security.result }}" | |
| echo "Coverage job status: ${{ needs.coverage.result }}" | |
| if [ "${{ needs.test.result }}" != "success" ] || | |
| [ "${{ needs.code-quality.result }}" != "success" ] || | |
| [ "${{ needs.integration-tests.result }}" != "success" ] || | |
| [ "${{ needs.security.result }}" != "success" ]; then | |
| echo "❌ One or more critical jobs failed" | |
| exit 1 | |
| fi | |
| echo "✅ All critical jobs passed successfully" |