TenantAtlas/apps/platform/tests/Feature/SupportRequests/TenantSupportRequestExternalHandoffTest.php
ahmido 7b394918ce
Some checks failed
Main Confidence / confidence (push) Failing after 1m48s
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m43s
chore(platform): merge platform-dev into dev (#302)
Integrates latest TenantPilot platform changes from `platform-dev` into `dev`.

This PR was created by agent on user request; do not merge automatically.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #302
2026-04-29 20:53:36 +00:00

188 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\TenantDashboard;
use App\Models\SupportRequest;
use App\Models\Tenant;
use App\Models\User;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
function spec256ConfigureTenantSupportDesk(array $overrides = []): void
{
config([
'support_desk.target' => array_merge([
'enabled' => true,
'name' => 'Spec 256 Desk',
'create_url' => 'https://desk.example.test/api/tickets',
'api_token' => null,
'ticket_url_template' => 'https://desk.example.test/tickets/{reference}',
'timeout_seconds' => 5,
], $overrides),
]);
}
function spec256TenantHandoffComponent(User $user, Tenant $tenant): \Livewire\Features\SupportTesting\Testable
{
test()->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
setTenantPanelContext($tenant);
return Livewire::actingAs($user)->test(TenantDashboard::class);
}
it('creates an external ticket from the tenant dashboard support action', function (): void {
spec256ConfigureTenantSupportDesk();
$tenant = Tenant::factory()->create(['name' => 'Spec 256 Tenant']);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
Http::fake([
'desk.example.test/*' => Http::response([
'ticket_reference' => 'PSA-2561',
'ticket_url' => 'https://desk.example.test/tickets/PSA-2561',
], 201),
]);
spec256TenantHandoffComponent($user, $tenant)
->mountAction('requestSupport')
->setActionData([
'severity' => SupportRequest::SEVERITY_HIGH,
'summary' => 'Tenant create external ticket handoff.',
'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET,
])
->callMountedAction()
->assertHasNoActionErrors()
->assertNotified('Support request submitted');
$supportRequest = SupportRequest::query()->sole();
expect($supportRequest->internal_reference)->toMatch('/^SR-[0-9A-HJKMNP-TV-Z]{26}$/')
->and($supportRequest->external_handoff_mode)->toBe(SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET)
->and($supportRequest->external_ticket_reference)->toBe('PSA-2561')
->and($supportRequest->external_ticket_url)->toBe('https://desk.example.test/tickets/PSA-2561')
->and($supportRequest->external_handoff_failure_summary)->toBeNull()
->and($supportRequest->externalHandoffOutcome())->toBe(SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_CREATED);
Http::assertSent(fn (Request $request): bool => $request->url() === 'https://desk.example.test/api/tickets'
&& data_get($request->data(), 'support_request.internal_reference') === $supportRequest->internal_reference);
});
it('links an existing external ticket from the tenant dashboard without creating a duplicate external ticket', function (): void {
spec256ConfigureTenantSupportDesk();
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator');
Http::fake();
spec256TenantHandoffComponent($user, $tenant)
->mountAction('requestSupport')
->setActionData([
'severity' => SupportRequest::SEVERITY_NORMAL,
'summary' => 'Tenant link existing external ticket.',
'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET,
'external_ticket_reference' => 'PSA-256-LINK',
'external_ticket_url' => 'https://desk.example.test/tickets/PSA-256-LINK',
])
->callMountedAction()
->assertHasNoActionErrors()
->assertNotified('Support request submitted');
$supportRequest = SupportRequest::query()->sole();
expect($supportRequest->external_handoff_mode)->toBe(SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET)
->and($supportRequest->external_ticket_reference)->toBe('PSA-256-LINK')
->and($supportRequest->external_ticket_url)->toBe('https://desk.example.test/tickets/PSA-256-LINK')
->and($supportRequest->externalHandoffOutcome())->toBe(SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_LINKED);
Http::assertNothingSent();
});
it('rejects invalid linked external ticket input before storing a tenant support request', function (): void {
spec256ConfigureTenantSupportDesk();
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator');
Http::fake();
spec256TenantHandoffComponent($user, $tenant)
->mountAction('requestSupport')
->setActionData([
'severity' => SupportRequest::SEVERITY_NORMAL,
'summary' => 'Tenant invalid link should not create support truth.',
'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET,
'external_ticket_reference' => 'not a ticket',
])
->callMountedAction()
->assertHasErrors(['external_ticket_reference']);
expect(SupportRequest::query()->count())->toBe(0);
Http::assertNothingSent();
});
it('keeps the internal tenant support request when external create fails', function (): void {
spec256ConfigureTenantSupportDesk();
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'manager');
Http::fake([
'desk.example.test/*' => Http::failedConnection(),
]);
spec256TenantHandoffComponent($user, $tenant)
->mountAction('requestSupport')
->setActionData([
'severity' => SupportRequest::SEVERITY_BLOCKING,
'summary' => 'Tenant external desk timeout should keep internal support request.',
'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET,
])
->callMountedAction()
->assertHasNoActionErrors()
->assertNotified('Support request submitted');
$supportRequest = SupportRequest::query()->sole();
expect($supportRequest->internal_reference)->toMatch('/^SR-/')
->and($supportRequest->external_handoff_mode)->toBe(SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET)
->and($supportRequest->external_ticket_reference)->toBeNull()
->and($supportRequest->external_ticket_url)->toBeNull()
->and($supportRequest->external_handoff_failure_summary)->toContain('configured timeout')
->and($supportRequest->externalHandoffOutcome())->toBe(SupportRequest::HANDOFF_OUTCOME_EXTERNAL_HANDOFF_FAILED);
});
it('forces tenant support requests to internal only when no external target is configured', function (): void {
spec256ConfigureTenantSupportDesk([
'enabled' => false,
]);
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
Http::fake();
spec256TenantHandoffComponent($user, $tenant)
->mountAction('requestSupport')
->setActionData([
'summary' => 'Tenant support stays internal when no support desk target exists.',
])
->callMountedAction()
->assertHasNoActionErrors();
$supportRequest = SupportRequest::query()->sole();
expect($supportRequest->external_handoff_mode)->toBe(SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY)
->and($supportRequest->external_ticket_reference)->toBeNull()
->and($supportRequest->external_handoff_failure_summary)->toBeNull();
Http::assertNothingSent();
});