Files
provider_imap/lib/Console/ServiceTestCommand.php
2026-03-28 12:43:42 -04:00

204 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXM\ProviderImap\Console;
use KTXM\ProviderImap\Providers\Provider;
use KTXM\ProviderImap\Providers\Service;
use KTXM\ProviderImap\Service\Remote\RemoteService;
use KTXC\SessionTenant;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Live IMAP connection test.
*
* Connects to the server using a stored service's credentials, authenticates,
* and lists the root-level mailboxes with their unread message counts as a
* quick end-to-end sanity check.
*
* Usage:
* bin/console provider_imap_mail:service:test <service-id> --tenant=t1 --user=u1
*/
#[AsCommand(
name: 'provider_imap_mail:service:test',
description: 'Test an IMAP service connection and list root mailboxes',
)]
class ServiceTestCommand extends Command
{
public function __construct(
private readonly Provider $provider,
private readonly SessionTenant $sessionTenant,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('service-id', InputArgument::OPTIONAL, 'Service ID to test')
->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID')
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID')
->addOption('all', 'a', InputOption::VALUE_NONE, 'List all mailboxes, not just the root level')
->setHelp(<<<'HELP'
The <info>provider_imap_mail:service:test</info> command opens a live IMAP
connection for a stored service, authenticates, and lists the available
mailboxes together with their message counts.
Examples:
Test a specific service:
<info>bin/console provider_imap_mail:service:test abc123 --tenant=t1 --user=u1</info>
Interactive (lists services, lets you choose one):
<info>bin/console provider_imap_mail:service:test --tenant=t1 --user=u1</info>
Show all mailboxes (not just top-level):
<info>bin/console provider_imap_mail:service:test abc123 --tenant=t1 --user=u1 --all</info>
HELP);
}
protected function interact(InputInterface $input, OutputInterface $output): void
{
$io = new SymfonyStyle($input, $output);
if (!$input->getOption('tenant')) {
$tenant = $io->ask('Tenant ID');
if ($tenant) $input->setOption('tenant', $tenant);
}
if (!$input->getOption('user')) {
$user = $io->ask('User ID');
if ($user) $input->setOption('user', $user);
}
if (!$input->getArgument('service-id')) {
$tenantId = (string) ($input->getOption('tenant') ?? '');
$userId = (string) ($input->getOption('user') ?? '');
if ($tenantId !== '' && $userId !== '') {
$this->sessionTenant->configureById($tenantId);
$services = $this->provider->serviceList($tenantId, $userId);
if (!empty($services)) {
$choices = [];
foreach ($services as $id => $service) {
$label = $service instanceof Service ? ($service->getLabel() ?? $id) : $id;
$choices[$id] = "{$label} [{$id}]";
}
$chosen = $io->choice('Select service to test', array_values($choices));
$serviceId = (string) array_search($chosen, $choices, true);
if ($serviceId !== '') {
$input->setArgument('service-id', $serviceId);
}
}
}
}
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$tenantId = (string) ($input->getOption('tenant') ?? '');
$userId = (string) ($input->getOption('user') ?? '');
$serviceId = (string) ($input->getArgument('service-id') ?? '');
$showAll = (bool) $input->getOption('all');
$errors = [];
if ($tenantId === '') $errors[] = 'Tenant ID is required (--tenant).';
if ($userId === '') $errors[] = 'User ID is required (--user).';
if ($serviceId === '') $errors[] = 'Service ID is required.';
if (!empty($errors)) {
$io->error($errors);
return Command::FAILURE;
}
// ── Fetch stored service ─────────────────────────────────────────────
$this->sessionTenant->configureById($tenantId);
$service = $this->provider->serviceFetch($tenantId, $userId, $serviceId);
if (!($service instanceof Service)) {
$io->error("Service '{$serviceId}' not found.");
return Command::FAILURE;
}
$host = $service->getLocation()?->getHost() ?? 'unknown';
$port = $service->getLocation()?->getPort() ?? 993;
$enc = $service->getLocation()?->getEncryption() ?? 'ssl';
$io->title('IMAP Connection Test');
$io->definitionList(
['Service' => $service->getLabel() ?? $serviceId],
['Host' => $host],
['Port' => (string) $port],
['Encryption' => $enc],
['Username' => $service->getIdentity()?->getIdentity() ?? ''],
);
// ── Quick single-call test via Provider ──────────────────────────────
$io->text('Authenticating…');
$startTime = microtime(true);
$testResult = $this->provider->serviceTest($service);
$latency = (int) round((microtime(true) - $startTime) * 1000);
if (!$testResult['success']) {
$io->error($testResult['message']);
return Command::FAILURE;
}
$io->success($testResult['message']);
// ── Mailbox listing ──────────────────────────────────────────────────
$io->text('Fetching mailbox list…');
try {
$wrapper = RemoteService::freshClient($service);
$mailboxes = $wrapper->mailboxes();
$rows = [];
foreach ($mailboxes as $mailbox) {
// Filter to root-level only unless --all
if (!$showAll && substr_count($mailbox->name, $mailbox->hierarchyDelimiter ?: '/') > 0) {
continue;
}
$selectable = $mailbox->isSelectable() ? '<fg=green>✓</>' : '<fg=yellow></>';
$rows[] = [
$mailbox->name,
$selectable,
];
}
if (empty($rows)) {
$io->note('No mailboxes found' . ($showAll ? '.' : ' at the root level. Use --all to see all mailboxes.'));
} else {
$io->table(['Mailbox', 'Selectable'], $rows);
$noun = count($rows) === 1 ? 'mailbox' : 'mailboxes';
$io->text(sprintf('<info>%d</info> %s listed. Latency: <comment>%d ms</comment>.', count($rows), $noun, $latency));
}
} catch (\Throwable $e) {
$io->warning('Could not list mailboxes: ' . $e->getMessage());
}
return Command::SUCCESS;
}
}