feat: separate tenant domain and base path, update related models, requests, and tests
This commit is contained in:
parent
8e26611097
commit
a8973f6171
|
|
@ -155,16 +155,19 @@ class GoogleAuthService
|
|||
$parts = parse_url($returnUrl);
|
||||
if (! is_array($parts)
|
||||
|| ! isset($parts['scheme'], $parts['host'])
|
||||
|| isset($parts['user'], $parts['pass'], $parts['query'], $parts['fragment'])
|
||||
|| ($parts['path'] ?? '') !== '') {
|
||||
|| isset($parts['user'], $parts['pass'], $parts['query'], $parts['fragment'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scheme = strtolower($parts['scheme']);
|
||||
$host = TenantDomainNormalizer::normalize($parts['host']);
|
||||
$tenantDomain = TenantDomainNormalizer::normalize($tenant->dominio);
|
||||
$returnPath = TenantDomainNormalizer::normalizePath($parts['path'] ?? '/');
|
||||
|
||||
if ($host === null || $tenantDomain === null || $host !== $tenantDomain) {
|
||||
if ($host === null
|
||||
|| $tenantDomain === null
|
||||
|| $host !== $tenantDomain
|
||||
|| $returnPath !== $tenant->base_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@ class TenantBootstrapService
|
|||
|
||||
public function get(string $domain, string $path = '/'): Tenant
|
||||
{
|
||||
$candidateKeys = TenantDomainNormalizer::tenantKeyCandidates($domain, $path);
|
||||
$tenantsByDomain = Tenant::query()
|
||||
->whereIn('dominio', $candidateKeys)
|
||||
$candidateBasePaths = TenantDomainNormalizer::basePathCandidates($path);
|
||||
$tenantsByBasePath = Tenant::query()
|
||||
->where('dominio', $domain)
|
||||
->whereIn('base_path', $candidateBasePaths)
|
||||
->get()
|
||||
->keyBy('dominio');
|
||||
->keyBy('base_path');
|
||||
|
||||
$tenant = collect($candidateKeys)
|
||||
->map(fn (string $candidate): ?Tenant => $tenantsByDomain->get($candidate))
|
||||
$tenant = collect($candidateBasePaths)
|
||||
->map(fn (string $candidate): ?Tenant => $tenantsByBasePath->get($candidate))
|
||||
->first(fn (?Tenant $candidate): bool => $candidate !== null);
|
||||
|
||||
if (! $tenant instanceof Tenant) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class ClientResource extends JsonResource
|
|||
'codigo' => $tenant->codigo,
|
||||
'nombre' => $tenant->nombre,
|
||||
'dominio' => $tenant->dominio,
|
||||
'base_path' => $tenant->base_path,
|
||||
])),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ class NotificationMailService
|
|||
PasswordResetRequested::CHANNEL_SCANNER => $tenant->websiteType?->scanner_domain,
|
||||
default => $tenant->dominio,
|
||||
};
|
||||
$recoveryBasePath = $channel === PasswordResetRequested::CHANNEL_STOREFRONT
|
||||
&& $tenant->base_path !== '/'
|
||||
? $tenant->base_path
|
||||
: '';
|
||||
$recoveryQuery = ['email' => $attempt->user->email];
|
||||
if (
|
||||
$channel === PasswordResetRequested::CHANNEL_SCANNER
|
||||
|
|
@ -70,7 +74,7 @@ class NotificationMailService
|
|||
}
|
||||
$recoveryUrl = $recoveryDomain === null
|
||||
? null
|
||||
: 'https://'.$recoveryDomain.'/recuperar-contrasena/codigo?'.http_build_query($recoveryQuery);
|
||||
: 'https://'.$recoveryDomain.$recoveryBasePath.'/recuperar-contrasena/codigo?'.http_build_query($recoveryQuery);
|
||||
|
||||
$this->mailService
|
||||
->forTenant($tenantCode)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use Illuminate\Support\Facades\Schema;
|
|||
'codigo',
|
||||
'nombre',
|
||||
'dominio',
|
||||
'base_path',
|
||||
'site_title',
|
||||
'primary_color',
|
||||
'secondary_color',
|
||||
|
|
@ -55,6 +56,7 @@ class Tenant extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $attributes = [
|
||||
'base_path' => '/',
|
||||
'search_product_layout' => ProductLayout::ColumnWithImage->value,
|
||||
'search_group_layout' => GroupLayout::Paginated->value,
|
||||
'search_items_per_page' => 12,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class StoreTenantRequest extends FormRequest
|
|||
{
|
||||
protected bool $hasInvalidDomain = false;
|
||||
|
||||
protected bool $hasInvalidBasePath = false;
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
|
|
@ -23,13 +25,21 @@ class StoreTenantRequest extends FormRequest
|
|||
protected function prepareForValidation(): void
|
||||
{
|
||||
$rawDomain = $this->input('dominio');
|
||||
$normalizedDomain = TenantDomainNormalizer::normalizeTenantKey($rawDomain);
|
||||
$hasExplicitBasePath = $this->has('base_path');
|
||||
$rawBasePath = $hasExplicitBasePath
|
||||
? $this->input('base_path')
|
||||
: TenantDomainNormalizer::pathFromDomain($rawDomain);
|
||||
$normalizedDomain = TenantDomainNormalizer::normalize($rawDomain);
|
||||
$normalizedBasePath = TenantDomainNormalizer::normalizePath($rawBasePath);
|
||||
|
||||
$this->hasInvalidDomain = TenantDomainNormalizer::hasValue($rawDomain)
|
||||
&& $normalizedDomain === null;
|
||||
$this->hasInvalidBasePath = ($hasExplicitBasePath && ! is_string($rawBasePath))
|
||||
|| $normalizedBasePath === null;
|
||||
|
||||
$this->merge([
|
||||
'dominio' => $normalizedDomain,
|
||||
'base_path' => $normalizedBasePath,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +64,21 @@ class StoreTenantRequest extends FormRequest
|
|||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio'),
|
||||
Rule::unique('tenants', 'dominio')
|
||||
->where('base_path', $this->input('base_path')),
|
||||
],
|
||||
'base_path' => [
|
||||
'bail',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($this->hasInvalidBasePath) {
|
||||
$fail("The {$attribute} field must contain a valid URL path.");
|
||||
}
|
||||
},
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'base_path')
|
||||
->where('dominio', $this->input('dominio')),
|
||||
],
|
||||
'site_title' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||
'primary_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class UpdateTenantRequest extends FormRequest
|
|||
{
|
||||
protected bool $hasInvalidDomain = false;
|
||||
|
||||
protected bool $hasInvalidBasePath = false;
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
|
|
@ -24,14 +26,29 @@ class UpdateTenantRequest extends FormRequest
|
|||
{
|
||||
if ($this->has('dominio')) {
|
||||
$rawDomain = $this->input('dominio');
|
||||
$normalizedDomain = TenantDomainNormalizer::normalizeTenantKey($rawDomain);
|
||||
$normalizedDomain = TenantDomainNormalizer::normalize($rawDomain);
|
||||
$embeddedBasePath = TenantDomainNormalizer::pathFromDomain($rawDomain);
|
||||
|
||||
$this->hasInvalidDomain = TenantDomainNormalizer::hasValue($rawDomain)
|
||||
&& $normalizedDomain === null;
|
||||
&& ($normalizedDomain === null || $embeddedBasePath === null);
|
||||
|
||||
$this->merge([
|
||||
'dominio' => $normalizedDomain,
|
||||
]);
|
||||
|
||||
if (! $this->has('base_path') && $embeddedBasePath !== null && $embeddedBasePath !== '/') {
|
||||
$this->merge(['base_path' => $embeddedBasePath]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->has('base_path')) {
|
||||
$rawBasePath = $this->input('base_path');
|
||||
$normalizedBasePath = TenantDomainNormalizer::normalizePath($rawBasePath);
|
||||
|
||||
$this->hasInvalidBasePath = ! is_string($rawBasePath)
|
||||
|| $normalizedBasePath === null;
|
||||
|
||||
$this->merge(['base_path' => $normalizedBasePath]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +59,8 @@ class UpdateTenantRequest extends FormRequest
|
|||
{
|
||||
/** @var Tenant|null $tenant */
|
||||
$tenant = $this->route('tenant');
|
||||
$domain = $this->input('dominio', $tenant?->dominio);
|
||||
$basePath = $this->input('base_path', $tenant?->base_path ?? '/');
|
||||
|
||||
$logoRule = ['nullable', new ImageOrBase64Rule];
|
||||
|
||||
|
|
@ -64,7 +83,23 @@ class UpdateTenantRequest extends FormRequest
|
|||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio')->ignore($tenant?->id),
|
||||
Rule::unique('tenants', 'dominio')
|
||||
->where('base_path', $basePath)
|
||||
->ignore($tenant?->id),
|
||||
],
|
||||
'base_path' => [
|
||||
'bail',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($this->hasInvalidBasePath) {
|
||||
$fail("The {$attribute} field must contain a valid URL path.");
|
||||
}
|
||||
},
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'base_path')
|
||||
->where('dominio', $domain)
|
||||
->ignore($tenant?->id),
|
||||
],
|
||||
'site_title' => ['sometimes', 'nullable', 'string', 'max:255'],
|
||||
'primary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class TenantResource extends JsonResource
|
|||
'codigo' => $this->codigo,
|
||||
'nombre' => $this->nombre,
|
||||
'dominio' => $this->dominio,
|
||||
'base_path' => $this->base_path,
|
||||
'site_title' => $this->site_title
|
||||
?? $this->websiteType?->site_title
|
||||
?? 'ShopitFront',
|
||||
|
|
|
|||
|
|
@ -42,13 +42,7 @@ class TenantDomainNormalizer
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($path === null && is_string($domain)) {
|
||||
$decodedDomain = trim(urldecode($domain));
|
||||
$candidate = str_contains($decodedDomain, '://')
|
||||
? $decodedDomain
|
||||
: "//{$decodedDomain}";
|
||||
$path = parse_url($candidate, PHP_URL_PATH) ?: '/';
|
||||
}
|
||||
$path ??= self::pathFromDomain($domain);
|
||||
|
||||
$normalizedPath = self::normalizePath($path);
|
||||
|
||||
|
|
@ -59,6 +53,25 @@ class TenantDomainNormalizer
|
|||
return $host.($normalizedPath === '/' ? '' : $normalizedPath);
|
||||
}
|
||||
|
||||
public static function pathFromDomain(mixed $domain): ?string
|
||||
{
|
||||
if (! is_string($domain)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decodedDomain = trim(urldecode($domain));
|
||||
|
||||
if ($decodedDomain === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidate = str_contains($decodedDomain, '://')
|
||||
? $decodedDomain
|
||||
: "//{$decodedDomain}";
|
||||
|
||||
return self::normalizePath(parse_url($candidate, PHP_URL_PATH) ?: '/');
|
||||
}
|
||||
|
||||
public static function normalizePath(mixed $path): ?string
|
||||
{
|
||||
if (! is_string($path)) {
|
||||
|
|
@ -98,9 +111,23 @@ class TenantDomainNormalizer
|
|||
public static function tenantKeyCandidates(mixed $domain, mixed $path): array
|
||||
{
|
||||
$host = self::normalize($domain);
|
||||
|
||||
if ($host === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (string $basePath): string => $host.($basePath === '/' ? '' : $basePath),
|
||||
self::basePathCandidates($path),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public static function basePathCandidates(mixed $path): array
|
||||
{
|
||||
$normalizedPath = self::normalizePath($path);
|
||||
|
||||
if ($host === null || $normalizedPath === null) {
|
||||
if ($normalizedPath === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -111,11 +138,11 @@ class TenantDomainNormalizer
|
|||
$candidates = [];
|
||||
|
||||
while ($segments !== []) {
|
||||
$candidates[] = $host.'/'.implode('/', $segments);
|
||||
$candidates[] = '/'.implode('/', $segments);
|
||||
array_pop($segments);
|
||||
}
|
||||
|
||||
$candidates[] = $host;
|
||||
$candidates[] = '/';
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Es la raíz del modelo multi-tenant. Gestiona organizaciones/sitios, tipos de we
|
|||
|
||||
## Modelo
|
||||
|
||||
- `Tenant`: entidad principal, resuelta en rutas por `codigo`; relaciona catálogo, fechas, redes, menús y configuración visual.
|
||||
- `Tenant`: entidad principal, resuelta en rutas por `codigo`; relaciona catálogo, fechas, redes, menús y configuración visual. Su ubicación pública se representa con `dominio` y `base_path` (`/` para la raíz).
|
||||
- `WebsiteType`: plantilla o tipo de sitio disponible.
|
||||
- `WebsiteTypeExtra`: definición de un extra y su configuración admitida.
|
||||
- `WebsiteExtra`: valor resuelto y estado del extra para un tenant.
|
||||
|
|
@ -20,6 +20,8 @@ Es la raíz del modelo multi-tenant. Gestiona organizaciones/sitios, tipos de we
|
|||
- `WebsiteExtraService`: construye reglas dinámicas, crea, actualiza y habilita/deshabilita extras.
|
||||
- `TenantDomainNormalizer`: normaliza dominios antes de resolver el tenant.
|
||||
|
||||
El par `(dominio, base_path)` es único. Un mismo dominio puede alojar el tenant raíz y otros tenants en prefijos diferentes. El bootstrap compara segmentos completos del path y selecciona el prefijo más específico.
|
||||
|
||||
## Endpoints
|
||||
|
||||
- Recurso REST público/administrativo `/tenants`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$locations = DB::table('tenants')
|
||||
->select(['id', 'dominio'])
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->mapWithKeys(function (object $tenant): array {
|
||||
[$domain, $basePath] = $this->splitTenantLocation($tenant->dominio, $tenant->id);
|
||||
|
||||
return [$tenant->id => compact('domain', 'basePath')];
|
||||
});
|
||||
$duplicates = $locations
|
||||
->groupBy(fn (array $location): string => $location['domain'].'|'.$location['basePath'])
|
||||
->filter(fn ($matches): bool => $matches->count() > 1);
|
||||
|
||||
if ($duplicates->isNotEmpty()) {
|
||||
throw new RuntimeException(
|
||||
'Cannot create the tenant domain/base-path unique index; duplicates exist: '
|
||||
.$duplicates->keys()->implode(', ')
|
||||
);
|
||||
}
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('base_path')->default('/')->after('dominio');
|
||||
$table->dropUnique('tenants_dominio_unique');
|
||||
});
|
||||
|
||||
foreach ($locations as $tenantId => $location) {
|
||||
DB::table('tenants')
|
||||
->where('id', $tenantId)
|
||||
->update([
|
||||
'dominio' => $location['domain'],
|
||||
'base_path' => $location['basePath'],
|
||||
]);
|
||||
}
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->unique(
|
||||
['dominio', 'base_path'],
|
||||
'tenants_dominio_base_path_unique',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropUnique('tenants_dominio_base_path_unique');
|
||||
});
|
||||
|
||||
DB::table('tenants')
|
||||
->select(['id', 'dominio', 'base_path'])
|
||||
->orderBy('id')
|
||||
->each(function (object $tenant): void {
|
||||
$tenantKey = $tenant->dominio.($tenant->base_path === '/' ? '' : $tenant->base_path);
|
||||
|
||||
DB::table('tenants')
|
||||
->where('id', $tenant->id)
|
||||
->update(['dominio' => $tenantKey]);
|
||||
});
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropColumn('base_path');
|
||||
$table->unique('dominio', 'tenants_dominio_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/** @return array{string, string} */
|
||||
private function splitTenantLocation(mixed $value, int $tenantId): array
|
||||
{
|
||||
if (! is_string($value) || trim(urldecode($value)) === '') {
|
||||
throw new RuntimeException("Cannot migrate tenant location for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
$decodedValue = trim(urldecode($value));
|
||||
$candidate = str_contains($decodedValue, '://')
|
||||
? $decodedValue
|
||||
: "//{$decodedValue}";
|
||||
$host = parse_url($candidate, PHP_URL_HOST);
|
||||
$path = parse_url($candidate, PHP_URL_PATH) ?: '/';
|
||||
|
||||
if (! is_string($host) || $host === '') {
|
||||
throw new RuntimeException("Cannot migrate tenant location '{$value}' for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
$segments = array_values(array_filter(
|
||||
explode('/', preg_replace('#/+#', '/', $path) ?? ''),
|
||||
static fn (string $segment): bool => $segment !== '',
|
||||
));
|
||||
|
||||
if (array_intersect($segments, ['.', '..']) !== []) {
|
||||
throw new RuntimeException("Cannot migrate tenant base path '{$path}' for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
return [
|
||||
strtolower($host),
|
||||
$segments === [] ? '/' : '/'.implode('/', $segments),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SeparateTenantDomainAndBasePathTest extends TestCase
|
||||
{
|
||||
private string $originalConnection;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->originalConnection = DB::getDefaultConnection();
|
||||
config()->set('database.connections.tenant_path_test', [
|
||||
'driver' => 'sqlite',
|
||||
'database' => ':memory:',
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => true,
|
||||
]);
|
||||
DB::setDefaultConnection('tenant_path_test');
|
||||
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('dominio');
|
||||
$table->unique('dominio', 'tenants_dominio_unique');
|
||||
});
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
DB::purge('tenant_path_test');
|
||||
DB::setDefaultConnection($this->originalConnection);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_splits_existing_tenant_keys_and_enforces_composite_uniqueness(): void
|
||||
{
|
||||
DB::table('tenants')->insert([
|
||||
['dominio' => 'onticket.com.ar'],
|
||||
['dominio' => 'onticket.com.ar/desfile/'],
|
||||
['dominio' => 'https://ONTICKET.COM.AR/sonder'],
|
||||
]);
|
||||
|
||||
$migration = require database_path(
|
||||
'migrations/2026_08_18_060000_separate_tenant_domain_and_base_path.php'
|
||||
);
|
||||
$migration->up();
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'dominio' => 'onticket.com.ar',
|
||||
'base_path' => '/',
|
||||
]);
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'dominio' => 'onticket.com.ar',
|
||||
'base_path' => '/desfile',
|
||||
]);
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'dominio' => 'onticket.com.ar',
|
||||
'base_path' => '/sonder',
|
||||
]);
|
||||
|
||||
$this->expectException(QueryException::class);
|
||||
|
||||
DB::table('tenants')->insert([
|
||||
'dominio' => 'onticket.com.ar',
|
||||
'base_path' => '/desfile',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_recombines_tenant_locations_when_rolled_back(): void
|
||||
{
|
||||
DB::table('tenants')->insert(['dominio' => 'onticket.com.ar/desfile']);
|
||||
|
||||
$migration = require database_path(
|
||||
'migrations/2026_08_18_060000_separate_tenant_domain_and_base_path.php'
|
||||
);
|
||||
$migration->up();
|
||||
$migration->down();
|
||||
|
||||
$this->assertFalse(Schema::hasColumn('tenants', 'base_path'));
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'dominio' => 'onticket.com.ar/desfile',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -142,23 +142,27 @@ class BootstrapTenantControllerTest extends TestCase
|
|||
$this->createTenant([
|
||||
'codigo' => 'pura-tendencia',
|
||||
'nombre' => 'Pura Tendencia',
|
||||
'dominio' => 'qa.onticket.com.ar/puratendencia',
|
||||
'dominio' => 'qa.onticket.com.ar',
|
||||
'base_path' => '/puratendencia',
|
||||
]);
|
||||
$this->createTenant([
|
||||
'codigo' => 'pura-tendencia-vip',
|
||||
'nombre' => 'Pura Tendencia VIP',
|
||||
'dominio' => 'qa.onticket.com.ar/puratendencia/vip',
|
||||
'dominio' => 'qa.onticket.com.ar',
|
||||
'base_path' => '/puratendencia/vip',
|
||||
]);
|
||||
|
||||
$this->getJson('/api/tenants/bootstrap?dominio=qa.onticket.com.ar&path=%2Fpuratendencia%2Fproductos%2F123')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.codigo', 'pura-tendencia')
|
||||
->assertJsonPath('data.dominio', 'qa.onticket.com.ar/puratendencia');
|
||||
->assertJsonPath('data.dominio', 'qa.onticket.com.ar')
|
||||
->assertJsonPath('data.base_path', '/puratendencia');
|
||||
|
||||
$this->getJson('/api/tenants/bootstrap?dominio=qa.onticket.com.ar&path=%2Fpuratendencia%2Fvip%2Fproductos%2F123')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.codigo', 'pura-tendencia-vip')
|
||||
->assertJsonPath('data.dominio', 'qa.onticket.com.ar/puratendencia/vip');
|
||||
->assertJsonPath('data.dominio', 'qa.onticket.com.ar')
|
||||
->assertJsonPath('data.base_path', '/puratendencia/vip');
|
||||
}
|
||||
|
||||
public function test_it_uses_the_root_tenant_for_spa_paths_without_a_tenant_prefix(): void
|
||||
|
|
@ -171,7 +175,8 @@ class BootstrapTenantControllerTest extends TestCase
|
|||
$this->createTenant([
|
||||
'codigo' => 'sonder',
|
||||
'nombre' => 'Sonder',
|
||||
'dominio' => 'qa.onticket.com.ar/sonder',
|
||||
'dominio' => 'qa.onticket.com.ar',
|
||||
'base_path' => '/sonder',
|
||||
]);
|
||||
|
||||
$this->getJson('/api/tenants/bootstrap?dominio=qa.onticket.com.ar&path=%2Fproducto%2F123')
|
||||
|
|
@ -574,7 +579,8 @@ class BootstrapTenantControllerTest extends TestCase
|
|||
|
||||
$firstResponse
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.dominio', 'acme.com/puratendencia')
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.base_path', '/puratendencia')
|
||||
->assertJsonPath('data.primary_color', '#111111')
|
||||
->assertJsonPath('data.secondary_color', '#222222')
|
||||
->assertJsonPath('data.danger_color', '#333333')
|
||||
|
|
@ -613,7 +619,8 @@ class BootstrapTenantControllerTest extends TestCase
|
|||
$differentPathResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'pura-tendencia',
|
||||
'nombre' => 'Pura Tendencia',
|
||||
'dominio' => 'acme.com/sonder',
|
||||
'dominio' => 'acme.com',
|
||||
'base_path' => '/sonder/',
|
||||
'primary_color' => '#111111',
|
||||
'secondary_color' => '#222222',
|
||||
'danger_color' => '#333333',
|
||||
|
|
@ -626,12 +633,14 @@ class BootstrapTenantControllerTest extends TestCase
|
|||
|
||||
$differentPathResponse
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.dominio', 'acme.com/sonder');
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.base_path', '/sonder');
|
||||
|
||||
$secondResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'globex',
|
||||
'nombre' => 'Globex',
|
||||
'dominio' => 'acme.com/puratendencia',
|
||||
'dominio' => 'acme.com',
|
||||
'base_path' => '/puratendencia',
|
||||
'primary_color' => '#111111',
|
||||
'secondary_color' => '#222222',
|
||||
'danger_color' => '#333333',
|
||||
|
|
|
|||
|
|
@ -73,4 +73,14 @@ class TenantDomainNormalizerTest extends TestCase
|
|||
'/sonder-shop/productos',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_it_builds_base_path_candidates_from_the_longest_path_to_root(): void
|
||||
{
|
||||
$this->assertSame([
|
||||
'/desfile/productos/123',
|
||||
'/desfile/productos',
|
||||
'/desfile',
|
||||
'/',
|
||||
], TenantDomainNormalizer::basePathCandidates('/desfile/productos/123?ref=home'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue