diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index a59d91f..09ef1d7 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -1,7 +1,6 @@
name: backend-ci-cd
-# CI/CD: только push git-тега (ручное тегирование на ветке prod|test|stage).
-# Push в ветки и feature-ветки pipeline не запускают.
+# CI/CD: только push git-тега. Pre-deploy tests на test|stage; prod — без тестов (ручной аппрув релиза).
on:
push:
@@ -14,25 +13,6 @@ env:
IMAGE_DEPLOY: git.sova.local/sova/backend
jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: '8.4'
- extensions: pdo_pgsql, redis, intl, zip, gd
- - name: Prepare CI environment
- run: |
- cp .env.ci .env.local
- mkdir -p config/jwt var
- openssl genrsa -out config/jwt/private.pem 2048
- openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
- - run: composer install --prefer-dist --no-interaction
- - run: composer phpunit || true
- - run: composer audit || true
-
parse-tag:
runs-on: ubuntu-latest
outputs:
@@ -48,8 +28,67 @@ jobs:
echo "env=$(echo "$TAG" | sed -E 's/backend-v([0-9.]+)-([a-z]+)/\2/')" >> "$GITHUB_OUTPUT"
echo "version=$(echo "$TAG" | sed -E 's/backend-v([0-9.]+).*/\1/')" >> "$GITHUB_OUTPUT"
+ test:
+ needs: [parse-tag]
+ if: needs.parse-tag.outputs.env != 'prod'
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:16-alpine
+ env:
+ POSTGRES_USER: ci
+ POSTGRES_PASSWORD: ci
+ POSTGRES_DB: ci
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd "pg_isready -U ci -d ci"
+ --health-interval 5s
+ --health-timeout 5s
+ --health-retries 10
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ extensions: pdo_pgsql, redis, intl, zip, gd
+ coverage: xdebug
+ - name: Prepare CI environment
+ run: |
+ cp .env.ci .env.local
+ mkdir -p config/jwt var/coverage
+ openssl genrsa -out config/jwt/private.pem 2048
+ openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
+ - run: composer install --prefer-dist --no-interaction
+ - name: Unit tests + coverage
+ run: composer phpunit:coverage
+ - name: Integration tests (test contour only)
+ if: needs.parse-tag.outputs.env == 'test'
+ continue-on-error: true
+ run: |
+ php bin/console doctrine:database:create --if-not-exists --env=test || true
+ php bin/console doctrine:migrations:migrate --no-interaction --env=test || true
+ composer phpunit:integration
+ - name: Upload coverage
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: backend-coverage-${{ needs.parse-tag.outputs.full_tag }}
+ path: var/coverage/
+ retention-days: 14
+ - run: composer audit || true
+
+ test-prod-skip:
+ needs: [parse-tag]
+ if: needs.parse-tag.outputs.env == 'prod'
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "Prod tag — automated tests skipped (no tests on production contour)."
+
build-and-push:
- needs: [test, parse-tag]
+ needs: [parse-tag, test, test-prod-skip]
+ if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.test-prod-skip.result == 'success' || needs.test-prod-skip.result == 'skipped')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/composer.json b/composer.json
index cd80aa0..bca9d1b 100644
--- a/composer.json
+++ b/composer.json
@@ -101,6 +101,9 @@
"@php bin/console cache:clear"
],
"phpunit": "phpunit",
+ "phpunit:unit": "phpunit --testsuite=unit",
+ "phpunit:integration": "phpunit --testsuite=integration",
+ "phpunit:coverage": "phpunit --testsuite=unit --coverage-clover var/coverage/clover.xml --coverage-text",
"generate-swagger": "php ./vendor/bin/openapi --output ./public/swagger.json ./src",
"auto-scripts": {
"cache:clear": "symfony-cmd",
diff --git a/config/services.yaml b/config/services.yaml
index 81c413c..1b3739c 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -30,6 +30,10 @@ services:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
+
+ App\Log\TestTraceProcessor:
+ tags:
+ - { name: monolog.processor }
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
index 0b31da2..641759d 100644
--- a/phpunit.dist.xml
+++ b/phpunit.dist.xml
@@ -18,8 +18,13 @@
-
- tests
+
+ tests/Unit
+ tests/Service
+
+
+ tests/Controller
+ tests/Integration
@@ -27,8 +32,19 @@
src
+
+ src/Entity
+
+
+
+
+
+
+
+
+
diff --git a/src/Log/TestTraceProcessor.php b/src/Log/TestTraceProcessor.php
new file mode 100644
index 0000000..8209972
--- /dev/null
+++ b/src/Log/TestTraceProcessor.php
@@ -0,0 +1,33 @@
+requestStack->getCurrentRequest();
+ if ($request === null || !$request->headers->has('X-Is-Auto-Test')) {
+ return $record;
+ }
+
+ return $record->with(extra: array_merge($record->extra, [
+ 'is_test' => true,
+ 'test_trace_id' => $request->headers->get('X-Test-Trace-Id', 'unknown'),
+ ]));
+ }
+}
diff --git a/tests/Unit/Log/TestTraceProcessorTest.php b/tests/Unit/Log/TestTraceProcessorTest.php
new file mode 100644
index 0000000..de3e323
--- /dev/null
+++ b/tests/Unit/Log/TestTraceProcessorTest.php
@@ -0,0 +1,44 @@
+headers->set('X-Is-Auto-Test', 'true');
+ $request->headers->set('X-Test-Trace-Id', 'run-42');
+
+ $stack = new RequestStack();
+ $stack->push($request);
+
+ $processor = new TestTraceProcessor($stack);
+ $record = new LogRecord(new \DateTimeImmutable(), 'app', Level::Info, 'msg');
+
+ $out = $processor($record);
+
+ self::assertTrue($out->extra['is_test']);
+ self::assertSame('run-42', $out->extra['test_trace_id']);
+ }
+
+ public function testLeavesRecordUntouchedWithoutHeader(): void
+ {
+ $stack = new RequestStack();
+ $processor = new TestTraceProcessor($stack);
+ $record = new LogRecord(new \DateTimeImmutable(), 'app', Level::Info, 'msg', extra: ['foo' => 1]);
+
+ $out = $processor($record);
+
+ self::assertSame(['foo' => 1], $out->extra);
+ }
+}