Skip to content

ci: initialize Mongo IntegrationV2 schema via service container #138

ci: initialize Mongo IntegrationV2 schema via service container

ci: initialize Mongo IntegrationV2 schema via service container #138

Workflow file for this run

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}')"