diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d58f6f073e..48c85854f2 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -53,6 +53,7 @@ use OCA\Mail\Listener\SpamReportListener; use OCA\Mail\Listener\UserDeletedListener; use OCA\Mail\Notification\Notifier; +use OCA\Mail\Provider\MailProvider; use OCA\Mail\Search\FilteringProvider; use OCA\Mail\Search\Provider; use OCA\Mail\Service\Attachment\AttachmentService; @@ -156,6 +157,9 @@ public function register(IRegistrationContext $context): void { $context->registerSearchProvider(Provider::class); } + // Added in version 4.0.0 + $context->registerMailProvider(MailProvider::class); + $context->registerNotifierService(Notifier::class); // bypass Horde Translation system diff --git a/lib/Db/MailAccountMapper.php b/lib/Db/MailAccountMapper.php index b42c1908c4..0e41bbe976 100644 --- a/lib/Db/MailAccountMapper.php +++ b/lib/Db/MailAccountMapper.php @@ -83,6 +83,27 @@ public function findByUserId(string $userId): array { return $this->findEntities($query); } + /** + * Finds a mail account(s) by user id and mail address + * + * @since 4.0.0 + * + * @param string $userId system user id + * @param string $address mail address (e.g. test@example.com) + * + * @return MailAccount[] + */ + public function findByUserIdAndAddress(string $userId, string $address): array { + $qb = $this->db->getQueryBuilder(); + $query = $qb + ->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('email', $qb->createNamedParameter($address))); + + return $this->findEntities($query); + } + /** * @throws DoesNotExistException * @throws MultipleObjectsReturnedException diff --git a/lib/Provider/Command/MessageSend.php b/lib/Provider/Command/MessageSend.php new file mode 100644 index 0000000000..6f747135a2 --- /dev/null +++ b/lib/Provider/Command/MessageSend.php @@ -0,0 +1,151 @@ +getTo()) === 0) { + throw new SendException('Invalid Message Parameter: MUST contain at least one TO address with a valid address'); + } + // validate that all To, CC and BCC have email address + $entries = array_merge($message->getTo(), $message->getCc(), $message->getBcc()); + array_walk($entries, function ($entry) { + if (empty($entry->getAddress())) { + throw new SendException('Invalid Message Parameter: All TO, CC and BCC addresses MUST contain at least an email address'); + } + }); + // validate that all attachments have a name, type, and contents + $entries = $message->getAttachments(); + array_walk($entries, function ($entry) { + if (empty($entry->getName()) || empty($entry->getType()) || empty($entry->getContents())) { + throw new SendException('Invalid Attachment Parameter: MUST contain values for Name, Type and Contents'); + } + }); + // retrieve user mail account details + try { + $account = $this->accountService->find($userId, (int)$serviceId); + } catch (ClientException $e) { + throw new SendException('Error: occurred while retrieving mail account details', 0, $e); + } + // convert mail provider message to mail app message + $localMessage = new LocalMessage(); + $localMessage->setType($localMessage::TYPE_OUTGOING); + $localMessage->setAccountId($account->getId()); + $localMessage->setSubject((string)$message->getSubject()); + $localMessage->setBody((string)$message->getBody()); + // disabled due to issues caused by opening these messages in gui + //$localMessage->setEditorBody($message->getBody()); + $localMessage->setHtml(true); + $localMessage->setSendAt($this->time->getTime()); + // convert mail provider addresses to recipient addresses + $to = $this->convertAddressArray($message->getTo()); + $cc = $this->convertAddressArray($message->getCc()); + $bcc = $this->convertAddressArray($message->getBcc()); + // save attachments + $attachments = []; + try { + foreach ($message->getAttachments() as $entry) { + $attachments[] = $this->attachmentService->addFileFromString( + $userId, + (string)$entry->getName(), + (string)$entry->getType(), + (string)$entry->getContents() + ); + } + } catch (UploadException $e) { + $this->purgeSavedAttachments($attachments); + throw new SendException('Error: occurred while saving mail message attachment', 0, $e); + } + // save message + $localMessage = $this->outboxService->saveMessage( + $account, + $localMessage, + $to, + $cc, + $bcc, + array_map(static fn (LocalAttachment $attachment) => $attachment->jsonSerialize(), $attachments) + ); + // send message + try { + $localMessage = $this->outboxService->sendMessage($localMessage, $account); + } catch (\Throwable $e) { + throw new SendException('Error: occurred while sending mail message', 0, $e); + } + + return $localMessage; + } + + /** + * Converts IAddress objects collection to plain array + * + * @since 4.0.0 + * + * @param array $addresses collection of IAddress objects + * + * @return array collection of addresses and labels + */ + protected function convertAddressArray(array $addresses): array { + return array_map(static function (IAddress $address) { + return !empty($address->getLabel()) + ? ['email' => (string)$address->getAddress(), 'label' => (string)$address->getLabel()] + : ['email' => (string)$address->getAddress()]; + }, $addresses); + } + + /** + * Removes attachments from data store + * + * @since 4.0.0 + * + * @param array $attachments collection of local attachment objects + */ + protected function purgeSavedAttachments(array $attachments): void { + foreach ($attachments as $attachment) { + $this->attachmentService->deleteAttachment($attachment->getUserId(), $attachment->getId()); + } + } + +} diff --git a/lib/Provider/MailProvider.php b/lib/Provider/MailProvider.php new file mode 100644 index 0000000000..29877ef17c --- /dev/null +++ b/lib/Provider/MailProvider.php @@ -0,0 +1,167 @@ +l10n->t('Mail Application'); + } + + /** + * Determine if any services are configured for a specific user + * + * @since 4.0.0 + * + * @param string $userId system user id + * + * @return bool true if any services are configure for the user + */ + public function hasServices(string $userId): bool { + return (count($this->listServices($userId)) > 0); + } + + /** + * Retrieve collection of services for a specific user + * + * @since 4.0.0 + * + * @param string $userId system user id + * + * @return array collection of service id and object ['1' => IServiceObject] + */ + public function listServices(string $userId): array { + // retrieve service(s) details from data store + $accounts = $this->accountService->findByUserId($userId); + // construct temporary collection + $services = []; + // add services to collection + foreach ($accounts as $entry) { + $services[(string)$entry->getId()] = $this->serviceFromAccount($userId, $entry); + } + // return list of services for user + return $services; + } + + /** + * Retrieve a service with a specific id + * + * @since 4.0.0 + * + * @param string $userId system user id + * @param string $serviceId mail account id + * + * @return IService|null returns service object or null if none found + * + */ + public function findServiceById(string $userId, string $serviceId): IService|null { + // determine if a valid user and service id was submitted + if (empty($userId) && !ctype_digit($serviceId)) { + return null; + } + // retrieve service details from data store + try { + $account = $this->accountService->find($userId, (int)$serviceId); + } catch(ClientException $e) { + $this->logger->error('Error occurred while retrieving mail account details', [ 'exception' => $e ]); + return null; + } + // return mail service object + return $this->serviceFromAccount($userId, $account); + } + + /** + * Retrieve a service for a specific mail address + * + * @since 4.0.0 + * + * @param string $userId system user id + * @param string $address mail address (e.g. test@example.com) + * + * @return IService|null returns service object or null if none found + */ + public function findServiceByAddress(string $userId, string $address): IService|null { + // retrieve service details from data store + $accounts = $this->accountService->findByUserIdAndAddress($userId, $address); + // evaluate if service details where found + if (count($accounts) > 0) { + // return mail service object + return $this->serviceFromAccount($userId, $accounts[0]); + } + + return null; + } + + /** + * Construct a new fresh service object + * + * @since 4.0.0 + * + * @return IService fresh service object + */ + public function initiateService(): IService { + return new MailService($this->container); + } + + /** + * Construct a service object from a mail account + * + * @since 4.0.0 + * + * @param string $userId system user id + * @param Account $account mail account + * + * @return IService service object + */ + protected function serviceFromAccount(string $userId, Account $account): IService { + // extract values + $serviceId = (string)$account->getId(); + $serviceLabel = $account->getName(); + $serviceAddress = new MailAddress($account->getEmail(), $account->getName()); + // return mail service object + return new MailService($this->container, $userId, $serviceId, $serviceLabel, $serviceAddress); + } + +} diff --git a/lib/Provider/MailService.php b/lib/Provider/MailService.php new file mode 100644 index 0000000000..22eb7f0b50 --- /dev/null +++ b/lib/Provider/MailService.php @@ -0,0 +1,182 @@ +serviceAbilities = ['MessageSend' => true]; + } + + /** + * Arbitrary unique text string identifying this service + * + * @since 4.0.0 + * + * @return string id of this service (e.g. 1 or service1 or anything else) + */ + public function id(): string { + return $this->serviceId; + } + + /** + * Checks if a service is able of performing an specific action + * + * @since 4.0.0 + * + * @param string $value required ability e.g. 'MessageSend' + * + * @return bool true/false if ability is supplied and found in collection + */ + public function capable(string $value): bool { + // evaluate if required ability exists + if (isset($this->serviceAbilities[$value])) { + return (bool)$this->serviceAbilities[$value]; + } + + return false; + } + + /** + * Retrieves a collection of what actions a service can perform + * + * @since 4.0.0 + * + * @return array collection of abilities otherwise empty collection + */ + public function capabilities(): array { + return $this->serviceAbilities; + } + + /** + * Gets the localized human friendly name of this service + * + * @since 4.0.0 + * + * @return string label/name of service (e.g. ACME Company Mail Service) + */ + public function getLabel(): string { + return $this->serviceLabel; + } + + /** + * Sets the localized human friendly name of this service + * + * @since 4.0.0 + * + * @param string $value label/name of service (e.g. ACME Company Mail Service) + * + * @return self return this object for command chaining + */ + public function setLabel(string $value): self { + $this->serviceLabel = $value; + return $this; + } + + /** + * Gets the primary mailing address for this service + * + * @since 4.0.0 + * + * @return IAddress mail address object + */ + public function getPrimaryAddress(): IAddress { + // retrieve and return primary service address + return $this->servicePrimaryAddress; + } + + /** + * Sets the primary mailing address for this service + * + * @since 4.0.0 + * + * @param IAddress $value mail address object + * + * @return self return this object for command chaining + */ + public function setPrimaryAddress(IAddress $value): self { + $this->servicePrimaryAddress = $value; + return $this; + } + + /** + * Gets the secondary mailing addresses (aliases) collection for this service + * + * @since 4.0.0 + * + * @return array collection of mail address object [IAddress, IAddress] + */ + public function getSecondaryAddresses(): array { + // retrieve and return secondary service addresses (aliases) collection + return $this->serviceSecondaryAddresses; + } + + /** + * Sets the secondary mailing addresses (aliases) for this service + * + * @since 4.0.0 + * + * @param IAddress ...$value collection of one or more mail address object + * + * @return self return this object for command chaining + */ + public function setSecondaryAddresses(IAddress ...$value): self { + $this->serviceSecondaryAddresses = $value; + return $this; + } + + /** + * Construct a new fresh message object + * + * @since 30.0.0 + * + * @return IMessage fresh message object + */ + public function initiateMessage(): IMessage { + return new Message(); + } + + /** + * Sends an outbound message + * + * @since 4.0.0 + * + * @param IMessage $message mail message object with all required parameters to send a message + * @param array $options array of options reserved for future use + * + * @throws SendException on failure, check message for reason + */ + public function sendMessage(IMessage $message, array $options = []): void { + /** @var MessageSend $cmd */ + $cmd = $this->container->get(MessageSend::class); + // perform action + $cmd->perform($this->userId, $this->serviceId, $message, $options); + } + +} diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 1e88ad9921..dea1e33985 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -78,6 +78,35 @@ public function findById(int $id): Account { return new Account($this->mapper->findById($id)); } + /** + * Finds a mail account by user id and mail address + * + * @since 4.0.0 + * + * @param string $userId system user id + * @param string $address mail address (e.g. test@example.com) + * + * @return Account[] + */ + public function findByUserIdAndAddress(string $userId, string $address): array { + // evaluate if cached accounts collection already exists + if (isset($this->accounts[$userId])) { + // initialize temporary collection + $list = []; + // iterate through accounts and find accounts matching mail address + foreach ($this->accounts[$userId] as $account) { + if ($account->getEmail() === $address) { + $list[] = $account; + } + } + return $list; + } + // if cached accounts collection did not exist retrieve account details directly from the data store + return array_map(static function ($a) { + return new Account($a); + }, $this->mapper->findByUserIdAndAddress($userId, $address)); + } + /** * @param string $userId * @param int $id diff --git a/tests/Unit/Provider/Command/MessageSendTest.php b/tests/Unit/Provider/Command/MessageSendTest.php new file mode 100644 index 0000000000..229f51d7a1 --- /dev/null +++ b/tests/Unit/Provider/Command/MessageSendTest.php @@ -0,0 +1,321 @@ +time = $this->createMock(ITimeFactory::class); + $this->accountService = $this->createMock(AccountService::class); + $this->outboxService = $this->createMock(OutboxService::class); + $this->attachmentService = $this->createMock(AttachmentService::class); + // construct test object + $this->commandSend = new MessageSend($this->time, $this->accountService, $this->outboxService, $this->attachmentService); + // construct mail provider attachment + $this->mailAttachment = new Attachment( + 'This is the contents of our plan', + 'plan.txt', + 'text/plain' + ); + // construct mail provider message + $this->mailMessage = new Message(); + $this->mailMessage->setFrom(new Address('user1@testing.com', 'User One')); + $this->mailMessage->setTo(new Address('user2@testing.com', 'User Two')); + $this->mailMessage->setSubject('World domination'); + $this->mailMessage->setBodyPlain('I have the most brilliant plan. Let me tell you all about it. What we do is, we'); + // construct mail app account object + $this->localAccount = new Account(new MailAccount([ + 'accountId' => 100, + 'accountName' => 'User One', + 'emailAddress' => 'user1@testing.com', + 'imapHost' => '', + 'imapPort' => '', + 'imapSslMode' => false, + 'imapUser' => '', + 'smtpHost' => '', + 'smtpPort' => '', + 'smtpSslMode' => false, + 'smtpUser' => '', + ])); + // construct mail app message object + $this->localMessageData = [ + 'type' => 0, + 'accountId' => 100, + 'subject' => 'World domination', + 'body' => 'I have the most brilliant plan. Let me tell you all about it. What we do is, we', + 'html' => true + ]; + // construct mail app attachment object + $this->localAttachmentData = [ + 'id' => null, + 'userId' => null, + 'fileName' => 'event.ics', + 'mimeType' => 'text/plain', + 'createdAt' => null, + 'localMessageId' => null + ]; + } + + public function testPerformWithAttachment(): void { + // define time factory return + $this->time->method('getTime')->willReturn(1719792000); + // define account service returns + $this->accountService->method('find')->will( + $this->returnValueMap([ + ['user1', 100, $this->localAccount] + ]) + ); + // construct mail app attachment + $localAttachmentReturned = $this->localAttachmentData; + $localAttachmentReturned['id'] = 1; + $localAttachmentReturned['userId'] = 'user1'; + $localAttachmentReturned = LocalAttachment::fromParams($localAttachmentReturned); + // define attachment service returns + $this->attachmentService->expects($this->once())->method('addFileFromString') + ->with( + 'user1', + $this->mailAttachment->getName(), + $this->mailAttachment->getType(), + $this->mailAttachment->getContents() + )->willReturn($localAttachmentReturned); + // construct mail app message objects + $localMessageFresh = $this->localMessageData; + $localMessageFresh['sendAt'] = $this->time->getTime($localMessageFresh); + $localMessageFresh = LocalMessage::fromParams($localMessageFresh); + $localMessageReturned = $this->localMessageData; + $localMessageReturned['id'] = 1; + $localMessageReturned['recipients'] = [['email' => 'use2@testing.com', 'label' => 'User Two']]; + $localMessageReturned['sendAt'] = $this->time->getTime(); + $localMessageReturned = LocalMessage::fromParams($localMessageReturned); + // define attachment service returns + $this->outboxService->expects($this->once())->method('saveMessage') + ->with( + $this->localAccount, + $localMessageFresh, + [['email' => 'user2@testing.com', 'label' => 'User Two']], + [], + [], + [$localAttachmentReturned->jsonSerialize()] + )->willReturn($localMessageReturned); + $this->outboxService->expects($this->once())->method('sendMessage') + ->with( + $localMessageReturned, + $this->localAccount, + )->willReturn($localMessageReturned); + // construct mail provider message with attachment + $mailMessage = $this->mailMessage; + $mailMessage->setAttachments($this->mailAttachment); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithOutAttachment(): void { + // define time factory return + $this->time->method('getTime')->willReturn(1719792000); + // define account service returns + $this->accountService->method('find')->will( + $this->returnValueMap([ + ['user1', 100, $this->localAccount] + ]) + ); + // construct mail app message objects + $localMessageFresh = $this->localMessageData; + $localMessageFresh['sendAt'] = $this->time->getTime($localMessageFresh); + $localMessageFresh = LocalMessage::fromParams($localMessageFresh); + $localMessageReturned = $this->localMessageData; + $localMessageReturned['id'] = 1; + $localMessageReturned['recipients'] = [['email' => 'use2@testing.com', 'label' => 'User Two']]; + $localMessageReturned['sendAt'] = $this->time->getTime(); + $localMessageReturned = LocalMessage::fromParams($localMessageReturned); + // define attachment service returns + $this->outboxService->expects($this->once())->method('saveMessage') + ->with( + $this->localAccount, + $localMessageFresh, + [['email' => 'user2@testing.com', 'label' => 'User Two']], + [], + [], + [] + )->willReturn($localMessageReturned); + $this->outboxService->expects($this->once())->method('sendMessage') + ->with( + $localMessageReturned, + $this->localAccount, + )->willReturn($localMessageReturned); + // construct mail provider message + $mailMessage = $this->mailMessage; + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithInvalidAttachment(): void { + // construct mail provider message with attachment + $mailMessage = $this->mailMessage; + $mailMessage->setAttachments(new Attachment('This is the contents of our plan', 'plan.txt', '')); + // define exception condition + $this->expectException(SendException::class); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithInvalidTo(): void { + // construct mail provider message + $mailMessage = $this->mailMessage; + $mailMessage->setTo(new Address('', 'User Two')); + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Invalid Message Parameter: All TO, CC and BCC addresses MUST contain at least an email address'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithInvalidCc(): void { + // construct mail provider message + $mailMessage = $this->mailMessage; + $mailMessage->setCc(new Address('', 'User Three')); + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Invalid Message Parameter: All TO, CC and BCC addresses MUST contain at least an email address'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithInvalidBcc(): void { + // construct mail provider message + $mailMessage = $this->mailMessage; + $mailMessage->setBcc(new Address('', 'User Three')); + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Invalid Message Parameter: All TO, CC and BCC addresses MUST contain at least an email address'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithAccountServiceFailure(): void { + // define time factory return + $this->time->method('getTime')->willReturn(1719792000); + // define account service returns + $this->accountService->method('find')->will( + $this->throwException(new ClientException('Something went wrong')) + ); + + // construct mail provider message + $mailMessage = $this->mailMessage; + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Error: occurred while retrieving mail account details'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithAttachmentServiceFailure(): void { + // define time factory return + $this->time->method('getTime')->willReturn(1719792000); + // define account service returns + $this->accountService->method('find')->will( + $this->returnValueMap([ + ['user1', 100, $this->localAccount] + ]) + ); + // define attachment service returns + $this->attachmentService->expects($this->once())->method('addFileFromString') + ->with( + 'user1', + $this->mailAttachment->getName(), + $this->mailAttachment->getType(), + $this->mailAttachment->getContents() + )->will( + $this->throwException(new UploadException('Something went wrong')) + ); + // construct mail provider message with attachment + $mailMessage = $this->mailMessage; + $mailMessage->setAttachments($this->mailAttachment); + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Error: occurred while saving mail message attachment'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } + + public function testPerformWithOutboxServiceSendFailure(): void { + // define time factory return + $this->time->method('getTime')->willReturn(1719792000); + // define account service returns + $this->accountService->method('find')->will( + $this->returnValueMap([ + ['user1', 100, $this->localAccount] + ]) + ); + // construct mail app message objects + $localMessageFresh = $this->localMessageData; + $localMessageFresh['sendAt'] = $this->time->getTime($localMessageFresh); + $localMessageFresh = LocalMessage::fromParams($localMessageFresh); + $localMessageReturned = $this->localMessageData; + $localMessageReturned['id'] = 1; + $localMessageReturned['recipients'] = [['email' => 'use2@testing.com', 'label' => 'User Two']]; + $localMessageReturned['sendAt'] = $this->time->getTime(); + $localMessageReturned = LocalMessage::fromParams($localMessageReturned); + // define outbox service returns + $this->outboxService->expects($this->once())->method('saveMessage') + ->with( + $this->localAccount, + $localMessageFresh, + [['email' => 'user2@testing.com', 'label' => 'User Two']], + [], + [], + [] + )->willReturn($localMessageReturned); + $this->outboxService->expects($this->once())->method('sendMessage') + ->with( + $localMessageReturned, + $this->localAccount, + )->will( + $this->throwException(new \Exception('Something went wrong')) + ); + // construct mail provider message + $mailMessage = $this->mailMessage; + // define exception condition + $this->expectException(SendException::class); + $this->expectExceptionMessage('Error: occurred while sending mail message'); + // test send message + $this->commandSend->perform('user1', '100', $mailMessage); + } +} diff --git a/tests/Unit/Provider/MailProviderTest.php b/tests/Unit/Provider/MailProviderTest.php new file mode 100644 index 0000000000..dd8ba23fb9 --- /dev/null +++ b/tests/Unit/Provider/MailProviderTest.php @@ -0,0 +1,218 @@ +containerInterface = $this->createMock(ContainerInterface::class); + $this->accountService = $this->createMock(AccountService::class); + $this->logger = new NullLogger(); + $this->l10n = $this->createMock(IL10N::class); + + } + + public function testId(): void { + + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test set by constructor + $this->assertEquals('mail-application', $mailProvider->id()); + + } + + public function testLabel(): void { + + // define l10n return + $this->l10n->expects(self::once())->method('t')->willReturn('Mail Application'); + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test set by constructor + $this->assertEquals('Mail Application', $mailProvider->label()); + + } + + public function testHasServices(): void { + + // construct dummy mail account + $mailAccount = new Account(new MailAccount([ + 'accountId' => 100, + 'accountName' => 'User One', + 'emailAddress' => 'user1@testing.com', + 'imapHost' => '', + 'imapPort' => '', + 'imapSslMode' => false, + 'imapUser' => '', + 'smtpHost' => '', + 'smtpPort' => '', + 'smtpSslMode' => false, + 'smtpUser' => '', + ])); + // define account services find + $this->accountService->expects($this->any())->method('findByUserId') + ->will( + $this->returnValueMap([ + ['user0', []], + ['user1', [100 => $mailAccount]] + ]) + ); + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test result with no services found + $this->assertFalse($mailProvider->hasServices('user0')); + // test result with services found + $this->assertTrue($mailProvider->hasServices('user1')); + + } + + public function testListServices(): void { + + // construct dummy mail account + $mailAccount = new Account(new MailAccount([ + 'accountId' => 100, + 'accountName' => 'User One', + 'emailAddress' => 'user1@testing.com', + 'imapHost' => '', + 'imapPort' => '', + 'imapSslMode' => false, + 'imapUser' => '', + 'smtpHost' => '', + 'smtpPort' => '', + 'smtpSslMode' => false, + 'smtpUser' => '', + ])); + // construct dummy mail service + $mailService = new MailService( + $this->containerInterface, + 'user1', + '100', + 'User One', + new MailAddress('user1@testing.com', 'User One') + ); + // define account services find + $this->accountService->expects($this->any())->method('findByUserId') + ->will( + $this->returnValueMap([ + ['user0', []], + ['user1', [$mailAccount]] + ]) + ); + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test result with no services found + $this->assertEquals([], $mailProvider->listServices('user0')); + // test result with services found + $this->assertEquals([100 => $mailService], $mailProvider->listServices('user1')); + + } + + public function testFindServiceById(): void { + + // construct dummy mail account + $mailAccount = new Account(new MailAccount([ + 'accountId' => 100, + 'accountName' => 'User One', + 'emailAddress' => 'user1@testing.com', + 'imapHost' => '', + 'imapPort' => '', + 'imapSslMode' => false, + 'imapUser' => '', + 'smtpHost' => '', + 'smtpPort' => '', + 'smtpSslMode' => false, + 'smtpUser' => '', + ])); + // construct dummy mail service + $mailService = new MailService( + $this->containerInterface, + 'user1', + '100', + 'User One', + new MailAddress('user1@testing.com', 'User One') + ); + // define account services find + $this->accountService->expects($this->any())->method('find') + ->willReturnCallback( + fn (string $userId, int $serviceId) => match (true) { + $userId === 'user1' && $serviceId === 100 => $mailAccount, + default => throw new ClientException() + } + ); + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test result with no services found + $this->assertEquals(null, $mailProvider->findServiceById('user0', '100')); + // test result with services found + $this->assertEquals($mailService, $mailProvider->findServiceById('user1', '100')); + + } + + public function testFindServiceByAddress(): void { + + // construct dummy mail account + $mailAccount = new Account(new MailAccount([ + 'accountId' => 100, + 'accountName' => 'User One', + 'emailAddress' => 'user1@testing.com', + 'imapHost' => '', + 'imapPort' => '', + 'imapSslMode' => false, + 'imapUser' => '', + 'smtpHost' => '', + 'smtpPort' => '', + 'smtpSslMode' => false, + 'smtpUser' => '', + ])); + // construct dummy mail service + $mailService = new MailService( + $this->containerInterface, + 'user1', + '100', + 'User One', + new MailAddress('user1@testing.com', 'User One') + ); + // define account services find + $this->accountService->expects($this->any())->method('findByUserIdAndAddress') + ->will( + $this->returnValueMap([ + ['user0', 'user0@testing.com', []], + ['user1', 'user1@testing.com', [$mailAccount]] + ]) + ); + // construct mail provider + $mailProvider = new MailProvider($this->containerInterface, $this->accountService, $this->logger, $this->l10n); + // test result with no services found + $this->assertEquals(null, $mailProvider->findServiceByAddress('user0', 'user0@testing.com')); + // test result with services found + $this->assertEquals($mailService, $mailProvider->findServiceByAddress('user1', 'user1@testing.com')); + + } + +} diff --git a/tests/Unit/Provider/MailServiceTest.php b/tests/Unit/Provider/MailServiceTest.php new file mode 100644 index 0000000000..301c18556c --- /dev/null +++ b/tests/Unit/Provider/MailServiceTest.php @@ -0,0 +1,90 @@ +createMock(ContainerInterface::class); + + $this->primaryAddress = new Address('test@testing.com', 'Tester'); + + $this->mailService = new MailService( + $container, + 'user1', + 'service1', + 'Mail Service', + $this->primaryAddress + ); + } + + public function testId(): void { + + $this->assertEquals('service1', $this->mailService->id()); + + } + + public function testCapable(): void { + + // test matched result + $this->assertEquals(true, $this->mailService->capable('MessageSend')); + + } + + public function testCapabilities(): void { + + // test collection result + $this->assertEquals([ + 'MessageSend' => true, + ], $this->mailService->capabilities()); + + } + + public function testLabel(): void { + + // test set by constructor + $this->assertEquals('Mail Service', $this->mailService->getLabel()); + // test set by setter + $this->mailService->setLabel('Mail Service 2'); + $this->assertEquals('Mail Service 2', $this->mailService->getLabel()); + + } + + public function testPrimaryAddress(): void { + + // test set by constructor + $this->assertEquals($this->primaryAddress, $this->mailService->getPrimaryAddress()); + // test set by setter + $address = new Address('tester@testing.com'); + $this->mailService->setPrimaryAddress($address); + $this->assertEquals($address, $this->mailService->getPrimaryAddress()); + + } + + public function testSecondaryAddresses(): void { + + // test set by setter + $address1 = new Address('test1@testing.com'); + $address2 = new Address('test2@testing.com'); + $this->mailService->setSecondaryAddresses($address1, $address2); + $this->assertEquals([$address1, $address2], $this->mailService->getSecondaryAddresses()); + + } + +}