116 lines
5.8 KiB
PHP
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";
|