ci: initialize Mongo IntegrationV2 schema via service container #138
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: Security Guard Tests | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| env: | |
| GH_START_ISO: ${{ github.event.head_commit.timestamp }} | |
| APP_ENV: testing | |
| # Redis | |
| REDIS_HOST: 127.0.0.1 | |
| REDIS_PORT: 6379 | |
| REDIS_PASS: "" | |
| REDIS_MAIN_DSN: "redis://127.0.0.1:6379" | |
| # Mongo | |
| MONGO_HOST: 127.0.0.1 | |
| MONGO_PORT: 27017 | |
| MONGO_USER: "" | |
| MONGO_PASS: "" | |
| MONGO_DB: security_guard | |
| MONGO_AUTH_SOURCE: admin | |
| MONGO_MAIN_DSN: "mongodb://127.0.0.1:27017/security_guard" | |
| # MySQL | |
| MYSQL_HOST: 127.0.0.1 | |
| MYSQL_PORT: 3306 | |
| MYSQL_USER: root | |
| MYSQL_PASS: root | |
| MYSQL_DB: security_guard_dev | |
| MYSQL_DRIVER: dbal | |
| MYSQL_DSN: "mysql:host=127.0.0.1;dbname=security_guard_dev;charset=utf8mb4" | |
| MYSQL_MAIN_DSN: "mysql:host=127.0.0.1;dbname=security_guard_main;charset=utf8mb4" | |
| MYSQL_MAIN_USER: root | |
| MYSQL_MAIN_PASS: root | |
| MYSQL_DEV_DSN: "mysql:host=127.0.0.1;dbname=security_guard_dev;charset=utf8mb4" | |
| MYSQL_LOGS_DSN: "mysql://root:root@127.0.0.1:3306/security_guard_logs" | |
| MYSQL_LOGS_DRIVER: dbal | |
| # Registry (phase 13) | |
| DB_REGISTRY_PATH: tests/fixtures/database_registry.json | |
| services: | |
| redis: | |
| image: redis:7 | |
| ports: [ "6379:6379" ] | |
| mongo: | |
| image: mongo:7 | |
| ports: [ "27017:27017" ] | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: security_guard_dev | |
| MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password | |
| ports: | |
| - "3306:3306" | |
| steps: | |
| # --------------------------------------------- | |
| # 1) Checkout | |
| # --------------------------------------------- | |
| - name: Checkout full repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # --------------------------------------------- | |
| # 2) PHP Setup | |
| # --------------------------------------------- | |
| - uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: "8.4" | |
| coverage: pcov | |
| extensions: mbstring, intl, pdo, pdo_mysql, redis, mongodb | |
| tools: composer | |
| # --------------------------------------------- | |
| # 2.5) Composer cache + GitHub token (avoid 429) | |
| # --------------------------------------------- | |
| - name: Cache Composer | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.composer/cache | |
| ~/.cache/composer | |
| key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Configure Composer GitHub token | |
| run: composer config -g github-oauth.github.com "${{ github.token }}" | |
| # --------------------------------------------- | |
| # 3) Install Dependencies | |
| # --------------------------------------------- | |
| - name: Install dependencies | |
| run: composer install --no-interaction --prefer-dist --no-progress | |
| # --------------------------------------------- | |
| # 4) Regenerate Autoload (CRITICAL) | |
| # --------------------------------------------- | |
| - name: Rebuild Autoload | |
| run: composer dump-autoload --optimize | |
| # --------------------------------------------- | |
| # 4.5) Install jq (required for Telegram JSON formatting) | |
| # --------------------------------------------- | |
| - name: Install jq | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| # --------------------------------------------- | |
| # 5) Debug Services (optional) | |
| # --------------------------------------------- | |
| - name: 🔍 Debug Connections | |
| run: | | |
| echo "Environment: $APP_ENV" | |
| echo "Checking Redis..." | |
| redis-cli -h $REDIS_HOST ping || echo "Redis not responding" | |
| echo "Checking MySQL..." | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS -e "SELECT VERSION();" || echo "MySQL not responding" | |
| echo "Checking Mongo..." | |
| mongo --host $MONGO_HOST --port $MONGO_PORT --eval "db.runCommand({ ping: 1 })" || echo "Mongo not responding" | |
| continue-on-error: true | |
| # --------------------------------------------- | |
| # 6) Wait for MySQL | |
| # --------------------------------------------- | |
| - name: Wait for MySQL | |
| run: | | |
| echo "Waiting for MySQL..." | |
| for i in {1..25}; do | |
| if mysqladmin ping -h $MYSQL_HOST --silent; then | |
| echo "MySQL ready!" | |
| break | |
| fi | |
| echo "Still waiting..." | |
| sleep 2 | |
| done | |
| # --------------------------------------------- | |
| # 7) Create Required Databases | |
| # --------------------------------------------- | |
| - name: Init MySQL databases | |
| run: | | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS \ | |
| -e "CREATE DATABASE IF NOT EXISTS security_guard_main;" | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS \ | |
| -e "CREATE DATABASE IF NOT EXISTS security_guard_logs;" | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS \ | |
| -e "CREATE DATABASE IF NOT EXISTS security_guard_dev;" | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS \ | |
| -e "CREATE DATABASE IF NOT EXISTS security_guard;" | |
| # --------------------------------------------- | |
| # 8) Run PHPStan (non-blocking) | |
| # --------------------------------------------- | |
| - name: Debug PHPStan version and command | |
| run: | | |
| which phpstan || true | |
| php -v | |
| composer show phpstan/phpstan | cat | |
| composer run-script analyse -- --version || true | |
| - name: Run PHPStan | |
| id: phpstan | |
| run: | | |
| set +e | |
| vendor/bin/phpstan analyse src tests --level=max > phpstan.log | |
| EXIT_CODE=$? | |
| ERRORS=$(grep -Eo "Found [0-9]+ errors" phpstan.log | grep -Eo "[0-9]+") | |
| if [ -z "$ERRORS" ]; then ERRORS=0; fi | |
| echo "phpstan_exit=$EXIT_CODE" >> $GITHUB_OUTPUT | |
| echo "phpstan_errors=$ERRORS" >> $GITHUB_OUTPUT | |
| echo "PHPStan completed. Errors: $ERRORS" | |
| - name: Show PHPStan output | |
| if: always() | |
| run: | | |
| echo "---- PHPStan log ----" | |
| cat phpstan.log | |
| # --------------------------------------------- | |
| # 9) Run PHPUnit | |
| # --------------------------------------------- | |
| - name: Install bc | |
| run: sudo apt-get install -y bc | |
| # - name: 🧪 Run PHPUnit with Coverage | |
| # id: tests | |
| # run: | | |
| # php -d pcov.enabled=1 \ | |
| # -d pcov.directory=./src \ | |
| # -d pcov.exclude="~(tests|vendor)~" \ | |
| # vendor/bin/phpunit \ | |
| # --configuration phpunit.xml.dist \ | |
| # --coverage-clover coverage.xml \ | |
| # --coverage-html coverage \ | |
| # | tee phpunit.log | |
| # --------------------------------------------- | |
| # 9-A) Run Resolver + Unit tests (NO IntegrationV2) | |
| # --------------------------------------------- | |
| - name: 🧪 Run Unit & Resolver Tests | |
| run: | | |
| vendor/bin/phpunit \ | |
| --configuration phpunit.xml.dist \ | |
| --testsuite unit,resolver \ | |
| | tee phpunit-unit.log | |
| # --------------------------------------------- | |
| # 9-B) Prepare IntegrationV2 Infrastructure | |
| # --------------------------------------------- | |
| - name: Init MySQL IntegrationV2 Schema | |
| run: | | |
| mysql -h $MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS security_guard_dev < resources/sql/security_guard_schema.sql | |
| # - name: Init Mongo IntegrationV2 Collections | |
| # run: | | |
| # mongosh <<EOF | |
| # use security_guard | |
| # db.createCollection("sg_attempts") | |
| # db.createCollection("sg_blocks") | |
| # EOF | |
| - name: Init Mongo IntegrationV2 Schema | |
| run: | | |
| docker exec $(docker ps -q --filter "ancestor=mongo:7") \ | |
| mongosh "mongodb://127.0.0.1:27017/security_guard" --eval ' | |
| db.createCollection("sg_attempts"); | |
| db.createCollection("sg_blocks"); | |
| ' | |
| # --------------------------------------------- | |
| # 9-C) Run IntegrationV2 Tests ONLY | |
| # --------------------------------------------- | |
| - name: 🧪 Run IntegrationV2 Tests | |
| run: | | |
| vendor/bin/phpunit \ | |
| --configuration phpunit.xml.dist \ | |
| --testsuite integrationv2 \ | |
| --coverage-clover coverage.xml \ | |
| --coverage-html coverage \ | |
| | tee phpunit-integration.log | |
| - name: Extract Coverage % (Clover) | |
| id: coverage | |
| run: | | |
| if [ ! -f coverage.xml ]; then | |
| echo "coverage=0" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| COV=$(php -r ' | |
| $xml=@simplexml_load_file("coverage.xml"); | |
| if(!$xml){ echo "0"; exit; } | |
| // Prefer project/metrics | |
| $m = $xml->project->metrics ?? null; | |
| // Fallback: sum all metrics nodes | |
| if(!$m){ | |
| $statements=0; $covered=0; | |
| foreach($xml->xpath("//metrics") ?: [] as $mx){ | |
| $statements += (int)$mx["statements"]; | |
| $covered += (int)$mx["coveredstatements"]; | |
| } | |
| echo $statements>0 ? (int)round(($covered/$statements)*100) : 0; | |
| exit; | |
| } | |
| $statements=(int)$m["statements"]; | |
| $covered=(int)$m["coveredstatements"]; | |
| echo $statements>0 ? (int)round(($covered/$statements)*100) : 0; | |
| ') | |
| echo "Coverage: ${COV}%" | |
| echo "coverage=${COV}" >> $GITHUB_OUTPUT | |
| - name: Generate Coverage Badge JSON | |
| run: | | |
| echo "{ | |
| \"schemaVersion\": 1, | |
| \"label\": \"coverage\", | |
| \"message\": \"${{ steps.coverage.outputs.coverage }}%\", | |
| \"color\": \"9C27B0\" | |
| }" > coverage-badge.json | |
| - name: Stash CI artifacts (before switching branches) | |
| if: github.event_name == 'push' | |
| run: | | |
| cp coverage-badge.json /tmp/coverage-badge.json | |
| cp phpunit.log /tmp/phpunit.log || true | |
| - name: Upload Coverage Badge to badges branch | |
| if: github.event_name == 'push' | |
| run: | | |
| git config user.name "github-actions" | |
| git config user.email "actions@github.com" | |
| git fetch origin | |
| if git ls-remote --exit-code origin badges; then | |
| echo "Badges branch exists. Checking out..." | |
| git checkout badges | |
| else | |
| echo "Badges branch does NOT exist. Creating it..." | |
| git checkout --orphan badges | |
| fi | |
| git reset --hard | |
| git clean -fdx | |
| cp /tmp/coverage-badge.json coverage.json | |
| git add -f coverage.json | |
| if git diff --cached --quiet; then | |
| echo "No changes" | |
| else | |
| git commit -m "Update Security Guard coverage to ${{ steps.coverage.outputs.coverage }}%" | |
| fi | |
| git push origin badges --force | |
| # --------------------------------------------- | |
| # 🔥 10) Summary (always) | |
| # --------------------------------------------- | |
| - name: 🧾 Summary Report | |
| if: always() | |
| run: | | |
| echo "### 🧾 Security Guard Test Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "📦 Environment: $APP_ENV" >> $GITHUB_STEP_SUMMARY | |
| echo "🧱 Redis: $REDIS_HOST" >> $GITHUB_STEP_SUMMARY | |
| echo "🗄️ MySQL: $MYSQL_HOST" >> $GITHUB_STEP_SUMMARY | |
| echo "📁 Mongo: $MONGO_HOST" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| tail -n 20 /tmp/phpunit.log >> $GITHUB_STEP_SUMMARY || tail -n 20 phpunit.log >> $GITHUB_STEP_SUMMARY || echo "(phpunit.log missing)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if grep -q "FAILURES!" /tmp/phpunit.log 2>/dev/null || grep -q "FAILURES!" phpunit.log 2>/dev/null; then | |
| echo "❌ Some tests failed." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "✅ All tests passed successfully." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| PHPSTAN_ERR=${{ steps.phpstan.outputs.phpstan_errors }} | |
| if [ "$PHPSTAN_ERR" -eq 0 ]; then | |
| echo "🧹 PHPStan: Passed (0 errors)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ PHPStan: $PHPSTAN_ERR errors" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "📊 Coverage: ${{ steps.coverage.outputs.coverage }}%" >> $GITHUB_STEP_SUMMARY | |
| # --------------------------------------------- | |
| # 11) Telegram Notification | |
| # --------------------------------------------- | |
| - name: 📲 Notify Telegram | |
| if: always() | |
| env: | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_CI_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CI_CHAT_ID }} | |
| run: | | |
| START_TS=$(date -u -d "$GH_START_ISO" +%s) | |
| END_TS=$(date +%s) | |
| START_TIME=$(date -u -d "@$START_TS" +"%H:%M:%S UTC") | |
| END_TIME=$(date -u +"%H:%M:%S UTC") | |
| DURATION=$((END_TS - START_TS)) | |
| if [ $DURATION -lt 60 ]; then | |
| DURATION_STR="${DURATION}s" | |
| else | |
| DURATION_STR="$(($DURATION / 60))m $(($DURATION % 60))s" | |
| fi | |
| STATUS="✅ Security Guard Tests passed successfully!" | |
| COLOR="🟢" | |
| HEADER="Security Guard CI Report (${GITHUB_REPOSITORY})" | |
| if grep -q "FAILURES!" phpunit.log || [ "${{ job.status }}" != "success" ]; then | |
| STATUS="❌ Test or PHPStan failure!" | |
| COLOR="🔴" | |
| HEADER="Security Guard CI Alert ${GITHUB_REPOSITORY}" | |
| fi | |
| PHPSTAN_ERR=${{ steps.phpstan.outputs.phpstan_errors }} | |
| if [ "$PHPSTAN_ERR" -eq 0 ]; then | |
| PHPSTAN_SUMMARY="🧹 <b>PHPStan:</b> Passed (0 errors)" | |
| else | |
| PHPSTAN_SUMMARY="🧹 <b>PHPStan:</b> ${PHPSTAN_ERR} errors" | |
| fi | |
| COVERAGE_SUMMARY="📊 <b>Coverage:</b> ${{ steps.coverage.outputs.coverage }}%" | |
| PROJECT="${GITHUB_REPOSITORY}" | |
| BRANCH="$GITHUB_REF_NAME" | |
| ACTOR="$GITHUB_ACTOR" | |
| URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| MESSAGE="📢 <b>${HEADER}</b> | |
| ${COLOR} ${STATUS} | |
| ${PHPSTAN_SUMMARY} | |
| ${COVERAGE_SUMMARY} | |
| 📦 <b>Project:</b> ${PROJECT} | |
| 🧱 <b>Branch:</b> ${BRANCH} | |
| 👨💻 <b>By:</b> ${ACTOR} | |
| ⏱ <b>Start:</b> ${START_TIME} | |
| 🕒 <b>End:</b> ${END_TIME} | |
| ⏳ <b>Duration:</b> ${DURATION_STR} | |
| 🔗 <a href='${URL}'>View CI Logs</a> | |
| " | |
| curl -s -X POST \ | |
| "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n --arg chat_id "$TELEGRAM_CHAT_ID" --arg text "$MESSAGE" --arg pm "HTML" '{chat_id: $chat_id, text: $text, parse_mode: $pm}')" |