From 7e1ffbf417f792207ab13e46b92dee5f9d97e00c Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 22 Jul 2026 09:23:58 -0300 Subject: [PATCH] feat(integration): add requires_tenant_configuration field and update related logic and tests --- .../Integration/Models/Integration.php | 2 ++ .../Requests/StoreIntegrationRequest.php | 1 + .../Requests/UpdateIntegrationRequest.php | 1 + .../Services/BaseIntegrationService.php | 2 +- .../Integration/Services/MailService.php | 15 +++++++++-- ...nt_configuration_to_integrations_table.php | 27 +++++++++++++++++++ database/seeders/EmailIntegrationSeeder.php | 1 + .../seeders/TelepagosIntegrationSeeder.php | 2 ++ tests/Feature/Integration/MailServiceTest.php | 22 +++++++++++++++ 9 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 database/migrations/2026_07_22_000000_add_requires_tenant_configuration_to_integrations_table.php diff --git a/app/Domains/Integration/Models/Integration.php b/app/Domains/Integration/Models/Integration.php index 2d8a2bc..04a08f6 100644 --- a/app/Domains/Integration/Models/Integration.php +++ b/app/Domains/Integration/Models/Integration.php @@ -13,10 +13,12 @@ class Integration extends Model 'name', 'url', 'integration_data_schema', + 'requires_tenant_configuration', ]; protected $casts = [ 'integration_data_schema' => 'array', + 'requires_tenant_configuration' => 'boolean', ]; public function tenantIntegrations() diff --git a/app/Domains/Integration/Requests/StoreIntegrationRequest.php b/app/Domains/Integration/Requests/StoreIntegrationRequest.php index 7134e08..a5f1b99 100644 --- a/app/Domains/Integration/Requests/StoreIntegrationRequest.php +++ b/app/Domains/Integration/Requests/StoreIntegrationRequest.php @@ -18,6 +18,7 @@ class StoreIntegrationRequest extends FormRequest 'name' => ['required', 'string', 'max:255'], 'url' => ['nullable', 'url', 'max:255'], 'integration_data_schema' => ['nullable', 'array'], + 'requires_tenant_configuration' => ['sometimes', 'boolean'], ]; } } diff --git a/app/Domains/Integration/Requests/UpdateIntegrationRequest.php b/app/Domains/Integration/Requests/UpdateIntegrationRequest.php index e9cd0f3..40dbfce 100644 --- a/app/Domains/Integration/Requests/UpdateIntegrationRequest.php +++ b/app/Domains/Integration/Requests/UpdateIntegrationRequest.php @@ -19,6 +19,7 @@ class UpdateIntegrationRequest extends FormRequest 'name' => ['sometimes', 'required', 'string', 'max:255'], 'url' => ['nullable', 'url', 'max:255'], 'integration_data_schema' => ['nullable', 'array'], + 'requires_tenant_configuration' => ['sometimes', 'boolean'], // the code shouldn't ideally be updatable, but if it is: 'integration_code' => ['sometimes', 'required', 'string', 'unique:integrations,integration_code,' . ($integration->id ?? '')], ]; diff --git a/app/Domains/Integration/Services/BaseIntegrationService.php b/app/Domains/Integration/Services/BaseIntegrationService.php index ba797e4..d1354a6 100644 --- a/app/Domains/Integration/Services/BaseIntegrationService.php +++ b/app/Domains/Integration/Services/BaseIntegrationService.php @@ -94,7 +94,7 @@ abstract class BaseIntegrationService ->where('integration_code', $this->integrationCode) ->first(); - if (!$this->tenantIntegration) { + if (!$this->tenantIntegration && $this->integration->requires_tenant_configuration) { throw new Exception("Tenant '{$this->tenantCode}' does not have integration '{$this->integrationCode}' configured."); } } diff --git a/app/Domains/Integration/Services/MailService.php b/app/Domains/Integration/Services/MailService.php index 5a8e36a..37a1e1b 100644 --- a/app/Domains/Integration/Services/MailService.php +++ b/app/Domains/Integration/Services/MailService.php @@ -29,6 +29,8 @@ class MailService extends BaseIntegrationService private ?Tenant $tenant = null; + private bool $usesTenantMailer = false; + public function __construct(?MailFactory $mailFactory = null) { $this->mailFactory = $mailFactory ?? app(MailFactory::class); @@ -41,7 +43,14 @@ class MailService extends BaseIntegrationService $this->tenant = Tenant::query() ->where('codigo', $tenantCode) ->firstOrFail(); - $this->mailer = $this->resolveMailer(); + + if ($this->tenantIntegration) { + $this->mailer = $this->resolveMailer(); + $this->usesTenantMailer = true; + } else { + $this->mailer = $this->mailFactory->mailer(); + $this->usesTenantMailer = false; + } return $this; } @@ -82,7 +91,9 @@ class MailService extends BaseIntegrationService public function mailerName(): string { - return 'tenant-smtp'; + return $this->usesTenantMailer + ? 'tenant-smtp' + : (string) config('mail.default'); } public function onSetup(): void diff --git a/database/migrations/2026_07_22_000000_add_requires_tenant_configuration_to_integrations_table.php b/database/migrations/2026_07_22_000000_add_requires_tenant_configuration_to_integrations_table.php new file mode 100644 index 0000000..00a2fc2 --- /dev/null +++ b/database/migrations/2026_07_22_000000_add_requires_tenant_configuration_to_integrations_table.php @@ -0,0 +1,27 @@ +boolean('requires_tenant_configuration')->default(true); + }); + + DB::table('integrations') + ->where('integration_code', 'email') + ->update(['requires_tenant_configuration' => false]); + } + + public function down(): void + { + Schema::table('integrations', function (Blueprint $table) { + $table->dropColumn('requires_tenant_configuration'); + }); + } +}; diff --git a/database/seeders/EmailIntegrationSeeder.php b/database/seeders/EmailIntegrationSeeder.php index edb3798..a7d7a9c 100644 --- a/database/seeders/EmailIntegrationSeeder.php +++ b/database/seeders/EmailIntegrationSeeder.php @@ -17,6 +17,7 @@ class EmailIntegrationSeeder extends Seeder [ 'name' => 'Email', 'url' => null, + 'requires_tenant_configuration' => false, 'integration_data_schema' => [ 'MAIL_MAILER' => 'required|string|in:smtp', 'MAIL_SCHEME' => 'required|string|in:smtp', diff --git a/database/seeders/TelepagosIntegrationSeeder.php b/database/seeders/TelepagosIntegrationSeeder.php index 9ecd2de..6e7c917 100644 --- a/database/seeders/TelepagosIntegrationSeeder.php +++ b/database/seeders/TelepagosIntegrationSeeder.php @@ -17,6 +17,7 @@ class TelepagosIntegrationSeeder extends Seeder [ 'name' => 'Telepagos', 'url' => 'https://api.telepagos.com.ar', + 'requires_tenant_configuration' => true, 'integration_data_schema' => [ 'username' => 'required|string', 'password' => 'required|string', @@ -29,6 +30,7 @@ class TelepagosIntegrationSeeder extends Seeder [ 'name' => 'Telepagos Homologación', 'url' => 'https://api.homo.telepagos.com.ar', + 'requires_tenant_configuration' => true, 'integration_data_schema' => [ 'username' => 'required|string', 'password' => 'required|string', diff --git a/tests/Feature/Integration/MailServiceTest.php b/tests/Feature/Integration/MailServiceTest.php index e71683e..fb34a89 100644 --- a/tests/Feature/Integration/MailServiceTest.php +++ b/tests/Feature/Integration/MailServiceTest.php @@ -57,6 +57,28 @@ class MailServiceTest extends TestCase $this->assertSame('tenant-smtp', $service->mailerName()); } + public function test_it_uses_the_default_mailer_when_tenant_configuration_is_not_required(): void + { + Mail::fake(); + config(['mail.default' => 'array']); + $tenant = $this->createTenant(); + Integration::create([ + 'integration_code' => 'email', + 'name' => 'Email', + 'requires_tenant_configuration' => false, + ]); + + $service = (new MailService)->forTenant($tenant->codigo); + $service->send('customer@example.com', 'Default mailer', '

Fallback

'); + + $this->assertSame('array', $service->mailerName()); + Mail::assertSent(Mailable::class, function (Mailable $mail): bool { + return $mail->hasTo('customer@example.com') + && $mail->subject === 'Default mailer' + && str_contains($mail->render(), 'Fallback'); + }); + } + public function test_on_setup_sends_a_branded_test_email_to_the_configured_sender(): void { Mail::fake();