From 3adef9341e5373816cfd5cab8c67cfffd7c6b04a Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 27 Aug 2026 12:10:23 -0300 Subject: [PATCH] refactor(tenant): remove unused CRUD API layer --- .../Tenant/Controllers/TenantController.php | 64 --- .../Tenant/Requests/StoreTenantRequest.php | 128 ------ .../Tenant/Requests/UpdateTenantRequest.php | 143 ------ .../Tenant/BootstrapTenantControllerTest.php | 412 ------------------ .../Tenant/StoreTenantWithExtrasTest.php | 218 --------- .../Feature/Tenant/TenantSocialMediaTest.php | 139 ------ 6 files changed, 1104 deletions(-) delete mode 100644 app/Domains/Tenant/Controllers/TenantController.php delete mode 100644 app/Domains/Tenant/Requests/StoreTenantRequest.php delete mode 100644 app/Domains/Tenant/Requests/UpdateTenantRequest.php delete mode 100644 tests/Feature/Tenant/StoreTenantWithExtrasTest.php diff --git a/app/Domains/Tenant/Controllers/TenantController.php b/app/Domains/Tenant/Controllers/TenantController.php deleted file mode 100644 index 5b105af..0000000 --- a/app/Domains/Tenant/Controllers/TenantController.php +++ /dev/null @@ -1,64 +0,0 @@ -latest() - ->paginateFromRequest(); - - $this->tenantInformationService->loadMany($tenants->getCollection()); - - return TenantResource::collection($tenants)->response(); - } - - public function store(StoreTenantRequest $request): JsonResponse - { - $tenant = $this->tenantService->create($request->validated()); - - return TenantResource::make( - $this->tenantInformationService->load($tenant) - )->response()->setStatusCode(201); - } - - public function show(Tenant $tenant): TenantResource - { - return TenantResource::make( - $this->tenantInformationService->load($tenant) - ); - } - - public function update(UpdateTenantRequest $request, Tenant $tenant): TenantResource - { - $tenant = $this->tenantService->update($tenant, $request->validated()); - - return TenantResource::make( - $this->tenantInformationService->load($tenant) - ); - } - - public function destroy(Tenant $tenant): Response - { - $tenant->delete(); - - return response()->noContent(); - } -} diff --git a/app/Domains/Tenant/Requests/StoreTenantRequest.php b/app/Domains/Tenant/Requests/StoreTenantRequest.php deleted file mode 100644 index ee37d92..0000000 --- a/app/Domains/Tenant/Requests/StoreTenantRequest.php +++ /dev/null @@ -1,128 +0,0 @@ -input('dominio'); - $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, - ]); - } - - /** - * @return array - */ - public function rules(): array - { - $logoRule = ['required', new ImageOrBase64Rule]; - - return array_merge([ - 'client_id' => ['sometimes', 'integer', Rule::exists('clients', 'id')], - 'codigo' => ['required', 'string', 'max:255', Rule::unique('tenants', 'codigo')], - 'nombre' => ['required', 'string', 'max:255'], - 'dominio' => [ - 'bail', - function (string $attribute, mixed $value, Closure $fail): void { - if ($this->hasInvalidDomain) { - $fail("The {$attribute} field must contain a valid domain or URL."); - } - }, - 'required', - 'string', - 'max:255', - 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'], - 'address' => ['sometimes', 'nullable', 'string', 'max:255'], - 'phone' => ['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})$/'], - 'secondary_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'danger_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'success_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'header_bg_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'footer_bg_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'header_logo' => $logoRule, - 'footer_logo' => $logoRule, - 'favicon' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'header_bg_image' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'footer_bg_image' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'social_media' => ['sometimes', 'array'], - 'social_media.*.code' => [ - 'required', - 'string', - 'distinct', - Rule::exists('social_media', 'code'), - ], - 'social_media.*.url' => ['required', 'url', 'max:2048'], - 'social_media.*.orden' => ['sometimes', 'integer', 'min:0', 'distinct'], - 'search_product_layout' => ['sometimes', Rule::enum(ProductLayout::class)], - 'search_group_layout' => ['sometimes', Rule::enum(GroupLayout::class)], - 'search_items_per_page' => ['sometimes', 'integer', 'min:4', 'max:48'], - 'display_categories' => ['sometimes', 'boolean'], - 'display_seach_bar' => ['sometimes', 'boolean'], - 'display_cart' => ['sometimes', 'boolean'], - 'cart_editing_policy' => ['sometimes', Rule::enum(CartEditingPolicy::class)], - 'checkout_editing_policy' => [ - 'sometimes', - Rule::in([CartEditingPolicy::Disabled->value]), - ], - 'display_cart_item_images' => ['sometimes', 'boolean'], - 'scanner_category_validation_enabled' => ['sometimes', 'boolean'], - 'website_type_code' => [ - 'required_with:extras', - 'sometimes', - 'string', - Rule::exists('website_type', 'codigo'), - ], - ], app(WebsiteExtraService::class)->requestRules($this->input('website_type_code'))); - } -} diff --git a/app/Domains/Tenant/Requests/UpdateTenantRequest.php b/app/Domains/Tenant/Requests/UpdateTenantRequest.php deleted file mode 100644 index e7a06bc..0000000 --- a/app/Domains/Tenant/Requests/UpdateTenantRequest.php +++ /dev/null @@ -1,143 +0,0 @@ -has('dominio')) { - $rawDomain = $this->input('dominio'); - $normalizedDomain = TenantDomainNormalizer::normalize($rawDomain); - $embeddedBasePath = TenantDomainNormalizer::pathFromDomain($rawDomain); - - $this->hasInvalidDomain = TenantDomainNormalizer::hasValue($rawDomain) - && ($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]); - } - } - - /** - * @return array - */ - public function rules(): array - { - /** @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]; - - return [ - 'client_id' => ['sometimes', 'integer', Rule::exists('clients', 'id')], - 'codigo' => [ - 'nullable', - 'string', - 'max:255', - Rule::unique('tenants', 'codigo')->ignore($tenant?->id), - ], - 'nombre' => ['nullable', 'string', 'max:255'], - 'dominio' => [ - 'bail', - function (string $attribute, mixed $value, Closure $fail): void { - if ($this->hasInvalidDomain) { - $fail("The {$attribute} field must contain a valid domain or URL."); - } - }, - 'nullable', - 'string', - 'max:255', - 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'], - 'address' => ['sometimes', 'nullable', 'string', 'max:255'], - 'phone' => ['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})$/'], - 'secondary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'danger_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'success_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'header_bg_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'footer_bg_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], - 'header_logo' => $logoRule, - 'footer_logo' => $logoRule, - 'favicon' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'header_bg_image' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'footer_bg_image' => ['sometimes', 'nullable', new ImageOrBase64Rule], - 'social_media' => ['sometimes', 'array'], - 'social_media.*.code' => [ - 'required', - 'string', - 'distinct', - Rule::exists('social_media', 'code'), - ], - 'social_media.*.url' => ['required', 'url', 'max:2048'], - 'social_media.*.orden' => ['sometimes', 'integer', 'min:0', 'distinct'], - 'search_product_layout' => ['sometimes', Rule::enum(ProductLayout::class)], - 'search_group_layout' => ['sometimes', Rule::enum(GroupLayout::class)], - 'search_items_per_page' => ['sometimes', 'integer', 'min:4', 'max:48'], - 'display_categories' => ['sometimes', 'boolean'], - 'display_seach_bar' => ['sometimes', 'boolean'], - 'display_cart' => ['sometimes', 'boolean'], - 'cart_editing_policy' => ['sometimes', Rule::enum(CartEditingPolicy::class)], - 'checkout_editing_policy' => [ - 'sometimes', - Rule::in([CartEditingPolicy::Disabled->value]), - ], - 'display_cart_item_images' => ['sometimes', 'boolean'], - 'scanner_category_validation_enabled' => ['sometimes', 'boolean'], - ]; - } -} diff --git a/tests/Feature/Tenant/BootstrapTenantControllerTest.php b/tests/Feature/Tenant/BootstrapTenantControllerTest.php index 80325a9..7a1d8eb 100644 --- a/tests/Feature/Tenant/BootstrapTenantControllerTest.php +++ b/tests/Feature/Tenant/BootstrapTenantControllerTest.php @@ -573,367 +573,6 @@ class BootstrapTenantControllerTest extends TestCase ->assertJsonPath('data.menues.0.submenues.1.code', 'tree.fox'); } - 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/puratendencia/', - '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, - ]); - - $firstResponse - ->assertCreated() - ->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') - ->assertJsonPath('data.success_color', '#555555') - ->assertJsonPath('data.header_bg_color', '#444444')->assertJsonPath('data.footer_bg_color', '#444444'); - - $tenant = Tenant::query()->with(['headerLogo', 'footerLogo'])->where('codigo', 'acme')->firstOrFail(); - - $this->assertNotNull($tenant->header_logo_id); - $this->assertNotNull($tenant->footer_logo_id); - - $this->assertDatabaseHas('tenants', [ - 'codigo' => 'acme', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo_id' => $tenant->header_logo_id, - 'footer_logo_id' => $tenant->footer_logo_id, - ]); - - $headerUrl = $firstResponse->json('data.header_logo'); - $footerUrl = $firstResponse->json('data.footer_logo'); - - $this->assertStringContainsString($tenant->headerLogo->key, $headerUrl); - $this->assertStringContainsString($tenant->footerLogo->key, $footerUrl); - $this->assertTrue( - str_contains($headerUrl, 'Expires=') || str_contains($headerUrl, 'expiration=') || str_contains($headerUrl, 'X-Amz-Expires=') - ); - $this->assertTrue( - 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', - 'base_path' => '/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') - ->assertJsonPath('data.base_path', '/sonder'); - - $secondResponse = $this->postJson('/api/tenants', [ - 'codigo' => 'globex', - 'nombre' => 'Globex', - 'dominio' => 'acme.com', - 'base_path' => '/puratendencia', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => (string) Str::uuid(), - 'footer_logo' => (string) Str::uuid(), - ]); - - $secondResponse - ->assertUnprocessable() - ->assertJsonValidationErrors(['dominio']); - } - - public function test_it_allows_keeping_the_same_domain_on_update_but_rejects_collisions(): void - { - $tenant = $this->createTenant([ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - ]); - - $otherTenant = $this->createTenant([ - 'codigo' => 'globex', - 'nombre' => 'Globex', - 'dominio' => 'globex.com', - ]); - - $hdrUuid = (string) Str::uuid(); - $ftrUuid = (string) Str::uuid(); - - $hdrAttachment = Attachment::create([ - 'key' => $hdrUuid, - 'path' => 'tenants/'.$hdrUuid.'.png', - 'filename' => 'hdr.png', - 'type' => AttachmentType::Image, - 'mime_type' => 'image/png', - ]); - $ftrAttachment = Attachment::create([ - 'key' => $ftrUuid, - 'path' => 'tenants/'.$ftrUuid.'.png', - 'filename' => 'ftr.png', - 'type' => AttachmentType::Image, - 'mime_type' => 'image/png', - ]); - - $successfulResponse = $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'codigo' => 'acme', - 'nombre' => 'Acme Updated', - 'dominio' => 'https://ACME.com:443/', - 'primary_color' => '#555555', - 'secondary_color' => '#666666', - 'danger_color' => '#777777', - 'success_color' => '#999999', - 'header_bg_color' => '#888888', - 'footer_bg_color' => '#888888', - 'header_logo' => $hdrUuid, - 'footer_logo' => $ftrUuid, - ]); - - $successfulResponse - ->assertOk() - ->assertJsonPath('data.nombre', 'Acme Updated') - ->assertJsonPath('data.dominio', 'acme.com') - ->assertJsonPath('data.primary_color', '#555555') - ->assertJsonPath('data.secondary_color', '#666666') - ->assertJsonPath('data.danger_color', '#777777') - ->assertJsonPath('data.success_color', '#999999') - ->assertJsonPath('data.header_bg_color', '#888888')->assertJsonPath('data.footer_bg_color', '#888888'); - - $headerUrl = $successfulResponse->json('data.header_logo'); - $footerUrl = $successfulResponse->json('data.footer_logo'); - - $this->assertStringContainsString($hdrUuid, $headerUrl); - $this->assertStringContainsString($ftrUuid, $footerUrl); - $this->assertTrue( - str_contains($headerUrl, 'Expires=') || str_contains($headerUrl, 'expiration=') || str_contains($headerUrl, 'X-Amz-Expires=') - ); - $this->assertTrue( - str_contains($footerUrl, 'Expires=') || str_contains($footerUrl, 'expiration=') || str_contains($footerUrl, 'X-Amz-Expires=') - ); - - $this->assertDatabaseHas('tenants', [ - 'id' => $tenant->id, - 'primary_color' => '#555555', - 'secondary_color' => '#666666', - 'danger_color' => '#777777', - 'success_color' => '#999999', - 'header_bg_color' => '#888888', - 'footer_bg_color' => '#888888', - 'header_logo_id' => $hdrAttachment->id, - 'footer_logo_id' => $ftrAttachment->id, - ]); - - $failingResponse = $this->putJson("/api/tenants/{$otherTenant->codigo}", [ - 'codigo' => 'globex', - 'nombre' => 'Globex', - 'dominio' => 'https://ACME.com/', - ]); - - $failingResponse - ->assertUnprocessable() - ->assertJsonValidationErrors(['dominio']); - } - - public function test_it_allows_partial_update_without_required_fields(): void - { - $tenant = $this->createTenant([ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#ffffff', - ]); - - $response = $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'primary_color' => '#000000', - 'cart_editing_policy' => 'quantity_and_remove', - 'checkout_editing_policy' => 'disabled', - 'display_cart_item_images' => false, - ]); - - $response - ->assertOk() - ->assertJsonPath('data.codigo', 'acme') - ->assertJsonPath('data.nombre', 'Acme') - ->assertJsonPath('data.primary_color', '#000000') - ->assertJsonPath('data.cart_editing_policy.code', 'quantity_and_remove') - ->assertJsonPath('data.cart_editing_policy.allow_modify', true) - ->assertJsonPath('data.cart_editing_policy.allow_delete', true) - ->assertJsonPath('data.cart_editing_policy.allow_update_quantity', true) - ->assertJsonPath('data.cart_editing_policy.allow_update_variant', false) - ->assertJsonPath('data.checkout_editing_policy.code', 'disabled') - ->assertJsonPath('data.checkout_editing_policy.allow_modify', false) - ->assertJsonPath('data.display_cart_item_images', false); - - $this->assertDatabaseHas('tenants', [ - 'id' => $tenant->id, - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'primary_color' => '#000000', - 'cart_editing_policy' => 'quantity_and_remove', - 'checkout_editing_policy' => 'disabled', - 'display_cart_item_images' => false, - ]); - } - - public function test_it_validates_aesthetic_colors(): void - { - $response = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => 'invalid-color', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => (string) Str::uuid(), - 'footer_logo' => (string) Str::uuid(), - ]); - - $response->assertJsonValidationErrors(['primary_color']); - - $response2 = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#12345', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => (string) Str::uuid(), - 'footer_logo' => (string) Str::uuid(), - ]); - - $response2->assertJsonValidationErrors(['primary_color']); - - $response3 = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => 'invalid-color', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => (string) Str::uuid(), - 'footer_logo' => (string) Str::uuid(), - ]); - - $response3->assertJsonValidationErrors(['success_color']); - } - - public function test_it_validates_logo_must_be_image_or_svg(): void - { - Storage::fake('s3'); - - $response = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => UploadedFile::fake()->create('document.pdf', 10, 'application/pdf'), - 'footer_logo' => (string) Str::uuid(), - ]); - - $response->assertJsonValidationErrors(['header_logo']); - - $response2 = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => 'data:application/pdf;base64,JVBERi0xLjQKJdcfqksKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAvUGFnZXMgMiAwIFI...', - 'footer_logo' => (string) Str::uuid(), - ]); - - $response2->assertJsonValidationErrors(['header_logo']); - } - - public function test_it_stores_uploaded_file_logos_in_tenants_directory(): void - { - Storage::fake('s3'); - - $header = UploadedFile::fake()->image('header.png'); - $footer = UploadedFile::fake()->image('footer.svg', 100, 100); - - $response = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#555555', - 'header_bg_color' => '#444444', - 'footer_bg_color' => '#444444', - 'header_logo' => $header, - 'footer_logo' => $footer, - ]); - - $response->assertCreated(); - - $tenant = Tenant::query()->with(['headerLogo', 'footerLogo'])->where('codigo', 'acme')->firstOrFail(); - - $this->assertNotNull($tenant->header_logo_id); - $this->assertNotNull($tenant->footer_logo_id); - - $headerUrl = $response->json('data.header_logo'); - $footerUrl = $response->json('data.footer_logo'); - - $this->assertStringContainsString($tenant->headerLogo->key, $headerUrl); - $this->assertStringContainsString($tenant->footerLogo->key, $footerUrl); - $this->assertTrue( - str_contains($headerUrl, 'Expires=') || str_contains($headerUrl, 'expiration=') || str_contains($headerUrl, 'X-Amz-Expires=') - ); - $this->assertTrue( - str_contains($footerUrl, 'Expires=') || str_contains($footerUrl, 'expiration=') || str_contains($footerUrl, 'X-Amz-Expires=') - ); - - $this->assertDatabaseHas('attachments', ['key' => $tenant->headerLogo->key]); - $this->assertDatabaseHas('attachments', ['key' => $tenant->footerLogo->key]); - } - private function createTenant(array $attributes = []): Tenant { $hdrKey = (string) Str::uuid(); @@ -969,55 +608,4 @@ class BootstrapTenantControllerTest extends TestCase ], $attributes)); } - public function test_it_stores_base64_logos_in_tenants_directory(): void - { - Storage::fake('s3'); - - $base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - - $ftrUuid = (string) Str::uuid(); - $footerAttachment = Attachment::create([ - 'key' => $ftrUuid, - 'path' => 'tenants/'.$ftrUuid.'.png', - 'filename' => 'logo_footer.png', - 'type' => AttachmentType::Image, - 'mime_type' => 'image/png', - ]); - - $response = $this->postJson('/api/tenants', [ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - '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' => $ftrUuid, - 'site_title' => 'Acme Store', - 'favicon' => $base64Image, - ]); - - $response->assertCreated(); - - $tenant = Tenant::query()->with(['headerLogo', 'favicon'])->where('codigo', 'acme')->firstOrFail(); - - $this->assertNotNull($tenant->header_logo_id); - $this->assertNotNull($tenant->favicon_id); - - $headerUrl = $response->json('data.header_logo'); - $faviconUrl = $response->json('data.favicon'); - - $response->assertJsonPath('data.site_title', 'Acme Store'); - $this->assertStringContainsString($tenant->headerLogo->key, $headerUrl); - $this->assertStringContainsString($tenant->favicon->key, $faviconUrl); - $this->assertTrue( - str_contains($headerUrl, 'Expires=') || str_contains($headerUrl, 'expiration=') || str_contains($headerUrl, 'X-Amz-Expires=') - ); - - $this->assertDatabaseHas('attachments', ['key' => $tenant->headerLogo->key]); - $this->assertDatabaseHas('attachments', ['key' => $tenant->favicon->key]); - } } diff --git a/tests/Feature/Tenant/StoreTenantWithExtrasTest.php b/tests/Feature/Tenant/StoreTenantWithExtrasTest.php deleted file mode 100644 index 6e9ca4e..0000000 --- a/tests/Feature/Tenant/StoreTenantWithExtrasTest.php +++ /dev/null @@ -1,218 +0,0 @@ -seed(WebsiteTypeSeeder::class); - } - - public function test_it_creates_a_tenant_and_validates_and_stores_its_website_extras(): void - { - $response = $this->postJson('/api/tenants', array_merge($this->tenantData(), [ - 'website_type_code' => 'onticket', - 'extras' => [ - 'eventConfig' => [ - 'title' => 'Festival', - 'location' => 'Buenos Aires', - 'dates_text' => '10 y 11 de octubre de 2026', - 'dates' => [ - [ - 'date' => '2026-10-10', - 'start_time' => '09:00', - 'end_time' => '18:00', - ], - [ - 'date' => '2026-10-11', - 'start_time' => '10:00', - 'end_time' => '17:00', - ], - ], - ], - ], - ])); - - $response - ->assertCreated() - ->assertJsonPath('data.website_type_code', 'onticket') - ->assertJsonMissingPath('data.website_type') - ->assertJsonPath('data.extras.eventConfig.title', 'Festival'); - - $tenant = Tenant::query()->where('codigo', 'festival')->sole(); - - $this->assertDatabaseHas('websites_extras', [ - 'website_code' => $tenant->codigo, - ]); - $this->assertSame( - [ - 'title' => 'Festival', - 'location' => 'Buenos Aires', - 'dates_text' => '10 y 11 de octubre de 2026', - 'dates' => [ - [ - 'date' => '2026-10-10', - 'start_time' => '09:00', - 'end_time' => '18:00', - ], - [ - 'date' => '2026-10-11', - 'start_time' => '10:00', - 'end_time' => '17:00', - ], - ], - ], - $tenant->websiteExtras()->sole()->config - ); - } - - public function test_it_rejects_an_extra_not_supported_by_the_selected_website_type(): void - { - $this->postJson('/api/tenants', array_merge($this->tenantData(), [ - 'website_type_code' => 'shopit', - 'extras' => [ - 'eventConfig' => [ - 'title' => 'Not supported', - ], - ], - ])) - ->assertUnprocessable() - ->assertJsonValidationErrors(['extras']); - - $this->assertDatabaseMissing('tenants', ['codigo' => 'festival']); - } - - public function test_it_applies_the_extra_schema_rules(): void - { - $this->postJson('/api/tenants', array_merge($this->tenantData(), [ - 'website_type_code' => 'onticket', - 'extras' => [ - 'eventConfig' => [ - 'dates' => [[ - 'date' => 'not-a-date', - 'start_time' => '09:00', - 'end_time' => '18:00', - ]], - ], - ], - ])) - ->assertUnprocessable() - ->assertJsonValidationErrors(['extras.eventConfig.dates.0.date']); - - $this->assertDatabaseMissing('tenants', ['codigo' => 'festival']); - } - - public function test_it_transforms_extra_images_to_attachment_ids(): void - { - $image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - - $response = $this->postJson('/api/tenants', array_merge($this->tenantData(), [ - 'website_type_code' => 'shopit', - 'extras' => [ - 'carousel' => [$image], - ], - ])); - - $response->assertCreated(); - - $tenant = Tenant::query() - ->where('codigo', 'festival') - ->sole(); - $config = $tenant->websiteExtras() - ->sole() - ->config; - - $this->assertCount(1, $config); - $this->assertIsInt($config[0]); - $this->assertDatabaseHas('attachments', [ - 'id' => $config[0], - 'type' => 'image', - ]); - - $attachment = Attachment::query()->findOrFail($config[0]); - $carouselUrl = $response->json('data.extras.carousel.0'); - - $this->assertIsString($carouselUrl); - $this->assertStringContainsString($attachment->key, $carouselUrl); - - app(TenantInformationService::class)->load($tenant); - - $this->assertInstanceOf( - Attachment::class, - $tenant->websiteExtras->sole()->resolvedConfig()[0] - ); - } - - public function test_it_returns_scalar_attachment_fields_as_temporary_urls(): void - { - $image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - - $response = $this->postJson('/api/tenants', array_merge($this->tenantData(), [ - 'website_type_code' => 'onticket', - 'extras' => [ - 'heroConfig' => [ - 'title_html' => '

Festival

', - 'background_image_id' => $image, - ], - ], - ])); - - $response->assertCreated(); - - $heroConfig = Tenant::query() - ->where('codigo', 'festival') - ->sole() - ->websiteExtras() - ->whereHas( - 'websiteTypeExtra', - fn ($query) => $query->where('codigo', 'heroConfig') - ) - ->sole() - ->config; - - $attachment = Attachment::query()->findOrFail($heroConfig['background_image_id']); - $backgroundUrl = $response->json('data.extras.heroConfig.background_image_id'); - - $this->assertIsString($backgroundUrl); - $this->assertStringContainsString($attachment->key, $backgroundUrl); - } - - /** - * @return array - */ - private function tenantData(): array - { - $image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - - return [ - 'codigo' => 'festival', - 'nombre' => 'Festival', - 'dominio' => 'festival.test', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#444444', - 'header_bg_color' => '#ffffff', - 'footer_bg_color' => '#ffffff', - 'header_logo' => $image, - 'footer_logo' => $image, - 'search_product_layout' => 'column_with_image', - 'search_group_layout' => 'paginated', - 'search_items_per_page' => 12, - ]; - } -} diff --git a/tests/Feature/Tenant/TenantSocialMediaTest.php b/tests/Feature/Tenant/TenantSocialMediaTest.php index 0901eb9..9127885 100644 --- a/tests/Feature/Tenant/TenantSocialMediaTest.php +++ b/tests/Feature/Tenant/TenantSocialMediaTest.php @@ -79,145 +79,6 @@ class TenantSocialMediaTest extends TestCase ]); } - public function test_tenant_update_synchronizes_social_media_and_returns_pivot_urls(): void - { - $tenant = $this->createTenant(); - $instagram = $this->createSocialMedia('instagram', 'Instagram'); - $facebook = $this->createSocialMedia('facebook', 'Facebook'); - - $tenant->socialMedia()->attach($facebook->code, [ - 'url' => 'https://facebook.com/old-acme', - ]); - - $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'social_media' => [ - [ - 'code' => $instagram->code, - 'url' => 'https://instagram.com/acme', - 'orden' => 4, - ], - ], - ]) - ->assertOk() - ->assertJsonPath('data.social_media.0.code', 'instagram') - ->assertJsonPath('data.social_media.0.icon', 'instagram') - ->assertJsonPath('data.social_media.0.name', 'Instagram') - ->assertJsonPath('data.social_media.0.url', 'https://instagram.com/acme') - ->assertJsonCount(1, 'data.social_media'); - - $this->assertDatabaseHas('tenant_social_media', [ - 'tenant_code' => $tenant->codigo, - 'social_media_code' => $instagram->code, - 'url' => 'https://instagram.com/acme', - 'orden' => 4, - ]); - $this->assertDatabaseMissing('tenant_social_media', [ - 'tenant_code' => $tenant->codigo, - 'social_media_code' => $facebook->code, - ]); - } - - public function test_tenant_social_media_are_returned_in_configured_order(): void - { - $tenant = $this->createTenant(); - $instagram = $this->createSocialMedia('instagram', 'Instagram'); - $facebook = $this->createSocialMedia('facebook', 'Facebook'); - - $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'social_media' => [ - [ - 'code' => $instagram->code, - 'url' => 'https://instagram.com/acme', - 'orden' => 20, - ], - [ - 'code' => $facebook->code, - 'url' => 'https://facebook.com/acme', - 'orden' => 10, - ], - ], - ]) - ->assertOk() - ->assertJsonPath('data.social_media.0.code', 'facebook') - ->assertJsonPath('data.social_media.1.code', 'instagram') - ->assertJsonMissingPath('data.social_media.0.orden') - ->assertJsonMissingPath('data.social_media.1.orden'); - } - - public function test_tenant_store_synchronizes_social_media(): void - { - $instagram = $this->createSocialMedia('instagram', 'Instagram'); - $headerLogo = $this->createAttachment('new-header.png'); - $footerLogo = $this->createAttachment('new-footer.png'); - - $this->postJson('/api/tenants', [ - 'codigo' => 'new-acme', - 'nombre' => 'New Acme', - 'dominio' => 'new-acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#444444', - 'header_bg_color' => '#ffffff', - 'footer_bg_color' => '#ffffff', - 'header_logo' => $headerLogo->key, - 'footer_logo' => $footerLogo->key, - 'social_media' => [ - [ - 'code' => $instagram->code, - 'url' => 'https://instagram.com/new-acme', - ], - ], - ]) - ->assertCreated() - ->assertJsonPath('data.social_media.0.code', 'instagram') - ->assertJsonPath('data.social_media.0.url', 'https://instagram.com/new-acme'); - - $this->assertDatabaseHas('tenant_social_media', [ - 'tenant_code' => 'new-acme', - 'social_media_code' => $instagram->code, - 'url' => 'https://instagram.com/new-acme', - ]); - } - - public function test_tenant_update_can_clear_social_media(): void - { - $tenant = $this->createTenant(); - $instagram = $this->createSocialMedia('instagram', 'Instagram'); - $tenant->socialMedia()->attach($instagram->code, [ - 'url' => 'https://instagram.com/acme', - ]); - - $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'social_media' => [], - ]) - ->assertOk() - ->assertJsonPath('data.social_media', []); - - $this->assertDatabaseMissing('tenant_social_media', [ - 'tenant_code' => $tenant->codigo, - ]); - } - - public function test_tenant_update_validates_social_media_codes_and_urls(): void - { - $tenant = $this->createTenant(); - - $this->putJson("/api/tenants/{$tenant->codigo}", [ - 'social_media' => [ - [ - 'code' => 'unknown', - 'url' => 'not-a-url', - ], - ], - ]) - ->assertUnprocessable() - ->assertJsonValidationErrors([ - 'social_media.0.code', - 'social_media.0.url', - ]); - } - private function createTenant(): Tenant { $headerLogo = $this->createAttachment('header.png');