feat: Infrastructure multi-tenant avec isolation par sous-domaine
Une application SaaS éducative nécessite une séparation stricte des données entre établissements scolaires. L'architecture multi-tenant par sous-domaine (ecole-alpha.classeo.local) permet cette isolation tout en utilisant une base de code unique. Le choix d'une résolution basée sur les sous-domaines plutôt que sur des headers ou tokens facilite le routage au niveau infrastructure (reverse proxy) et offre une UX plus naturelle où chaque école accède à "son" URL dédiée.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Shared\Infrastructure\Tenant;
|
||||
|
||||
use App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantNotFoundException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(InMemoryTenantRegistry::class)]
|
||||
final class InMemoryTenantRegistryTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itReturnsConfigByTenantId(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
|
||||
$config = new TenantConfig(
|
||||
tenantId: $tenantId,
|
||||
subdomain: 'ecole-alpha',
|
||||
databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_alpha',
|
||||
);
|
||||
|
||||
$registry = new InMemoryTenantRegistry([$config]);
|
||||
|
||||
$result = $registry->getConfig($tenantId);
|
||||
|
||||
self::assertSame($config, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itReturnsConfigBySubdomain(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
|
||||
$config = new TenantConfig(
|
||||
tenantId: $tenantId,
|
||||
subdomain: 'ecole-alpha',
|
||||
databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_alpha',
|
||||
);
|
||||
|
||||
$registry = new InMemoryTenantRegistry([$config]);
|
||||
|
||||
$result = $registry->getBySubdomain('ecole-alpha');
|
||||
|
||||
self::assertSame($config, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsExceptionForUnknownTenantId(): void
|
||||
{
|
||||
$registry = new InMemoryTenantRegistry([]);
|
||||
|
||||
$this->expectException(TenantNotFoundException::class);
|
||||
|
||||
$registry->getConfig(TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsExceptionForUnknownSubdomain(): void
|
||||
{
|
||||
$registry = new InMemoryTenantRegistry([]);
|
||||
|
||||
$this->expectException(TenantNotFoundException::class);
|
||||
|
||||
$registry->getBySubdomain('ecole-inexistant');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itChecksIfTenantExists(): void
|
||||
{
|
||||
$config = new TenantConfig(
|
||||
tenantId: TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890'),
|
||||
subdomain: 'ecole-alpha',
|
||||
databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_alpha',
|
||||
);
|
||||
|
||||
$registry = new InMemoryTenantRegistry([$config]);
|
||||
|
||||
self::assertTrue($registry->exists('ecole-alpha'));
|
||||
self::assertFalse($registry->exists('ecole-inexistant'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itSupportsMultipleTenants(): void
|
||||
{
|
||||
$configAlpha = new TenantConfig(
|
||||
tenantId: TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890'),
|
||||
subdomain: 'ecole-alpha',
|
||||
databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_alpha',
|
||||
);
|
||||
$configBeta = new TenantConfig(
|
||||
tenantId: TenantId::fromString('b2c3d4e5-f6a7-8901-bcde-f12345678901'),
|
||||
subdomain: 'ecole-beta',
|
||||
databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_beta',
|
||||
);
|
||||
|
||||
$registry = new InMemoryTenantRegistry([$configAlpha, $configBeta]);
|
||||
|
||||
self::assertSame($configAlpha, $registry->getBySubdomain('ecole-alpha'));
|
||||
self::assertSame($configBeta, $registry->getBySubdomain('ecole-beta'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user