fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -2,15 +2,24 @@
declare(strict_types=1);
use App\Framework\Config\AppConfig;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationCollection;
use App\Framework\Database\Migration\MigrationDependencyGraph;
use App\Framework\Database\Migration\MigrationRunner;
use App\Framework\Database\Migration\Services\MigrationDatabaseManager;
use App\Framework\Database\Migration\Services\MigrationErrorAnalyzer;
use App\Framework\Database\Migration\Services\MigrationLogger;
use App\Framework\Database\Migration\Services\MigrationPerformanceTracker;
use App\Framework\Database\Migration\Services\MigrationValidator;
use App\Framework\Database\Migration\ValueObjects\MigrationTableConfig;
use App\Framework\Database\Platform\MySqlPlatform;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\SystemClock;
use App\Framework\Id\Ulid\UlidGenerator;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\OperationTracker;
@@ -20,15 +29,49 @@ beforeEach(function () {
$this->clock = new SystemClock();
$this->memoryMonitor = new MemoryMonitor();
$this->operationTracker = new OperationTracker();
$this->ulidGenerator = new class implements UlidGenerator {
public function generate(): string
{
return '01ARZ3NDEKTSV4RRFFQ69G5FAV';
}
};
$this->appConfig = new AppConfig(\App\Framework\Config\EnvironmentType::DEVELOPMENT);
$tableConfig = MigrationTableConfig::withCustomTable('test_migrations');
$dependencyGraph = new MigrationDependencyGraph();
$databaseManager = new MigrationDatabaseManager(
$this->connection,
$this->platform,
$this->clock,
$tableConfig
);
$performanceTracker = new MigrationPerformanceTracker(
$this->operationTracker,
$this->memoryMonitor,
null,
null,
null
);
$migrationLogger = new MigrationLogger(null);
$validator = new MigrationValidator(
$this->connection,
$this->platform,
$this->appConfig
);
$errorAnalyzer = new MigrationErrorAnalyzer();
$this->migrationRunner = new MigrationRunner(
$this->connection,
$this->platform,
$this->clock,
null, // tableConfig
null, // logger
$this->operationTracker,
$this->memoryMonitor
$this->ulidGenerator,
$this->appConfig,
$dependencyGraph,
$databaseManager,
$performanceTracker,
$migrationLogger,
$validator,
$errorAnalyzer
);
});
@@ -38,7 +81,8 @@ test('constructor creates migrations table', function () {
expect($queries)->toHaveCount(1)
->and($queries[0]['type'])->toBe('execute')
->and($queries[0]['sql'])->toContain('CREATE TABLE IF NOT EXISTS test_migrations');
->and($queries[0]['sql'])->toContain('CREATE TABLE')
->and($queries[0]['sql'])->toContain('test_migrations');
});
test('migrate runs pending migrations', function () {
@@ -66,16 +110,12 @@ test('migrate runs pending migrations', function () {
test('migrate skips already applied migrations', function () {
$migration = new TestMigration();
$migrationData = (object) [
'version' => '2024_01_01_000000',
'description' => 'Test Migration',
'instance' => $migration,
];
$migrations = new MigrationCollection($migration);
// Set migration as already applied
$this->connection->setAppliedMigrations([['version' => '2024_01_01_000000']]);
$result = $this->migrationRunner->migrate([$migrationData]);
$result = $this->migrationRunner->migrate($migrations);
expect($result)->toBeEmpty();
expect($migration->wasExecuted())->toBeFalse();
@@ -84,17 +124,13 @@ test('migrate skips already applied migrations', function () {
test('migrate rolls back on failure', function () {
// Mock failing migration
$failingMigration = new FailingTestMigration();
$migrationData = (object) [
'version' => '2024_01_01_000000',
'description' => 'Failing Migration',
'instance' => $failingMigration,
];
$migrations = new MigrationCollection($failingMigration);
// Set no applied migrations initially
$this->connection->setAppliedMigrations([]);
$this->connection->setShouldFail(true);
expect(fn () => $this->migrationRunner->migrate([$migrationData]))
expect(fn () => $this->migrationRunner->migrate($migrations))
->toThrow(DatabaseException::class);
// Verify transaction was used (inTransaction was called)
@@ -103,18 +139,14 @@ test('migrate rolls back on failure', function () {
test('rollback reverts applied migration', function () {
$migration = new TestMigration();
$migrationData = (object) [
'version' => '2024_01_01_000000',
'description' => 'Test Migration',
'instance' => $migration,
];
$migrations = new MigrationCollection($migration);
// Set migration as already applied
$this->connection->setAppliedMigrations([['version' => '2024_01_01_000000']]);
$result = $this->migrationRunner->rollback([$migrationData], 1);
$result = $this->migrationRunner->rollback($migrations, 1);
expect($result)->toContain('2024_01_01_000000');
expect($result)->toHaveCount(1);
expect($migration->wasRolledBack())->toBeTrue();
// Verify the migration record was deleted
@@ -122,31 +154,31 @@ test('rollback reverts applied migration', function () {
$deleteQueries = array_filter(
$queries,
fn ($q) =>
$q['type'] === 'execute' && str_contains($q['sql'], 'DELETE FROM test_migrations')
$q['type'] === 'execute' && str_contains($q['sql'], 'DELETE FROM')
);
expect($deleteQueries)->toHaveCount(1);
});
test('get status returns migration status', function () {
$migration1Data = (object) [
'version' => '2024_01_01_000000',
'description' => 'Applied Migration',
'instance' => new TestMigration(),
];
$migration2Data = (object) [
'version' => '2024_01_02_000000',
'description' => 'Pending Migration',
'instance' => new TestMigration(),
];
$migration1 = new TestMigration();
$migration2 = new class implements Migration {
public function up(ConnectionInterface $connection): void {}
public function down(ConnectionInterface $connection): void {}
public function getDescription(): string { return 'Pending Migration'; }
public function getVersion(): \App\Framework\Database\Migration\MigrationVersion {
return \App\Framework\Database\Migration\MigrationVersion::fromTimestamp('2024_01_02_000000');
}
};
$migrations = new MigrationCollection($migration1, $migration2);
// Set only first migration as applied
$this->connection->setAppliedMigrations([['version' => '2024_01_01_000000']]);
$status = $this->migrationRunner->getStatus([$migration1Data, $migration2Data]);
$status = $this->migrationRunner->getStatus($migrations);
expect($status)->toHaveCount(2)
->and($status[0]['applied'])->toBeTrue()
->and($status[1]['applied'])->toBeFalse();
->and($status[0]->applied)->toBeTrue()
->and($status[1]->applied)->toBeFalse();
});
// Test fixtures
@@ -160,14 +192,14 @@ class TestMigration implements Migration
{
$this->executed = true;
// Simulate migration execution
$connection->execute('CREATE TABLE test_table (id INT)');
$connection->execute(SqlQuery::create('CREATE TABLE test_table (id INT)'));
}
public function down(ConnectionInterface $connection): void
{
$this->rolledBack = true;
// Simulate migration rollback
$connection->execute('DROP TABLE test_table');
$connection->execute(SqlQuery::create('DROP TABLE test_table'));
}
public function getDescription(): string

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Seed\SeedRepository;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\SystemClock;
describe('SeedRepository', function () {
beforeEach(function () {
$this->connection = Mockery::mock(ConnectionInterface::class);
$this->clock = new SystemClock();
$this->repository = new SeedRepository($this->connection, $this->clock);
});
it('checks if seeder has run', function () {
$result = Mockery::mock();
$result->shouldReceive('fetch')
->once()
->andReturn(['count' => 1]);
$this->connection->shouldReceive('query')
->once()
->with(Mockery::on(function (SqlQuery $query) {
return str_contains($query->sql, 'SELECT COUNT(*)');
}))
->andReturn($result);
expect($this->repository->hasRun('TestSeeder'))->toBeTrue();
});
it('returns false when seeder has not run', function () {
$result = Mockery::mock();
$result->shouldReceive('fetch')
->once()
->andReturn(['count' => 0]);
$this->connection->shouldReceive('query')
->once()
->andReturn($result);
expect($this->repository->hasRun('TestSeeder'))->toBeFalse();
});
it('marks seeder as run', function () {
$this->connection->shouldReceive('execute')
->once()
->with(Mockery::on(function (SqlQuery $query) {
return str_contains($query->sql, 'INSERT INTO seeds');
}));
$this->repository->markAsRun('TestSeeder', 'Test description');
});
it('clears all seeds', function () {
$this->connection->shouldReceive('execute')
->once()
->with(Mockery::on(function (SqlQuery $query) {
return str_contains($query->sql, 'DELETE FROM seeds');
}));
$this->repository->clearAll();
});
});

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
use App\Framework\Database\Seed\SeedRepository;
use App\Framework\Database\Seed\SeedRunner;
use App\Framework\Database\Seed\Seeder;
describe('SeedRunner', function () {
beforeEach(function () {
$this->seedRepository = Mockery::mock(SeedRepository::class);
$this->runner = new SeedRunner($this->seedRepository);
});
it('skips seeder if already run', function () {
$seeder = Mockery::mock(Seeder::class);
$seeder->shouldReceive('getName')
->once()
->andReturn('TestSeeder');
$this->seedRepository->shouldReceive('hasRun')
->once()
->with('TestSeeder')
->andReturn(true);
$this->seedRepository->shouldNotReceive('markAsRun');
$this->runner->run($seeder);
});
it('runs seeder if not run yet', function () {
$seeder = Mockery::mock(Seeder::class);
$seeder->shouldReceive('getName')
->once()
->andReturn('TestSeeder');
$seeder->shouldReceive('getDescription')
->once()
->andReturn('Test description');
$seeder->shouldReceive('seed')
->once();
$this->seedRepository->shouldReceive('hasRun')
->once()
->with('TestSeeder')
->andReturn(false);
$this->seedRepository->shouldReceive('markAsRun')
->once()
->with('TestSeeder', 'Test description');
$this->runner->run($seeder);
});
it('throws exception if seeder fails', function () {
$seeder = Mockery::mock(Seeder::class);
$seeder->shouldReceive('getName')
->once()
->andReturn('TestSeeder');
$seeder->shouldReceive('seed')
->once()
->andThrow(new \RuntimeException('Seeder failed'));
$this->seedRepository->shouldReceive('hasRun')
->once()
->with('TestSeeder')
->andReturn(false);
$this->seedRepository->shouldNotReceive('markAsRun');
expect(fn () => $this->runner->run($seeder))
->toThrow(\RuntimeException::class, 'Seeder failed');
});
it('runs multiple seeders', function () {
$seeder1 = Mockery::mock(Seeder::class);
$seeder1->shouldReceive('getName')->andReturn('Seeder1');
$seeder1->shouldReceive('getDescription')->andReturn('Description 1');
$seeder1->shouldReceive('seed');
$seeder2 = Mockery::mock(Seeder::class);
$seeder2->shouldReceive('getName')->andReturn('Seeder2');
$seeder2->shouldReceive('getDescription')->andReturn('Description 2');
$seeder2->shouldReceive('seed');
$this->seedRepository->shouldReceive('hasRun')
->with('Seeder1')
->andReturn(false);
$this->seedRepository->shouldReceive('hasRun')
->with('Seeder2')
->andReturn(false);
$this->seedRepository->shouldReceive('markAsRun')
->with('Seeder1', 'Description 1');
$this->seedRepository->shouldReceive('markAsRun')
->with('Seeder2', 'Description 2');
$this->runner->runAll([$seeder1, $seeder2]);
});
});