setValue($ticket, 4); $ticket->setClientId(7); $ticket->setStatus('on_hold'); $helpdesk = new Helpdesk(); $helpdesk->setCanReopen(true); $ticket->setSupportHelpdesk($helpdesk); $support = new class($ticket) extends \Box\Mod\Support\Service { public function __construct(private SupportTicket $ticket) {} public function setDi(\Pimple\Container $di): void { $this->di = $di; } public function getTicketById(int $id): SupportTicket { if ($id !== 4) { throw new \FOSSBilling\InformationException('Ticket not found'); } return $this->ticket; } }; $em = new class { public array $messages = []; public function persist($message): void { if (!$message instanceof SupportTicketMessage) { throw new RuntimeException('Unexpected entity'); } (new ReflectionProperty($message, 'id'))->setValue($message, count($this->messages) + 10); $this->messages[] = $message; } public function flush(): void {} }; $di = new \Pimple\Container(); $lookup = new class { public array $ids = [7]; public function fetchFirstColumn($sql, $params) { return $this->ids; } }; $di['dbal'] = $lookup; $di['mailgateway_notification_cooldown'] = new class { public function run($sender, $queue): bool { return $queue() !== false; } }; $di['db'] = new class($client) { public function __construct(public $client) {} public function getExistingModelById($type, $id) { return $this->client; } }; $di['em'] = $em; $di['request'] = new \Symfony\Component\HttpFoundation\Request(); $di['events_manager'] = new class { public array $events = []; public function fire($data): void { $this->events[] = $data; } }; $di['logger'] = new class { public function info(...$args): void {} public function warning(...$args): void {} }; $email = new class { public array $notifications = []; public bool $fail = false; public function sendTemplate($data): void { if ($this->fail) { throw new RuntimeException('Simulated mail failure'); } $this->notifications[] = $data; } }; $di['mod_service'] = $di->protect(fn ($module) => $module === 'email' ? $email : $support); $support->setDi($di); $service->setDi($di); $reply = ['ticket_id' => '4', 'sender_email' => 'customer@example.com', 'content' => ' Customer reply ']; $result = $service->reply($reply); if ($result !== ['ticket_id' => 4, 'message_id' => 10] || $ticket->getStatus() !== 'open' || $em->messages[0]->getClientId() !== 7 || $em->messages[0]->getAdminId() !== null || $em->messages[0]->getContent() !== 'Customer reply') { throw new RuntimeException('Customer reply attribution or status incorrect'); } foreach ([[], [8], [7, 8]] as $ids) { $lookup->ids = $ids; try { $service->reply($reply); throw new RuntimeException('Wrong or ambiguous sender accepted'); } catch (\FOSSBilling\InformationException) {} } $lookup->ids = [7]; foreach ([0, -1, '4x', [], true, null, 4.5, 999] as $id) { try { $service->reply(array_replace($reply, ['ticket_id' => $id])); throw new RuntimeException('Invalid or missing ticket accepted'); } catch (\FOSSBilling\InformationException) {} } $ticket->setStatus('closed'); $helpdesk->setCanReopen(false); try { $service->reply($reply); throw new RuntimeException('Reopen restriction bypassed'); } catch (\FOSSBilling\InformationException) {} $helpdesk->setCanReopen(true); $service->reply($reply); // A guest ticket retains its guest owner even if that address later registers. $ticket->setClientId(null); $ticket->setAccessHash('private-hash'); $ticket->setAuthorEmail('guest@example.com'); $ticket->setStatus('on_hold'); $reply['sender_email'] = 'GUEST@example.com'; $service->reply($reply); $message = $em->messages[2]; if ($message->getClientId() !== null || $message->getAdminId() !== null || $ticket->getStatus() !== 'open') { throw new RuntimeException('Guest reply attribution or status incorrect'); } $reply['sender_email'] = 'other@example.com'; try { $service->reply($reply); throw new RuntimeException('Wrong guest accepted'); } catch (\FOSSBilling\InformationException) {} if (count($em->messages) !== 3) { throw new RuntimeException('Rejected reply created a message'); } if ($email->notifications !== []) { throw new RuntimeException('Direct reply processing attempted a notification'); } $service->notifySenderMismatch('other@example.com', 4); foreach ($email->notifications as $notification) { if ($notification !== [ 'to' => $notification['sender_email'], 'code' => 'mailgateway_sender_mismatch', 'sender_email' => $notification['sender_email'], 'ticket_id' => 4, ]) { throw new RuntimeException('Mismatch notification contains incorrect or private ticket data'); } } $email->fail = true; $service->notifySenderMismatch('other@example.com', 4); try { $service->reply($reply); throw new RuntimeException('Mail failure allowed a mismatched reply'); } catch (\FOSSBilling\InformationException) {} if (count($em->messages) !== 3) { throw new RuntimeException('Mail failure created a message'); } echo "Passed customer and guest reply attribution, sender ownership, ambiguity, ticket validation, and reopen policy using Support ticketReply.\n"; echo "Passed mismatch notification recipient, private data exclusion, and rejection despite mail failure.\n";