diff --git a/Classes/Domain/Service/Email/SendSummaryService.php b/Classes/Domain/Service/Email/SendSummaryService.php index c8ec6619..023d3871 100644 --- a/Classes/Domain/Service/Email/SendSummaryService.php +++ b/Classes/Domain/Service/Email/SendSummaryService.php @@ -22,9 +22,6 @@ class SendSummaryService protected array $visitors; protected ?ConfigurationService $configurationService = null; - /** - * @param array $visitors - */ public function __construct(array $visitors) { $this->visitors = $visitors; @@ -32,8 +29,6 @@ public function __construct(array $visitors) } /** - * @param array $emails - * @return bool * @throws ConfigurationException * @throws EmailValidationException * @throws InvalidConfigurationTypeException @@ -53,34 +48,49 @@ public function send(array $emails): bool } /** - * @return array + * @throws ConfigurationException * @throws InvalidConfigurationTypeException */ protected function getSender(): array { - $configuration = $this->configurationService->getTypoScriptSettingsByPath('commandControllers.summaryMail'); + $configuration = $this->getSummaryMailConfiguration(); return [$configuration['fromEmail'] => $configuration['fromName']]; } /** - * @return string + * @throws ConfigurationException * @throws InvalidConfigurationTypeException */ protected function getSubject(): string { - return $this->configurationService->getTypoScriptSettingsByPath('commandControllers.summaryMail.subject'); + return $this->getSummaryMailConfiguration()['subject'] ?? ''; } /** - * @param array $assignment - * @return string + * @throws ConfigurationException + * @throws InvalidConfigurationTypeException + */ + protected function getSummaryMailConfiguration(): array + { + $configuration = $this->configurationService->getTypoScriptSettingsByPath('commandControllers.summaryMail'); + if (is_array($configuration) === false) { + throw new ConfigurationException( + 'TypoScript setting plugin.tx_lux_fe.settings.commandControllers.summaryMail could not be ' + . 'resolved. Please add the static TypoScript of EXT:lux to the TypoScript template of the ' + . 'site that is used by this command.', + 1789652586 + ); + } + return $configuration; + } + + /** + * @throws ConfigurationException * @throws InvalidConfigurationTypeException */ protected function getMailTemplate(array $assignment = []): string { - $mailTemplatePath = $this->configurationService->getTypoScriptSettingsByPath( - 'commandControllers.summaryMail.mailTemplate' - ); + $mailTemplatePath = $this->getSummaryMailConfiguration()['mailTemplate'] ?? ''; $view = GeneralUtility::makeInstance(ViewFactoryInterface::class)->create(new ViewFactoryData( templatePathAndFilename: GeneralUtility::getFileAbsFileName($mailTemplatePath), )); @@ -89,12 +99,10 @@ protected function getMailTemplate(array $assignment = []): string } /** - * @param array $emails - * @return void * @throws EmailValidationException * @throws ConfigurationException */ - protected function checkProperties(array $emails) + protected function checkProperties(array $emails): void { if ($emails === []) { throw new ConfigurationException('No emails to send given', 1524299754); diff --git a/Tests/Unit/Domain/Service/Email/SendSummaryServiceTest.php b/Tests/Unit/Domain/Service/Email/SendSummaryServiceTest.php new file mode 100644 index 00000000..751b0fe7 --- /dev/null +++ b/Tests/Unit/Domain/Service/Email/SendSummaryServiceTest.php @@ -0,0 +1,205 @@ + 'sender@domain.org', + 'fromName' => 'Sender Name', + 'subject' => 'Your lead summary', + 'mailTemplate' => 'EXT:lux/Resources/Private/Templates/Mail/SummaryMail.html', + ]; + + protected bool $resetSingletonInstances = true; + + public function setUp(): void + { + parent::setUp(); + TestingHelper::setDefaultConstants(); + } + + #[Test] + public function testGetSummaryMailConfigurationReturnsConfiguration(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + self::assertSame(self::VALID_CONFIGURATION, $service->getSummaryMailConfigurationPublic()); + } + + #[Test] + public function testGetSenderReturnsEmailAndName(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + self::assertSame(['sender@domain.org' => 'Sender Name'], $service->getSenderPublic()); + } + + #[Test] + public function testGetSubjectReturnsSubject(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + self::assertSame('Your lead summary', $service->getSubjectPublic()); + } + + #[Test] + public function testGetSubjectReturnsEmptyStringOnMissingSubject(): void + { + $configuration = self::VALID_CONFIGURATION; + unset($configuration['subject']); + $service = $this->getServiceFixture($configuration); + self::assertSame('', $service->getSubjectPublic()); + } + + public static function missingConfigurationDataProvider(): array + { + return [ + 'empty string from unresolvable typoscript path' => [''], + 'string instead of array' => ['summaryMail'], + 'null' => [null], + 'integer' => [0], + ]; + } + + #[Test] + #[DataProvider('missingConfigurationDataProvider')] + public function testGetSummaryMailConfigurationThrowsExceptionOnMissingConfiguration( + mixed $configuration + ): void { + $service = $this->getServiceFixture($configuration); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(self::MISSING_CONFIGURATION_EXCEPTION_CODE); + $service->getSummaryMailConfigurationPublic(); + } + + #[Test] + public function testGetSummaryMailConfigurationExceptionMessageNamesTypoScriptPath(): void + { + $service = $this->getServiceFixture(''); + $this->expectException(ConfigurationException::class); + $this->expectExceptionMessageMatches('~plugin\.tx_lux_fe\.settings\.commandControllers\.summaryMail~'); + $service->getSummaryMailConfigurationPublic(); + } + + #[Test] + public function testGetSenderThrowsExceptionOnMissingConfiguration(): void + { + $service = $this->getServiceFixture(''); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(self::MISSING_CONFIGURATION_EXCEPTION_CODE); + $service->getSenderPublic(); + } + + #[Test] + public function testGetSubjectThrowsExceptionOnMissingConfiguration(): void + { + $service = $this->getServiceFixture(''); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(self::MISSING_CONFIGURATION_EXCEPTION_CODE); + $service->getSubjectPublic(); + } + + #[Test] + public function testGetMailTemplateThrowsExceptionOnMissingConfiguration(): void + { + $service = $this->getServiceFixture(''); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(self::MISSING_CONFIGURATION_EXCEPTION_CODE); + $service->getMailTemplatePublic(); + } + + #[Test] + public function testConfigurationIsAlwaysResolvedFromTheSameTypoScriptPath(): void + { + $configurationService = $this->createMock(ConfigurationService::class); + $configurationService + ->expects(self::atLeastOnce()) + ->method('getTypoScriptSettingsByPath') + ->with('commandControllers.summaryMail') + ->willReturn(self::VALID_CONFIGURATION); + GeneralUtility::setSingletonInstance(ConfigurationService::class, $configurationService); + + $service = new SendSummaryServiceFixture([new Visitor()]); + $service->getSenderPublic(); + $service->getSubjectPublic(); + } + + #[Test] + public function testCheckPropertiesThrowsExceptionOnEmptyEmails(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(1524299754); + $service->checkPropertiesPublic([]); + } + + public static function invalidEmailDataProvider(): array + { + return [ + 'no email at all' => ['receiver'], + 'missing domain' => ['receiver@'], + 'missing local part' => ['@domain.org'], + 'whitespace' => ['receiver @domain.org'], + ]; + } + + #[Test] + #[DataProvider('invalidEmailDataProvider')] + public function testCheckPropertiesThrowsExceptionOnInvalidEmail(string $email): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + $this->expectException(EmailValidationException::class); + $this->expectExceptionCode(1524299869); + $service->checkPropertiesPublic([$email]); + } + + #[Test] + public function testCheckPropertiesThrowsExceptionOnMissingVisitors(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION, []); + $this->expectException(ConfigurationException::class); + $this->expectExceptionCode(1524300114); + $service->checkPropertiesPublic(['receiver@domain.org']); + } + + #[Test] + public function testCheckPropertiesPassesWithValidProperties(): void + { + $service = $this->getServiceFixture(self::VALID_CONFIGURATION); + $this->expectNotToPerformAssertions(); + $service->checkPropertiesPublic(['receiver@domain.org', 'receiver2@domain.org']); + } + + protected function getServiceFixture(mixed $configuration, ?array $visitors = null): SendSummaryServiceFixture + { + $configurationService = self::createStub(ConfigurationService::class); + $configurationService + ->method('getTypoScriptSettingsByPath') + ->willReturn($configuration); + GeneralUtility::setSingletonInstance(ConfigurationService::class, $configurationService); + return new SendSummaryServiceFixture($visitors ?? [new Visitor()]); + } +} diff --git a/Tests/Unit/Fixtures/Domain/Service/Email/SendSummaryServiceFixture.php b/Tests/Unit/Fixtures/Domain/Service/Email/SendSummaryServiceFixture.php new file mode 100644 index 00000000..5cdbb60d --- /dev/null +++ b/Tests/Unit/Fixtures/Domain/Service/Email/SendSummaryServiceFixture.php @@ -0,0 +1,58 @@ +getSummaryMailConfiguration(); + } + + /** + * @throws ConfigurationException + * @throws InvalidConfigurationTypeException + */ + public function getSenderPublic(): array + { + return $this->getSender(); + } + + /** + * @throws ConfigurationException + * @throws InvalidConfigurationTypeException + */ + public function getSubjectPublic(): string + { + return $this->getSubject(); + } + + /** + * @throws ConfigurationException + * @throws InvalidConfigurationTypeException + */ + public function getMailTemplatePublic(array $assignment = []): string + { + return $this->getMailTemplate($assignment); + } + + /** + * @throws ConfigurationException + * @throws EmailValidationException + */ + public function checkPropertiesPublic(array $emails): void + { + $this->checkProperties($emails); + } +}