Files
fossbilling-mailgateway/tests/cooldown.php
T

116 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/automatic.php';
$connection->executeStatement('CREATE TABLE mailgateway_notification (sender_key TEXT PRIMARY KEY, last_notified_at INTEGER NOT NULL)');
$sent = 0;
$send = function () use (&$sent): bool { ++$sent; return true; };
$limiter = new \Box\Mod\Mailgateway\NotificationCooldown($connection);
$newRun = new \Box\Mod\Mailgateway\NotificationCooldown($connection);
if (!$limiter->run('Guest@example.com', $send) || $newRun->run('guest@example.com', $send) || $sent !== 1) {
throw new RuntimeException('New polling run reset cooldown');
}
$key = hash('sha256', 'guest@example.com');
$connection->update('mailgateway_notification', ['last_notified_at' => time() - 300], ['sender_key' => $key]);
if ($newRun->run('guest@example.com', $send)) {
throw new RuntimeException('Five-minute poll bypassed cooldown');
}
$connection->update('mailgateway_notification', ['last_notified_at' => time() - 3601], ['sender_key' => $key]);
if (!$newRun->run('guest@example.com', $send)) {
throw new RuntimeException('Expired cooldown did not permit notification');
}
if ($limiter->run('disabled@example.com', fn () => false) || !$limiter->run('disabled@example.com', $send)) {
throw new RuntimeException('Disabled template consumed cooldown');
}
$before = (int) $connection->fetchOne('SELECT COUNT(*) FROM test_messages');
try {
$limiter->run('failure@example.com', function () use ($connection): void {
$connection->insert('test_messages', ['ticket_id' => 41]);
throw new RuntimeException('Queue failed');
});
throw new LogicException('Queue failure swallowed');
} catch (RuntimeException $error) {
if ($error->getMessage() !== 'Queue failed') { throw $error; }
}
if ((int) $connection->fetchOne('SELECT COUNT(*) FROM test_messages') !== $before || !$limiter->run('failure@example.com', $send)) {
throw new RuntimeException('Failed queue did not roll back');
}
$connection->beginTransaction();
$limiter->run('transaction@example.com', $send);
$connection->rollBack();
if (!$limiter->run('transaction@example.com', $send)) {
throw new RuntimeException('Outer rollback retained cooldown');
}
$connection->executeStatement('CREATE TABLE client (id INTEGER PRIMARY KEY, email TEXT)');
$di = new \Pimple\Container();
$di['dbal'] = $connection;
$di['mailgateway_settings'] = new class {
public function get(): array { return ['support_helpdesk_id' => null]; }
};
$di['em'] = new class { public function flush(): void {} public function clear(): void {} };
$di['logger'] = new class { public function warning(...$args): void {} };
$mail = new class {
public array $emails = [];
public function sendTemplate($data): bool { $this->emails[] = $data; return true; }
};
$ticket = new \Box\Mod\Support\Entity\SupportTicket();
(new ReflectionProperty($ticket, 'id'))->setValue($ticket, 50);
$ticket->setAccessHash('guest-hash');
$ticket->setAuthorEmail('guest-owner@example.com');
$support = new class($di, $ticket) {
public int $created = 0;
public int $replies = 0;
public bool $fail = false;
public function __construct(public $di, public $ticket) {}
public function getHelpdeskRepository() { return $this; }
public function findBy($criteria, $order, $limit)
{
$desk = new \Box\Mod\Support\Entity\Helpdesk();
(new ReflectionProperty($desk, 'id'))->setValue($desk, 1);
return [$desk];
}
public function ticketCreateForGuest($data)
{
++$this->created;
foreach (['mod_support_ticket_open', 'mod_support_ticket_staff_reply'] as $code) {
$this->di['mod_service']('email')->sendTemplate(['code' => $code, 'to' => $data['email']]);
}
if ($this->fail) { throw new RuntimeException('Support failed'); }
return 'guest-hash';
}
public function findOneByHash($hash) { return $this->ticket; }
public function getTicketById($id) { return $this->ticket; }
public function canBeReopened($ticket): bool { return true; }
public function ticketReply($ticket, $identity, $content): int { return ++$this->replies; }
};
$lookup = fn ($module, $sub = '') => $module === 'email' ? $mail : $support;
$di['mod_service'] = $di->protect($lookup);
$service = new \Box\Mod\Mailgateway\Service();
$service->setDi($di);
$input = ['sender_email' => 'guest-owner@example.com', 'subject' => 'Test', 'content' => 'Test body'];
$service->import($input);
$service->import($input);
if ($support->created !== 2 || count($mail->emails) !== 1 || $di['mod_service'] !== $lookup) {
throw new RuntimeException('Guest notification cooldown or lookup restoration failed');
}
$service->reply(['sender_email' => $input['sender_email'], 'ticket_id' => 50, 'content' => 'Human reply']);
if ($support->replies !== 1) { throw new RuntimeException('Human reply blocked by cooldown'); }
foreach ([1, 2] as $attempt) {
try {
$tracker->process(['mailbox' => 'support@example.com', 'message_id' => "<cooldown-mismatch-$attempt@example.com>",
'sender_email' => 'wrong@example.com', 'subject' => '[Ticket #50] Reply', 'content' => 'Mismatch'], $service);
throw new RuntimeException('Mismatch accepted');
} catch (\FOSSBilling\InformationException) {}
}
if (count($mail->emails) !== 2 || $mail->emails[1]['code'] !== 'mailgateway_sender_mismatch') {
throw new RuntimeException('Mismatch cooldown failed');
}
$support->fail = true;
try { $service->import($input); } catch (RuntimeException $error) {
if ($error->getMessage() !== 'Support failed') { throw $error; }
}
if ($di['mod_service'] !== $lookup) { throw new RuntimeException('Failure left email interception active'); }
echo "Passed persistent cooldown, five-minute polls, expiry, failed/disabled queues, nested rollback, guest acknowledgements, mismatch notifications, human replies, and service restoration.\n";