seed(AuthorizationSeeder::class); $this->websiteType = WebsiteType::query()->create([ 'codigo' => 'test-store', 'nombre' => 'Test Store', ]); $this->websiteType->extras()->create([ 'codigo' => 'contactConfig', 'nombre' => 'Configuración de contacto', 'descripcion' => 'Datos de contacto visibles en la tienda.', 'is_required' => false, 'config_schema' => [ 'request_rules' => [ '$' => 'required|array', 'phone' => 'required|string|max:30', ], 'transforms' => [], ], ]); } public function test_authentication_is_required(): void { $this->getJson('/api/v1/adminapp/tenant/website-extras') ->assertUnauthorized(); } public function test_a_customer_cannot_access_adminapp_website_extras(): void { $customer = User::factory()->create([ 'rol_codigo' => RoleCode::User->value, 'tenant_codigo' => null, ]); Sanctum::actingAs($customer); $this->getJson('/api/v1/adminapp/tenant/website-extras') ->assertForbidden(); } public function test_adminapp_user_can_read_definitions_and_current_values(): void { $tenant = $this->createTenant('acme'); $definition = $this->websiteType->extras()->firstOrFail(); $tenant->websiteExtras()->create([ 'website_type_extra_id' => $definition->id, 'config' => ['phone' => '+54 341 555 0101'], ]); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->getJson('/api/v1/adminapp/tenant/website-extras') ->assertOk() ->assertJsonPath('data.website_type.codigo', 'test-store') ->assertJsonPath('data.definitions.contactConfig.codigo', 'contactConfig') ->assertJsonPath('data.definitions.contactConfig.nombre', 'Configuración de contacto') ->assertJsonPath('data.definitions.contactConfig.is_required', false) ->assertJsonPath('data.definitions.contactConfig.is_enabled', null) ->assertJsonPath('data.extras.contactConfig.phone', '+54 341 555 0101') ->assertJsonPath('data.resolved_extras.contactConfig.phone', '+54 341 555 0101'); } public function test_adminapp_user_can_read_one_website_extra(): void { $tenant = $this->createTenant('acme'); $definition = $this->websiteType->extras()->firstOrFail(); $tenant->websiteExtras()->create([ 'website_type_extra_id' => $definition->id, 'config' => ['phone' => '+54 341 555 0101'], 'is_enabled' => true, ]); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->getJson('/api/v1/adminapp/tenant/website-extras/contactConfig') ->assertOk() ->assertJsonPath('data.codigo', 'contactConfig') ->assertJsonPath('data.nombre', 'Configuración de contacto') ->assertJsonPath('data.is_enabled', true) ->assertJsonPath('data.config.phone', '+54 341 555 0101') ->assertJsonPath('data.resolved_config.phone', '+54 341 555 0101'); } public function test_reading_an_unconfigured_website_extra_returns_not_found(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->getJson('/api/v1/adminapp/tenant/website-extras/contactConfig') ->assertNotFound(); } public function test_adminapp_user_updates_one_extra_without_touching_other_tenants(): void { $tenant = $this->createTenant('acme'); $otherTenant = $this->createTenant('other'); $definition = $this->websiteType->extras()->firstOrFail(); $secondaryDefinition = $this->websiteType->extras()->create([ 'codigo' => 'footerConfig', 'nombre' => 'Configuración del pie', 'descripcion' => 'Configuración adicional del pie.', 'is_required' => false, 'config_schema' => [ 'request_rules' => ['$' => 'required|array'], 'transforms' => [], ], ]); $tenant->websiteExtras()->create([ 'website_type_extra_id' => $definition->id, 'config' => ['phone' => 'old'], ]); $otherTenant->websiteExtras()->create([ 'website_type_extra_id' => $definition->id, 'config' => ['phone' => 'untouched'], ]); $tenant->websiteExtras()->create([ 'website_type_extra_id' => $secondaryDefinition->id, 'config' => ['text' => 'also untouched'], ]); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->putJson('/api/v1/adminapp/tenant/website-extras/contactConfig', [ 'config' => [ 'phone' => '+54 341 555 9999', ], ]) ->assertOk() ->assertJsonPath('data.extras.contactConfig.phone', '+54 341 555 9999'); $this->assertSame( ['phone' => '+54 341 555 9999'], $tenant->websiteExtras()->firstOrFail()->config ); $this->assertSame( ['phone' => 'untouched'], $otherTenant->websiteExtras()->firstOrFail()->config ); $this->assertSame( ['text' => 'also untouched'], $tenant->websiteExtras() ->where('website_type_extra_id', $secondaryDefinition->id) ->firstOrFail() ->config ); } public function test_adminapp_user_saves_the_banner_description_in_the_hero_extra(): void { $heroDefinition = $this->websiteType->extras()->create([ 'codigo' => 'heroConfig', 'nombre' => 'Banner principal', 'descripcion' => 'Configuración del banner principal.', 'is_required' => false, 'config_schema' => [ 'request_rules' => [ '$' => 'required|array', 'title_html' => 'nullable|string', 'description_html' => 'nullable|string', 'button_text' => 'nullable|string', 'button_href' => 'nullable|string', 'background_image_id' => 'nullable|string', ], 'transforms' => [], ], ]); $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->putJson('/api/v1/adminapp/tenant/website-extras/heroConfig', [ 'config' => [ 'title_html' => '

Evento

', 'description_html' => '

Nueva descripción del banner

', 'button_text' => 'Comprar', 'button_href' => '/tickets', 'background_image_id' => null, ], ]) ->assertOk() ->assertJsonPath( 'data.extras.heroConfig.description_html', '

Nueva descripción del banner

' ); $this->assertSame( '

Nueva descripción del banner

', $tenant->websiteExtras() ->where('website_type_extra_id', $heroDefinition->id) ->firstOrFail() ->config['description_html'] ); } public function test_adminapp_user_stores_a_cropped_banner_and_receives_original_edit_data(): void { Storage::fake('s3'); $this->websiteType->extras()->create([ 'codigo' => 'heroConfig', 'nombre' => 'Banner principal', 'descripcion' => 'Configuración del banner principal.', 'is_required' => false, 'config_schema' => [ 'request_rules' => [ '$' => 'required|array', 'background_image_id' => 'nullable|cropped_image_or_base64', ], 'transforms' => [ 'background_image_id' => [ 'handler' => 'attachment', 'attachment_type' => 'image', ], ], ], ]); $tenant = $this->createTenant('acme'); $file = UploadedFile::fake()->image('banner.jpg', 200, 100); $image = 'data:image/jpeg;base64,'.base64_encode((string) file_get_contents($file->getRealPath())); Sanctum::actingAs($this->createAdminAppUser($tenant)); $response = $this->putJson('/api/v1/adminapp/tenant/website-extras/heroConfig', [ 'config' => [ 'background_image_id' => [ 'image' => $image, 'crop_horizontal' => [ 'start_percentage' => 25, 'end_percentage' => 75, ], 'crop_vertical' => [ 'start_percentage' => 10, 'end_percentage' => 85, ], ], ], ])->assertOk(); $original = Attachment::query()->whereNotNull('cropped_attachment_id')->sole(); $response ->assertJsonPath('data.extras.heroConfig.background_image_id', $original->key) ->assertJsonPath( 'data.resolved_extras.heroConfig.background_image_id.crop_horizontal.start_percentage', 25 ) ->assertJsonPath( 'data.resolved_extras.heroConfig.background_image_id.crop_vertical.end_percentage', 85 ); $this->assertStringContainsString( $original->key, $response->json('data.resolved_extras.heroConfig.background_image_id.url') ); $this->assertDatabaseCount('attachments', 2); $previousCropId = $original->cropped_attachment_id; $this->putJson('/api/v1/adminapp/tenant/website-extras/heroConfig', [ 'config' => [ 'background_image_id' => [ 'image' => $original->key, 'crop_horizontal' => [ 'start_percentage' => 10, 'end_percentage' => 90, ], 'crop_vertical' => [ 'start_percentage' => 20, 'end_percentage' => 80, ], ], ], ]) ->assertOk() ->assertJsonPath( 'data.resolved_extras.heroConfig.background_image_id.crop_horizontal.start_percentage', 10 ); $this->assertNotSame($previousCropId, $original->refresh()->cropped_attachment_id); $this->assertDatabaseMissing('attachments', ['id' => $previousCropId]); $this->assertDatabaseCount('attachments', 2); } public function test_update_returns_not_found_for_an_unsupported_extra_code(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->putJson('/api/v1/adminapp/tenant/website-extras/unknown', [ 'config' => ['enabled' => true], ]) ->assertNotFound(); } public function test_adminapp_user_can_toggle_an_existing_website_extra(): void { $tenant = $this->createTenant('acme'); $definition = $this->websiteType->extras()->firstOrFail(); $websiteExtra = $tenant->websiteExtras()->create([ 'website_type_extra_id' => $definition->id, 'config' => ['phone' => '+54 341 555 0101'], 'is_enabled' => false, ]); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->patchJson('/api/v1/adminapp/tenant/website-extras/contactConfig/toggle') ->assertOk() ->assertJsonPath('code', 'tenant.website_extra_toggled') ->assertJsonPath('message', 'El estado de la sección se actualizó correctamente.') ->assertJsonPath('data.definitions.contactConfig.is_enabled', true); $this->assertTrue($websiteExtra->refresh()->is_enabled); $this->patchJson('/api/v1/adminapp/tenant/website-extras/contactConfig/toggle') ->assertOk() ->assertJsonPath('data.definitions.contactConfig.is_enabled', false); $this->assertFalse($websiteExtra->refresh()->is_enabled); } public function test_toggle_enables_an_extra_without_a_tenant_value(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->patchJson('/api/v1/adminapp/tenant/website-extras/contactConfig/toggle') ->assertOk() ->assertJsonPath('data.definitions.contactConfig.is_enabled', true) ->assertJsonPath('data.extras.contactConfig', []); $this->assertDatabaseHas('websites_extras', [ 'website_code' => $tenant->codigo, 'website_type_extra_id' => $this->websiteType->extras()->firstOrFail()->id, 'is_enabled' => true, ]); } private function createTenant(string $code): Tenant { return Tenant::query()->create([ 'codigo' => $code, 'nombre' => ucfirst($code), 'dominio' => "{$code}.test", 'website_type_code' => $this->websiteType->codigo, ]); } private function createAdminAppUser(Tenant $tenant): User { return User::factory()->create([ 'rol_codigo' => RoleCode::AdminApp->value, 'tenant_codigo' => $tenant->codigo, ]); } }