feat: Introduce client management and integration updates
- Added client_id field to StoreTenantRequest and UpdateTenantRequest for tenant management. - Updated TenantResource to include client_id in the response. - Created migration to establish clients table and link tenants to clients. - Migrated existing tenant integrations to client_integrations table. - Grouped specific tenants under a shared client (OnTicket) in a new migration. - Updated seeders to reflect new client structure and relationships. - Adjusted integration configurations to require client instead of tenant. - Added tests for client integration functionality and ensured existing tests reflect the new client structure.
This commit is contained in:
@@ -2,15 +2,20 @@
|
||||
|
||||
namespace Tests\Feature\Integration;
|
||||
|
||||
use App\Domains\Attachable\Enums\AttachmentType;
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Integration\Models\ClientIntegration;
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use App\Domains\Integration\Models\TenantIntegration;
|
||||
use App\Domains\Integration\Services\TelepagosIntegrationService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Tests\TestCase;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IntegrationServiceTest extends TestCase
|
||||
{
|
||||
@@ -23,26 +28,26 @@ class IntegrationServiceTest extends TestCase
|
||||
parent::setUp();
|
||||
|
||||
// Set the integrations secret for tests
|
||||
config(['services.integrations.secret' => 'base64:' . base64_encode(random_bytes(32))]);
|
||||
config(['services.integrations.secret' => 'base64:'.base64_encode(random_bytes(32))]);
|
||||
|
||||
// Clear cache to prevent test pollution
|
||||
Cache::flush();
|
||||
|
||||
$hdrKey = (string) \Illuminate\Support\Str::uuid();
|
||||
$ftrKey = (string) \Illuminate\Support\Str::uuid();
|
||||
$hdrKey = (string) Str::uuid();
|
||||
$ftrKey = (string) Str::uuid();
|
||||
|
||||
$headerAttachment = \App\Domains\Attachable\Models\Attachment::create([
|
||||
$headerAttachment = Attachment::create([
|
||||
'key' => $hdrKey,
|
||||
'path' => 'tenants/' . $hdrKey . '.png',
|
||||
'path' => 'tenants/'.$hdrKey.'.png',
|
||||
'filename' => 'logo_header.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'type' => AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$footerAttachment = \App\Domains\Attachable\Models\Attachment::create([
|
||||
$footerAttachment = Attachment::create([
|
||||
'key' => $ftrKey,
|
||||
'path' => 'tenants/' . $ftrKey . '.png',
|
||||
'path' => 'tenants/'.$ftrKey.'.png',
|
||||
'filename' => 'logo_footer.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'type' => AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
|
||||
@@ -72,7 +77,7 @@ class IntegrationServiceTest extends TestCase
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_if_tenant_integration_is_not_configured(): void
|
||||
public function test_it_throws_exception_if_client_integration_is_not_configured(): void
|
||||
{
|
||||
// Seed integration but don't configure for tenant
|
||||
Integration::create([
|
||||
@@ -82,13 +87,13 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Tenant 'test-tenant' does not have integration 'telepagos_homo' configured.");
|
||||
$this->expectExceptionMessage("Client 'test-tenant' does not have integration 'telepagos_homo' configured.");
|
||||
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
}
|
||||
@@ -102,16 +107,16 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'user123',
|
||||
'password' => 'pass123',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -131,16 +136,16 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Mock HTTP response sequence for authentication
|
||||
@@ -149,13 +154,13 @@ class IntegrationServiceTest extends TestCase
|
||||
->push([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200)
|
||||
->push([
|
||||
'status' => 'ok',
|
||||
'token' => 'new-mock-jwt-token',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
], 200)
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -181,6 +186,50 @@ class IntegrationServiceTest extends TestCase
|
||||
Http::assertSentCount(2);
|
||||
}
|
||||
|
||||
public function test_tenants_from_the_same_client_share_configuration_and_cached_token(): void
|
||||
{
|
||||
Integration::create([
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'name' => 'Telepagos',
|
||||
'url' => 'https://api.telepagos.com.ar',
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
],
|
||||
]);
|
||||
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'shared-user',
|
||||
'password' => 'shared-password',
|
||||
],
|
||||
]);
|
||||
|
||||
$secondTenant = $this->tenant->replicate()->fill([
|
||||
'codigo' => 'second-tenant',
|
||||
'nombre' => 'Second Tenant',
|
||||
'dominio' => 'second.test.com',
|
||||
]);
|
||||
$secondTenant->save();
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'shared-token',
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
]),
|
||||
]);
|
||||
|
||||
$firstService = (new TelepagosIntegrationService('telepagos'))->forTenant($this->tenant->codigo);
|
||||
$secondService = (new TelepagosIntegrationService('telepagos'))->forTenant($secondTenant->codigo);
|
||||
|
||||
$this->assertSame('shared-token', $firstService->getToken());
|
||||
$this->assertSame('shared-token', $secondService->getToken());
|
||||
Http::assertSentCount(1);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_if_credentials_are_missing(): void
|
||||
{
|
||||
Integration::create([
|
||||
@@ -190,23 +239,23 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => '',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Missing username or password in Telepagos integration settings.");
|
||||
$this->expectExceptionMessage('Missing username or password in Telepagos integration settings.');
|
||||
|
||||
$service->getToken();
|
||||
}
|
||||
@@ -220,26 +269,26 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid credentials'
|
||||
], 401)
|
||||
'message' => 'Invalid credentials',
|
||||
], 401),
|
||||
]);
|
||||
|
||||
\Illuminate\Support\Facades\Log::shouldReceive('error')
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos authentication failed: Invalid credentials', \Mockery::on(function ($context) {
|
||||
return $context['username'] === 'tele_user'
|
||||
@@ -251,7 +300,7 @@ class IntegrationServiceTest extends TestCase
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Telepagos authentication failed: Invalid credentials");
|
||||
$this->expectExceptionMessage('Telepagos authentication failed: Invalid credentials');
|
||||
|
||||
$service->getToken();
|
||||
}
|
||||
@@ -265,28 +314,28 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
'https://api.telepagos.com.ar/v1/payments' => Http::response([
|
||||
'status' => 'success',
|
||||
'payment_id' => 999
|
||||
], 200)
|
||||
'payment_id' => 999,
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -294,7 +343,7 @@ class IntegrationServiceTest extends TestCase
|
||||
|
||||
// Get configured client and perform GET request
|
||||
$client = $service->client();
|
||||
$this->assertInstanceOf(\Illuminate\Http\Client\PendingRequest::class, $client);
|
||||
$this->assertInstanceOf(PendingRequest::class, $client);
|
||||
|
||||
$response = $client->get('/v1/payments');
|
||||
|
||||
@@ -317,29 +366,29 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
'https://api.telepagos.com.ar/v2/payment/cashin/qr/generate' => Http::response([
|
||||
'status' => 'ok',
|
||||
'qr_code' => 'mock-qr-code-data',
|
||||
'qr_order_id' => 6353
|
||||
], 200)
|
||||
'qr_order_id' => 6353,
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -369,31 +418,31 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
'https://api.telepagos.com.ar/v2/payment/cashin/qr/generate' => Http::response([
|
||||
'status' => 'error',
|
||||
'message' => 'Importe inválido'
|
||||
], 422)
|
||||
'message' => 'Importe inválido',
|
||||
], 422),
|
||||
]);
|
||||
|
||||
\Illuminate\Support\Facades\Log::shouldReceive('error')
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos QR generation failed: Importe inválido', \Mockery::on(function ($context) {
|
||||
return $context['amount'] === 1200.00
|
||||
@@ -407,7 +456,7 @@ class IntegrationServiceTest extends TestCase
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Telepagos QR generation failed: Importe inválido");
|
||||
$this->expectExceptionMessage('Telepagos QR generation failed: Importe inválido');
|
||||
|
||||
$service->generateQr(1200.00, 'Test Concept', 'Test Description');
|
||||
}
|
||||
@@ -421,29 +470,29 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
'https://api.telepagos.com.ar/v2/payment/cashin/6351' => Http::response([
|
||||
'status' => 'ok',
|
||||
'buyer' => [
|
||||
'cuit' => '20416561398',
|
||||
'cvu' => '0000124900000000011974'
|
||||
'cvu' => '0000124900000000011974',
|
||||
],
|
||||
'amount' => 1200,
|
||||
'concept' => 'VAR',
|
||||
@@ -452,8 +501,8 @@ class IntegrationServiceTest extends TestCase
|
||||
'description' => 'Pago prueba',
|
||||
'transaction_id' => '2026070364',
|
||||
'qr_order_id' => 6351,
|
||||
'link_id' => null
|
||||
], 200)
|
||||
'link_id' => null,
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -482,31 +531,31 @@ class IntegrationServiceTest extends TestCase
|
||||
'integration_data_schema' => [
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
TenantIntegration::create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
ClientIntegration::create([
|
||||
'client_id' => $this->tenant->client_id,
|
||||
'integration_code' => 'telepagos_homo',
|
||||
'integration_data' => [
|
||||
'username' => 'tele_user',
|
||||
'password' => 'tele_pass',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'ok',
|
||||
'token' => 'mock-jwt-token-123',
|
||||
'expires_at' => now()->addHour()->toDateTimeString()
|
||||
'expires_at' => now()->addHour()->toDateTimeString(),
|
||||
], 200),
|
||||
'https://api.telepagos.com.ar/v2/payment/cashin/6351' => Http::response([
|
||||
'status' => 'error',
|
||||
'message' => 'Cashin no encontrado'
|
||||
], 404)
|
||||
'message' => 'Cashin no encontrado',
|
||||
], 404),
|
||||
]);
|
||||
|
||||
\Illuminate\Support\Facades\Log::shouldReceive('error')
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos get cash-in details failed: Cashin no encontrado', \Mockery::on(function ($context) {
|
||||
return $context['cashin_id'] === 6351
|
||||
@@ -518,7 +567,7 @@ class IntegrationServiceTest extends TestCase
|
||||
$service->forTenant($this->tenant->codigo);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Telepagos get cash-in details failed: Cashin no encontrado");
|
||||
$this->expectExceptionMessage('Telepagos get cash-in details failed: Cashin no encontrado');
|
||||
|
||||
$service->getCashinDetails(6351);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user