feat(bootstrap): update tenant bootstrap endpoint to accept domain and path as query parameters, enhancing tenant resolution logic

This commit is contained in:
2026-08-14 15:17:24 -03:00
parent de8da72354
commit c1bfab471c
15 changed files with 344 additions and 37 deletions

View File

@@ -74,7 +74,7 @@ class BootstrapTenantControllerTest extends TestCase
'display_cart' => false,
]);
$response = $this->getJson('/api/tenants/bootstrap/acme.com');
$response = $this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F');
$response
->assertOk()
@@ -117,9 +117,9 @@ class BootstrapTenantControllerTest extends TestCase
'dominio' => 'acme.com',
]);
$encodedDomain = urlencode('https://ACME.com:8080/path?foo=bar#frag');
$encodedDomain = urlencode('https://ACME.com:8080');
$response = $this->getJson("/api/tenants/bootstrap/{$encodedDomain}");
$response = $this->getJson("/api/tenants/bootstrap?dominio={$encodedDomain}&path=%2F");
$response
->assertOk()
@@ -127,6 +127,62 @@ class BootstrapTenantControllerTest extends TestCase
->assertJsonPath('data.dominio', 'acme.com');
}
public function test_it_resolves_the_longest_tenant_prefix_for_deep_links(): void
{
$this->createTenant([
'codigo' => 'qa-root',
'nombre' => 'QA Root',
'dominio' => 'qa.onticket.com.ar',
]);
$this->createTenant([
'codigo' => 'pura-tendencia',
'nombre' => 'Pura Tendencia',
'dominio' => 'qa.onticket.com.ar/puratendencia',
]);
$this->createTenant([
'codigo' => 'pura-tendencia-vip',
'nombre' => 'Pura Tendencia VIP',
'dominio' => 'qa.onticket.com.ar/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');
$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');
}
public function test_it_uses_the_root_tenant_for_spa_paths_without_a_tenant_prefix(): void
{
$this->createTenant([
'codigo' => 'qa-root',
'nombre' => 'QA Root',
'dominio' => 'qa.onticket.com.ar',
]);
$this->createTenant([
'codigo' => 'sonder',
'nombre' => 'Sonder',
'dominio' => 'qa.onticket.com.ar/sonder',
]);
$this->getJson('/api/tenants/bootstrap?dominio=qa.onticket.com.ar&path=%2Fproducto%2F123')
->assertOk()
->assertJsonPath('data.codigo', 'qa-root');
$this->getJson('/api/tenants/bootstrap?dominio=qa.onticket.com.ar&path=%2Fsonder-shop%2Fproducto%2F123')
->assertOk()
->assertJsonPath('data.codigo', 'qa-root');
}
public function test_the_legacy_bootstrap_route_is_not_available(): void
{
$this->getJson('/api/tenants/bootstrap/acme.com')->assertNotFound();
}
public function test_it_resolves_browser_branding_from_the_tenant_before_the_website_type(): void
{
$typeFavicon = Attachment::query()->create([
@@ -153,7 +209,7 @@ class BootstrapTenantControllerTest extends TestCase
'website_type_code' => $websiteType->codigo,
]);
$typeResponse = $this->getJson('/api/tenants/bootstrap/acme.com');
$typeResponse = $this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F');
$typeResponse
->assertOk()
@@ -165,7 +221,7 @@ class BootstrapTenantControllerTest extends TestCase
'favicon_id' => $tenantFavicon->id,
]);
$tenantResponse = $this->getJson('/api/tenants/bootstrap/acme.com');
$tenantResponse = $this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F');
$tenantResponse
->assertOk()
@@ -177,7 +233,7 @@ class BootstrapTenantControllerTest extends TestCase
{
$this->createTenant();
$this->getJson('/api/tenants/bootstrap/acme.com')
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
->assertOk()
->assertJsonPath('data.site_title', 'ShopitFront')
->assertJsonPath('data.favicon', null);
@@ -210,7 +266,7 @@ class BootstrapTenantControllerTest extends TestCase
'nombre' => 'Global',
]);
$this->getJson('/api/tenants/bootstrap/acme.com')
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
->assertOk()
->assertJsonCount(1, 'data.categories')
->assertJsonPath('data.categories.0.nombre', 'Remeras')
@@ -254,7 +310,7 @@ class BootstrapTenantControllerTest extends TestCase
'is_enabled' => false,
]);
$this->getJson('/api/tenants/bootstrap/acme.com')
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
->assertOk()
->assertJsonMissingPath('data.website_type')
->assertJsonPath('data.extras.contact.phone', '+54 341 555 0101')
@@ -263,7 +319,7 @@ class BootstrapTenantControllerTest extends TestCase
public function test_it_returns_not_found_when_the_domain_does_not_exist(): void
{
$response = $this->getJson('/api/tenants/bootstrap/missing.example');
$response = $this->getJson('/api/tenants/bootstrap?dominio=missing.example&path=%2F');
$response->assertNotFound();
}
@@ -317,7 +373,7 @@ class BootstrapTenantControllerTest extends TestCase
$dynamicChild->code,
]);
$response = $this->getJson('/api/tenants/bootstrap/acme.com');
$response = $this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F');
$response
->assertOk()
@@ -375,7 +431,7 @@ class BootstrapTenantControllerTest extends TestCase
$userRole->menus()->sync([$userMenu->code]);
$adminRole->menus()->sync([$adminMenu->code]);
$this->getJson('/api/tenants/bootstrap/acme.com')
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
->assertOk()
->assertJsonPath('data.menues.0.code', 'account')
->assertJsonMissing(['code' => 'admin.catalog']);
@@ -416,7 +472,7 @@ class BootstrapTenantControllerTest extends TestCase
$tenant->menues()->sync($menuCodes);
$userRole->menus()->sync($menuCodes);
$this->getJson('/api/tenants/bootstrap/acme.com')
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
->assertOk()
->assertJsonPath('data.menues.0.code', 'tree')
->assertJsonPath('data.menues.1.code', 'zeta')
@@ -424,14 +480,14 @@ class BootstrapTenantControllerTest extends TestCase
->assertJsonPath('data.menues.0.submenues.1.code', 'tree.fox');
}
public function test_it_rejects_duplicate_domains_after_normalization_when_storing(): void
public function test_it_allows_different_domain_paths_and_rejects_duplicate_tenant_keys(): void
{
$base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
$firstResponse = $this->postJson('/api/tenants', [
'codigo' => 'acme',
'nombre' => 'Acme',
'dominio' => 'https://ACME.com/path',
'dominio' => 'https://ACME.com/puratendencia/',
'primary_color' => '#111111',
'secondary_color' => '#222222',
'danger_color' => '#333333',
@@ -444,7 +500,7 @@ class BootstrapTenantControllerTest extends TestCase
$firstResponse
->assertCreated()
->assertJsonPath('data.dominio', 'acme.com')
->assertJsonPath('data.dominio', 'acme.com/puratendencia')
->assertJsonPath('data.primary_color', '#111111')
->assertJsonPath('data.secondary_color', '#222222')
->assertJsonPath('data.danger_color', '#333333')
@@ -480,10 +536,28 @@ class BootstrapTenantControllerTest extends TestCase
str_contains($footerUrl, 'Expires=') || str_contains($footerUrl, 'expiration=') || str_contains($footerUrl, 'X-Amz-Expires=')
);
$differentPathResponse = $this->postJson('/api/tenants', [
'codigo' => 'pura-tendencia',
'nombre' => 'Pura Tendencia',
'dominio' => 'acme.com/sonder',
'primary_color' => '#111111',
'secondary_color' => '#222222',
'danger_color' => '#333333',
'success_color' => '#555555',
'header_bg_color' => '#444444',
'footer_bg_color' => '#444444',
'header_logo' => $base64Image,
'footer_logo' => $base64Image,
]);
$differentPathResponse
->assertCreated()
->assertJsonPath('data.dominio', 'acme.com/sonder');
$secondResponse = $this->postJson('/api/tenants', [
'codigo' => 'globex',
'nombre' => 'Globex',
'dominio' => 'acme.com',
'dominio' => 'acme.com/puratendencia',
'primary_color' => '#111111',
'secondary_color' => '#222222',
'danger_color' => '#333333',
@@ -534,7 +608,7 @@ class BootstrapTenantControllerTest extends TestCase
$successfulResponse = $this->putJson("/api/tenants/{$tenant->codigo}", [
'codigo' => 'acme',
'nombre' => 'Acme Updated',
'dominio' => 'https://ACME.com:443/admin',
'dominio' => 'https://ACME.com:443/',
'primary_color' => '#555555',
'secondary_color' => '#666666',
'danger_color' => '#777777',
@@ -582,7 +656,7 @@ class BootstrapTenantControllerTest extends TestCase
$failingResponse = $this->putJson("/api/tenants/{$otherTenant->codigo}", [
'codigo' => 'globex',
'nombre' => 'Globex',
'dominio' => 'https://ACME.com/storefront',
'dominio' => 'https://ACME.com/',
]);
$failingResponse

View File

@@ -0,0 +1,19 @@
<?php
namespace Tests\Feature\Tenant;
use App\Domains\Bootstrap\Controllers\TenantBootstrapController;
use Illuminate\Http\Request;
use Tests\TestCase;
class TenantBootstrapRouteTest extends TestCase
{
public function test_the_static_bootstrap_route_takes_precedence_over_the_tenant_resource(): void
{
$route = app('router')->getRoutes()->match(
Request::create('/api/tenants/bootstrap', 'GET')
);
$this->assertSame(TenantBootstrapController::class, $route->getActionName());
}
}