refactor(integration): streamline integration management by removing IntegrationInstanceController and updating related requests and services
This commit is contained in:
@@ -4,10 +4,7 @@ namespace App\Domains\Integration\Controllers;
|
||||
|
||||
use App\Domains\Client\Models\Client;
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use App\Domains\Integration\Models\IntegrationInstance;
|
||||
use App\Domains\Integration\Requests\AssociateIntegrationInstanceRequest;
|
||||
use App\Domains\Integration\Requests\StoreClientIntegrationRequest;
|
||||
use App\Domains\Integration\Resources\IntegrationAssociationResource;
|
||||
use App\Domains\Integration\Requests\ConfigureIntegrationRequest;
|
||||
use App\Domains\Integration\Services\ClientIntegrationService;
|
||||
use App\Domains\Integration\Services\IntegrationAssociationService;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -39,7 +36,7 @@ class ClientIntegrationController extends Controller
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreClientIntegrationRequest $request,
|
||||
ConfigureIntegrationRequest $request,
|
||||
Client $client,
|
||||
string $integrationCode,
|
||||
): JsonResponse {
|
||||
@@ -66,13 +63,6 @@ class ClientIntegrationController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function associate(AssociateIntegrationInstanceRequest $request, Client $client, string $integrationCode, IntegrationAssociationService $service)
|
||||
{
|
||||
return new IntegrationAssociationResource($service->associate(
|
||||
$client, $integrationCode, IntegrationInstance::findOrFail($request->validated('integration_instance_id')),
|
||||
));
|
||||
}
|
||||
|
||||
public function destroy(Client $client, string $integrationCode, IntegrationAssociationService $service)
|
||||
{
|
||||
$service->detach($client, $integrationCode);
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Integration\Controllers;
|
||||
|
||||
use App\Domains\Integration\Models\IntegrationInstance;
|
||||
use App\Domains\Integration\Requests\StoreIntegrationInstanceRequest;
|
||||
use App\Domains\Integration\Resources\IntegrationInstanceResource;
|
||||
use App\Domains\Integration\Services\IntegrationInstanceService;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class IntegrationInstanceController extends Controller
|
||||
{
|
||||
public function __construct(private readonly IntegrationInstanceService $service) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return IntegrationInstanceResource::collection(IntegrationInstance::query()->orderBy('id')->paginate());
|
||||
}
|
||||
|
||||
public function store(StoreIntegrationInstanceRequest $request)
|
||||
{
|
||||
return (new IntegrationInstanceResource($this->service->create($request->validated())))
|
||||
->response()->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(IntegrationInstance $integrationInstance)
|
||||
{
|
||||
return new IntegrationInstanceResource($integrationInstance);
|
||||
}
|
||||
|
||||
public function update(StoreIntegrationInstanceRequest $request, IntegrationInstance $integrationInstance)
|
||||
{
|
||||
return new IntegrationInstanceResource($this->service->update($integrationInstance, $request->validated()));
|
||||
}
|
||||
|
||||
public function destroy(IntegrationInstance $integrationInstance)
|
||||
{
|
||||
$this->service->delete($integrationInstance);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Domains\Integration\Controllers;
|
||||
|
||||
use App\Domains\Integration\Models\IntegrationInstance;
|
||||
use App\Domains\Integration\Requests\AssociateIntegrationInstanceRequest;
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use App\Domains\Integration\Requests\ConfigureIntegrationRequest;
|
||||
use App\Domains\Integration\Resources\IntegrationAssociationResource;
|
||||
use App\Domains\Integration\Services\IntegrationAssociationService;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
@@ -23,10 +23,14 @@ class WebsiteTypeIntegrationController extends Controller
|
||||
return new IntegrationAssociationResource($websiteType->integrations()->with('integrationInstance')->where('integration_code', $integrationCode)->firstOrFail());
|
||||
}
|
||||
|
||||
public function store(AssociateIntegrationInstanceRequest $request, WebsiteType $websiteType, string $integrationCode)
|
||||
public function store(ConfigureIntegrationRequest $request, WebsiteType $websiteType, string $integrationCode)
|
||||
{
|
||||
return new IntegrationAssociationResource($this->service->associate(
|
||||
$websiteType, $integrationCode, IntegrationInstance::findOrFail($request->validated('integration_instance_id')),
|
||||
$integration = Integration::query()->where('integration_code', $integrationCode)->firstOrFail();
|
||||
|
||||
return new IntegrationAssociationResource($this->service->configure(
|
||||
$websiteType,
|
||||
$integration,
|
||||
$request->validated('integration_data'),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Integration\Requests;
|
||||
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AssociateIntegrationInstanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('manage', Integration::class) ?? false;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'integration_instance_id' => [
|
||||
'required', 'integer',
|
||||
Rule::exists('integration_instances', 'id')->where('integration_code', $this->route('integration_code')),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use App\Domains\Integration\Models\Integration;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class StoreClientIntegrationRequest extends FormRequest
|
||||
class ConfigureIntegrationRequest extends FormRequest
|
||||
{
|
||||
protected ?Integration $integrationModel = null;
|
||||
|
||||
@@ -17,9 +17,8 @@ class StoreClientIntegrationRequest extends FormRequest
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$integrationCode = $this->route('integration_code');
|
||||
$this->integrationModel = Integration::query()
|
||||
->where('integration_code', $integrationCode)
|
||||
->where('integration_code', $this->route('integration_code'))
|
||||
->first();
|
||||
|
||||
if (! $this->integrationModel) {
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Integration\Requests;
|
||||
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreIntegrationInstanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('manage', Integration::class) ?? false;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$instance = $this->route('integration_instance');
|
||||
$code = $instance?->integration_code ?? $this->input('integration_code');
|
||||
$integration = is_string($code) ? Integration::where('integration_code', $code)->first() : null;
|
||||
$rules = [
|
||||
'integration_code' => $instance ? ['prohibited'] : ['required', 'string', 'exists:integrations,integration_code'],
|
||||
'name' => [$instance ? 'sometimes' : 'required', 'required', 'string', 'max:255'],
|
||||
// PUT/PATCH replaces the complete credential object when supplied.
|
||||
'integration_data' => [$instance ? 'sometimes' : 'present', 'array'],
|
||||
];
|
||||
|
||||
if (! $instance || $this->exists('integration_data')) {
|
||||
foreach ($integration?->integration_data_schema ?? [] as $field => $rule) {
|
||||
$rules['integration_data.'.$field] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -30,19 +30,7 @@ class ClientIntegrationService
|
||||
array $data,
|
||||
): ClientIntegration {
|
||||
return DB::transaction(function () use ($client, $integration, $data): ClientIntegration {
|
||||
// Legacy configuration endpoint replaces this client's instance only.
|
||||
$instance = app(IntegrationInstanceService::class)->create([
|
||||
'integration_code' => $integration->integration_code,
|
||||
'name' => $integration->name.' / '.$client->name,
|
||||
'integration_data' => $data,
|
||||
]);
|
||||
$clientIntegration = ClientIntegration::query()->updateOrCreate(
|
||||
[
|
||||
'client_id' => $client->id,
|
||||
'integration_code' => $integration->integration_code,
|
||||
],
|
||||
['integration_instance_id' => $instance->id],
|
||||
);
|
||||
$clientIntegration = app(IntegrationAssociationService::class)->configure($client, $integration, $data);
|
||||
|
||||
$service = $this->resolveService($integration->integration_code);
|
||||
$service?->forClient($client)->onSetup();
|
||||
|
||||
@@ -4,12 +4,27 @@ namespace App\Domains\Integration\Services;
|
||||
|
||||
use App\Domains\Client\Models\Client;
|
||||
use App\Domains\Integration\Models\ClientIntegration;
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use App\Domains\Integration\Models\IntegrationInstance;
|
||||
use App\Domains\Integration\Models\WebsiteTypeIntegration;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IntegrationAssociationService
|
||||
{
|
||||
public function configure(Client|WebsiteType $owner, Integration $integration, array $integrationData): ClientIntegration|WebsiteTypeIntegration
|
||||
{
|
||||
return DB::transaction(function () use ($owner, $integration, $integrationData): ClientIntegration|WebsiteTypeIntegration {
|
||||
$instance = IntegrationInstance::create([
|
||||
'integration_code' => $integration->integration_code,
|
||||
'name' => $integration->name.' / '.$this->ownerName($owner),
|
||||
'integration_data' => $integrationData,
|
||||
]);
|
||||
|
||||
return $this->associate($owner, $integration->integration_code, $instance);
|
||||
});
|
||||
}
|
||||
|
||||
public function associate(Client|WebsiteType $owner, string $code, IntegrationInstance $instance): ClientIntegration|WebsiteTypeIntegration
|
||||
{
|
||||
abort_unless($instance->integration_code === $code, 422, 'The instance belongs to another integration.');
|
||||
@@ -24,4 +39,9 @@ class IntegrationAssociationService
|
||||
{
|
||||
$owner->integrations()->where('integration_code', $code)->delete();
|
||||
}
|
||||
|
||||
private function ownerName(Client|WebsiteType $owner): string
|
||||
{
|
||||
return $owner instanceof Client ? $owner->name : $owner->nombre;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
## Modelo
|
||||
|
||||
- `Integration`: catálogo, URL base, `integration_data_schema` y `requires_configuration`.
|
||||
- `IntegrationInstance`: configuración concreta con nombre. `integration_data` se cifra con `EncryptedIntegrationData`, se almacena en `longText` y nunca se devuelve en la API.
|
||||
- `IntegrationInstance`: configuración interna concreta con nombre. `integration_data` se cifra con `EncryptedIntegrationData`, se almacena en `longText` y nunca se devuelve en la API.
|
||||
- `ClientIntegration` y `WebsiteTypeIntegration`: asociaciones a instancias. La clave compuesta verifica el código de la instancia y la unicidad permite una instancia por integración y propietario.
|
||||
|
||||
Editar una instancia compartida afecta a todos sus consumidores. Desvincularla solo elimina la asociación. Las instancias vinculadas no se pueden eliminar (HTTP 409); las instancias sin asociaciones se conservan hasta su eliminación explícita. El código de una integración o instancia no cambia después de crearla.
|
||||
Las instancias no se administran directamente por HTTP. Cada configuración enviada desde un cliente o tipo de sitio crea una instancia interna nueva y reemplaza únicamente la asociación de ese propietario. Desvincularla solo elimina la asociación.
|
||||
|
||||
## Resolución
|
||||
|
||||
@@ -14,29 +14,24 @@ Editar una instancia compartida afecta a todos sus consumidores. Desvincularla s
|
||||
|
||||
`forClient()` usa únicamente la asociación del cliente: sin un tenant concreto no se elige un tipo de sitio. Si no existe una instancia y `requires_configuration` es verdadero, se genera un error. Para correo opcional, `MailService` usa el mailer global si no encuentra una instancia; cuando la encuentra, construye un transporte SMTP aislado identificado como `integration-smtp`.
|
||||
|
||||
Telepagos utiliza una clave de caché basada en el ID de instancia y una huella del texto cifrado. Compartir instancia permite reutilizar el token. Guardar otras credenciales cambia la clave; la edición explícita elimina la anterior después del commit. Volver a configurar el servicio con `forClient()` o `forTenant()` carga la configuración actual.
|
||||
Telepagos utiliza una clave de caché basada en el ID de instancia y una huella del texto cifrado. Volver a configurar el servicio con `forClient()` o `forTenant()` carga la configuración actual.
|
||||
|
||||
## Administración
|
||||
|
||||
Todas estas rutas llevan el prefijo `/api`, requieren `auth:sanctum` y el rol global `admin` mediante `IntegrationPolicy`. Los roles `adminapp`, `scanner` y `user` no administran credenciales compartidas.
|
||||
Todas estas rutas llevan el prefijo `/api`, requieren `auth:sanctum` y el rol global `admin` mediante `IntegrationPolicy`. Los roles `adminapp`, `scanner` y `user` no administran configuraciones.
|
||||
|
||||
| Método | Ruta | Operación |
|
||||
| --- | --- | --- |
|
||||
| CRUD | `/integrations` | Catálogo. |
|
||||
| GET, POST | `/integration-instances` | Listar con paginación o crear. |
|
||||
| GET, PUT, PATCH, DELETE | `/integration-instances/{id}` | Consultar, editar o eliminar. |
|
||||
| GET | `/clients/{client}/integrations[/{integration_code}]` | Consultar asociaciones directas. |
|
||||
| PUT | `/clients/{client}/integrations/{integration_code}` | Guardado compatible: crea una instancia nueva y reemplaza solo la asociación del cliente. Ejecuta el hook de configuración existente. |
|
||||
| PUT | `/clients/{client}/integrations/{integration_code}/instance` | Asociar una instancia existente. |
|
||||
| PUT | `/clients/{client}/integrations/{integration_code}` | Configurar: crea una instancia interna nueva y reemplaza solo la asociación del cliente. Ejecuta el hook de configuración existente. |
|
||||
| DELETE | `/clients/{client}/integrations/{integration_code}` | Desvincular. |
|
||||
| GET | `/website-types/{codigo}/integrations[/{integration_code}]` | Consultar asociaciones del tipo de sitio. |
|
||||
| PUT, DELETE | `/website-types/{codigo}/integrations/{integration_code}` | Asociar una instancia existente o desvincular. |
|
||||
| PUT | `/website-types/{codigo}/integrations/{integration_code}` | Configurar: crea una instancia interna nueva y reemplaza solo la asociación del tipo de sitio. |
|
||||
| DELETE | `/website-types/{codigo}/integrations/{integration_code}` | Desvincular. |
|
||||
|
||||
Crear una instancia requiere `integration_code`, `name` e `integration_data` (objeto validado según el esquema del catálogo). Editar solo `name` conserva las credenciales. Enviar `integration_data` reemplaza el objeto completo y valida todo el esquema. El código de la instancia es inmutable.
|
||||
Los dos `PUT` reciben `integration_data`, un objeto completo validado según el esquema de la integración. La integración debe existir previamente en el catálogo interno. No hay endpoints públicos para administrar el catálogo ni las instancias directamente.
|
||||
|
||||
Asociar requiere un payload como `{"integration_instance_id": 12}`. Se verifica que exista y pertenezca al código solicitado. La creación y edición explícita de instancias validan el esquema sin enviar mensajes ni llamar al proveedor. Para editar una configuración compartida se utiliza explícitamente el endpoint de la instancia.
|
||||
|
||||
Los nuevos endpoints usan resources con envoltorio `data`, sin credenciales. Las consultas históricas de cliente conservan su formato JSON e incluyen `integration_instance` sin credenciales.
|
||||
Las respuestas y consultas incluyen metadatos de `integration_instance`, pero nunca sus credenciales. La configuración del cliente conserva su mensaje de respuesta histórico; la del tipo de sitio usa un resource con envoltorio `data`.
|
||||
|
||||
## Webhooks y contexto operativo
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Integration\Controllers\ClientIntegrationController;
|
||||
use App\Domains\Integration\Controllers\IntegrationController;
|
||||
use App\Domains\Integration\Controllers\IntegrationInstanceController;
|
||||
use App\Domains\Integration\Controllers\TelepagosWebhookController;
|
||||
use App\Domains\Integration\Controllers\WebsiteTypeIntegrationController;
|
||||
use App\Domains\Integration\Models\Integration;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(['auth:sanctum', 'can:manage,'.Integration::class])->group(function (): void {
|
||||
Route::apiResource('integration-instances', IntegrationInstanceController::class);
|
||||
|
||||
Route::prefix('website-types/{websiteType:codigo}/integrations')->group(function (): void {
|
||||
Route::get('/', [WebsiteTypeIntegrationController::class, 'index']);
|
||||
Route::get('/{integration_code}', [WebsiteTypeIntegrationController::class, 'show']);
|
||||
@@ -18,19 +14,10 @@ Route::middleware(['auth:sanctum', 'can:manage,'.Integration::class])->group(fun
|
||||
Route::delete('/{integration_code}', [WebsiteTypeIntegrationController::class, 'destroy']);
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'integrations'], function () {
|
||||
Route::get('/', [IntegrationController::class, 'index']);
|
||||
Route::post('/', [IntegrationController::class, 'store']);
|
||||
Route::get('/{integration}', [IntegrationController::class, 'show']);
|
||||
Route::put('/{integration}', [IntegrationController::class, 'update']);
|
||||
Route::delete('/{integration}', [IntegrationController::class, 'destroy']);
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'clients/{client}/integrations'], function () {
|
||||
Route::get('/', [ClientIntegrationController::class, 'index']);
|
||||
Route::get('/{integration_code}', [ClientIntegrationController::class, 'show']);
|
||||
Route::put('/{integration_code}', [ClientIntegrationController::class, 'store']);
|
||||
Route::put('/{integration_code}/instance', [ClientIntegrationController::class, 'associate']);
|
||||
Route::delete('/{integration_code}', [ClientIntegrationController::class, 'destroy']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,49 +39,47 @@ class IntegrationInstanceTest extends TestCase
|
||||
|
||||
public function test_management_requires_an_authenticated_global_admin(): void
|
||||
{
|
||||
$this->getJson('/api/integration-instances')->assertUnauthorized();
|
||||
Client::create(['code' => 'acme', 'name' => 'Acme']);
|
||||
WebsiteType::create(['codigo' => 'demo', 'nombre' => 'Demo']);
|
||||
|
||||
$this->getJson('/api/clients/acme/integrations')->assertUnauthorized();
|
||||
Sanctum::actingAs(User::factory()->create());
|
||||
$this->getJson('/api/integration-instances')->assertForbidden();
|
||||
$this->postJson('/api/integration-instances', [])->assertForbidden();
|
||||
$this->getJson('/api/integrations')->assertForbidden();
|
||||
$this->getJson('/api/clients/acme/integrations')->assertForbidden();
|
||||
$this->getJson('/api/website-types/demo/integrations')->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_instance_crud_validates_configuration_and_never_returns_secrets(): void
|
||||
{
|
||||
$this->admin();
|
||||
$this->postJson('/api/integration-instances', [
|
||||
'integration_code' => 'test', 'name' => 'Invalid', 'integration_data' => [],
|
||||
])->assertUnprocessable()->assertJsonValidationErrors('integration_data.api_key');
|
||||
$response = $this->postJson('/api/integration-instances', [
|
||||
'integration_code' => 'test', 'name' => 'Shared', 'integration_data' => ['api_key' => 'secret'],
|
||||
])->assertCreated()->assertJsonMissingPath('data.integration_data');
|
||||
$id = $response->json('data.id');
|
||||
$this->getJson('/api/integration-instances/'.$id)->assertOk()->assertJsonMissingPath('data.integration_data');
|
||||
$this->patchJson('/api/integration-instances/'.$id, ['name' => 'Renamed'])->assertOk();
|
||||
self::assertSame('secret', IntegrationInstance::findOrFail($id)->integration_data['api_key']);
|
||||
$this->patchJson('/api/integration-instances/'.$id, ['integration_code' => 'test'])->assertUnprocessable();
|
||||
$this->patchJson('/api/integration-instances/'.$id, ['integration_data' => []])->assertUnprocessable();
|
||||
$this->patchJson('/api/integration-instances/'.$id, ['integration_data' => ['api_key' => 'new']])->assertOk();
|
||||
self::assertSame('new', IntegrationInstance::findOrFail($id)->integration_data['api_key']);
|
||||
$this->deleteJson('/api/integration-instances/'.$id)->assertNoContent();
|
||||
}
|
||||
|
||||
public function test_association_api_checks_integration_and_protects_linked_instances(): void
|
||||
public function test_only_owner_endpoints_can_manage_configuration(): void
|
||||
{
|
||||
$this->admin();
|
||||
$client = Client::create(['code' => 'acme', 'name' => 'Acme']);
|
||||
$type = WebsiteType::create(['codigo' => 'demo', 'nombre' => 'Demo']);
|
||||
$instance = $this->makeInstance('shared');
|
||||
$this->putJson('/api/clients/acme/integrations/wrong/instance', ['integration_instance_id' => $instance->id])->assertUnprocessable();
|
||||
$this->putJson('/api/clients/acme/integrations/test/instance', ['integration_instance_id' => $instance->id])
|
||||
->assertCreated()->assertJsonPath('data.integration_instance.name', 'shared')->assertJsonMissingPath('data.integration_instance.integration_data');
|
||||
$this->putJson('/api/website-types/demo/integrations/test', ['integration_instance_id' => $instance->id])->assertCreated();
|
||||
$this->getJson('/api/website-types/demo/integrations/test')->assertOk()->assertJsonPath('data.integration_instance_id', $instance->id);
|
||||
$this->deleteJson('/api/integration-instances/'.$instance->id)->assertConflict();
|
||||
|
||||
$this->getJson('/api/integrations')->assertNotFound();
|
||||
$this->getJson('/api/integration-instances')->assertNotFound();
|
||||
$this->putJson('/api/clients/acme/integrations/test/instance', [])->assertNotFound();
|
||||
|
||||
$this->putJson('/api/clients/acme/integrations/test', ['integration_data' => []])
|
||||
->assertUnprocessable()->assertJsonValidationErrors('integration_data.api_key');
|
||||
$this->putJson('/api/website-types/demo/integrations/test', ['integration_data' => []])
|
||||
->assertUnprocessable()->assertJsonValidationErrors('integration_data.api_key');
|
||||
|
||||
$this->putJson('/api/clients/acme/integrations/test', [
|
||||
'integration_data' => ['api_key' => 'client-secret'],
|
||||
])->assertOk()->assertJsonMissingPath('integration_data');
|
||||
$clientInstanceId = $client->integrations()->firstOrFail()->integration_instance_id;
|
||||
self::assertSame('client-secret', IntegrationInstance::findOrFail($clientInstanceId)->integration_data['api_key']);
|
||||
|
||||
$this->putJson('/api/website-types/demo/integrations/test', [
|
||||
'integration_data' => ['api_key' => 'type-secret'],
|
||||
])->assertCreated()
|
||||
->assertJsonPath('data.integration_instance.name', 'Test / Demo')
|
||||
->assertJsonMissingPath('data.integration_instance.integration_data');
|
||||
$typeInstanceId = $type->integrations()->firstOrFail()->integration_instance_id;
|
||||
$this->getJson('/api/website-types/demo/integrations/test')
|
||||
->assertOk()->assertJsonPath('data.integration_instance_id', $typeInstanceId);
|
||||
|
||||
$this->deleteJson('/api/clients/acme/integrations/test')->assertNoContent();
|
||||
$this->deleteJson('/api/website-types/demo/integrations/test')->assertNoContent();
|
||||
self::assertTrue($instance->fresh()->exists);
|
||||
$this->deleteJson('/api/integration-instances/'.$instance->id)->assertNoContent();
|
||||
}
|
||||
|
||||
public function test_tenant_prefers_client_then_type_and_client_context_does_not_inherit(): void
|
||||
|
||||
Reference in New Issue
Block a user