Wire Doctrine's default connection to the tenant database resolved from the subdomain for HTTP requests and tenant-scoped Messenger messages while keeping master-only services on the master connection. This removes the production inconsistency where demo data, migrations and tenant commands used the tenant database but the web runtime still read from master.
110 lines
4.1 KiB
PHP
110 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Shared\Infrastructure\Persistence\Doctrine;
|
|
|
|
use App\Shared\Infrastructure\Persistence\Doctrine\TenantAwareConnection;
|
|
use Doctrine\DBAL\Driver;
|
|
use Doctrine\DBAL\Driver\API\ExceptionConverter;
|
|
use Doctrine\DBAL\Driver\Connection as DriverConnection;
|
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use RuntimeException;
|
|
|
|
#[CoversClass(TenantAwareConnection::class)]
|
|
final class TenantAwareConnectionTest extends TestCase
|
|
{
|
|
#[Test]
|
|
public function itSwitchesBetweenDefaultAndTenantConnectionParams(): void
|
|
{
|
|
$driverConnection = $this->createMock(DriverConnection::class);
|
|
$driverConnection->method('getServerVersion')->willReturn('18.1');
|
|
|
|
$capturedParams = [];
|
|
$driver = $this->createDriver($driverConnection, $capturedParams);
|
|
|
|
$connection = new TenantAwareConnection(
|
|
[
|
|
'driver' => 'pdo_pgsql',
|
|
'host' => 'db',
|
|
'port' => 5432,
|
|
'user' => 'master',
|
|
'password' => 'secret',
|
|
'dbname' => 'classeo_master',
|
|
'serverVersion' => '18',
|
|
],
|
|
$driver,
|
|
);
|
|
|
|
self::assertSame('18.1', $connection->getServerVersion());
|
|
self::assertSame('classeo_master', $capturedParams[0]['dbname']);
|
|
self::assertNull($connection->currentDatabaseUrl());
|
|
|
|
$tenantDatabaseUrl = 'postgresql://tenant:tenantpass@tenant-db:5432/classeo_tenant_alpha?serverVersion=18&charset=utf8';
|
|
$connection->useTenantDatabase($tenantDatabaseUrl);
|
|
|
|
self::assertSame('18.1', $connection->getServerVersion());
|
|
self::assertSame('classeo_tenant_alpha', $capturedParams[1]['dbname']);
|
|
self::assertSame('tenant-db', $capturedParams[1]['host']);
|
|
self::assertSame('tenant', $capturedParams[1]['user']);
|
|
self::assertSame($tenantDatabaseUrl, $connection->currentDatabaseUrl());
|
|
|
|
$connection->useDefaultDatabase();
|
|
|
|
self::assertSame('18.1', $connection->getServerVersion());
|
|
self::assertSame('classeo_master', $capturedParams[2]['dbname']);
|
|
self::assertNull($connection->currentDatabaseUrl());
|
|
}
|
|
|
|
#[Test]
|
|
public function itRejectsDatabaseSwitchesWhileATransactionIsOpen(): void
|
|
{
|
|
$driverConnection = $this->createMock(DriverConnection::class);
|
|
$driverConnection->method('getServerVersion')->willReturn('18.1');
|
|
|
|
$capturedParams = [];
|
|
$driver = $this->createDriver($driverConnection, $capturedParams);
|
|
$connection = new TenantAwareConnection(
|
|
[
|
|
'driver' => 'pdo_pgsql',
|
|
'host' => 'db',
|
|
'port' => 5432,
|
|
'user' => 'master',
|
|
'password' => 'secret',
|
|
'dbname' => 'classeo_master',
|
|
'serverVersion' => '18',
|
|
],
|
|
$driver,
|
|
);
|
|
|
|
$connection->beginTransaction();
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('Cannot switch database while a transaction is active.');
|
|
|
|
$connection->useTenantDatabase('postgresql://tenant:tenantpass@tenant-db:5432/classeo_tenant_alpha?serverVersion=18&charset=utf8');
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $capturedParams
|
|
*/
|
|
private function createDriver(DriverConnection $driverConnection, array &$capturedParams): Driver
|
|
{
|
|
$driver = $this->createMock(Driver::class);
|
|
$driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));
|
|
$driver->method('getExceptionConverter')->willReturn($this->createMock(ExceptionConverter::class));
|
|
$driver->method('connect')->willReturnCallback(
|
|
static function (array $params) use (&$capturedParams, $driverConnection): DriverConnection {
|
|
$capturedParams[] = $params;
|
|
|
|
return $driverConnection;
|
|
},
|
|
);
|
|
|
|
return $driver;
|
|
}
|
|
}
|