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(); });