Skip to content

Commit bf29a75

Browse files
Merge pull request #12141 from IONOS-Productivity/mk/dev/test_setup_service
add unit tests for SetupService
2 parents 2d31bb3 + 3ec74a7 commit bf29a75

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\Tests\Unit\Service;
11+
12+
use ChristophWurst\Nextcloud\Testing\TestCase;
13+
use Horde_Imap_Client_Exception;
14+
use Horde_Imap_Client_Socket;
15+
use Horde_Mail_Exception;
16+
use Horde_Mail_Transport_Smtphorde;
17+
use InvalidArgumentException;
18+
use OCA\Mail\Account;
19+
use OCA\Mail\Db\MailAccount;
20+
use OCA\Mail\Db\TagMapper;
21+
use OCA\Mail\Exception\CouldNotConnectException;
22+
use OCA\Mail\IMAP\IMAPClientFactory;
23+
use OCA\Mail\Service\AccountService;
24+
use OCA\Mail\Service\SetupService;
25+
use OCA\Mail\SMTP\SmtpClientFactory;
26+
use OCP\Security\ICrypto;
27+
use PHPUnit\Framework\MockObject\MockObject;
28+
use Psr\Log\LoggerInterface;
29+
30+
class SetupServiceTest extends TestCase {
31+
private const ACCOUNT_NAME = 'Test Account';
32+
private const EMAIL_ADDRESS = '[email protected]';
33+
private const IMAP_HOST = 'imap.example.com';
34+
private const IMAP_PORT = 993;
35+
private const IMAP_SSL_MODE = 'ssl';
36+
private const IMAP_USER = '[email protected]';
37+
private const IMAP_PASSWORD = 'imap-password';
38+
private const SMTP_HOST = 'smtp.example.com';
39+
private const SMTP_PORT = 465;
40+
private const SMTP_SSL_MODE = 'ssl';
41+
private const SMTP_USER = '[email protected]';
42+
private const SMTP_PASSWORD = 'smtp-password';
43+
private const USER_ID = 'user123';
44+
private const AUTH_METHOD_PASSWORD = 'password';
45+
private const AUTH_METHOD_OAUTH2 = 'xoauth2';
46+
47+
private AccountService&MockObject $accountService;
48+
private ICrypto&MockObject $crypto;
49+
private SmtpClientFactory&MockObject $smtpClientFactory;
50+
private IMAPClientFactory&MockObject $imapClientFactory;
51+
private LoggerInterface&MockObject $logger;
52+
private TagMapper&MockObject $tagMapper;
53+
private SetupService $setupService;
54+
55+
protected function setUp(): void {
56+
parent::setUp();
57+
58+
$this->accountService = $this->createMock(AccountService::class);
59+
$this->crypto = $this->createMock(ICrypto::class);
60+
$this->smtpClientFactory = $this->createMock(SmtpClientFactory::class);
61+
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
62+
$this->logger = $this->createMock(LoggerInterface::class);
63+
$this->tagMapper = $this->createMock(TagMapper::class);
64+
65+
$this->setupService = new SetupService(
66+
$this->accountService,
67+
$this->crypto,
68+
$this->smtpClientFactory,
69+
$this->imapClientFactory,
70+
$this->logger,
71+
$this->tagMapper
72+
);
73+
}
74+
75+
private function mockSuccessfulImapConnection(): Horde_Imap_Client_Socket&MockObject {
76+
$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
77+
$imapClient->expects(self::once())->method('login');
78+
$imapClient->expects(self::once())->method('logout');
79+
80+
$this->imapClientFactory->expects(self::once())
81+
->method('getClient')
82+
->willReturn($imapClient);
83+
84+
return $imapClient;
85+
}
86+
87+
private function mockSuccessfulSmtpConnection(): Horde_Mail_Transport_Smtphorde&MockObject {
88+
$smtpTransport = $this->createMock(Horde_Mail_Transport_Smtphorde::class);
89+
$smtpTransport->expects(self::once())->method('getSMTPObject');
90+
91+
$this->smtpClientFactory->expects(self::once())
92+
->method('create')
93+
->willReturn($smtpTransport);
94+
95+
return $smtpTransport;
96+
}
97+
98+
private function mockPasswordEncryption(): void {
99+
$this->crypto->expects(self::exactly(2))
100+
->method('encrypt')
101+
->willReturnOnConsecutiveCalls('encrypted-imap-password', 'encrypted-smtp-password');
102+
}
103+
104+
private function assertAccountPropertiesMatch(
105+
MailAccount $account,
106+
string $accountName,
107+
string $emailAddress,
108+
string $imapHost,
109+
int $imapPort,
110+
string $imapSslMode,
111+
string $imapUser,
112+
string $smtpHost,
113+
int $smtpPort,
114+
string $smtpSslMode,
115+
string $smtpUser,
116+
string $uid,
117+
string $authMethod,
118+
): void {
119+
self::assertSame($accountName, $account->getName(), 'Account name does not match');
120+
self::assertSame($emailAddress, $account->getEmail(), 'Email address does not match');
121+
self::assertSame($imapHost, $account->getInboundHost(), 'IMAP host does not match');
122+
self::assertSame($imapPort, $account->getInboundPort(), 'IMAP port does not match');
123+
self::assertSame($imapSslMode, $account->getInboundSslMode(), 'IMAP SSL mode does not match');
124+
self::assertSame($imapUser, $account->getInboundUser(), 'IMAP user does not match');
125+
self::assertSame($smtpHost, $account->getOutboundHost(), 'SMTP host does not match');
126+
self::assertSame($smtpPort, $account->getOutboundPort(), 'SMTP port does not match');
127+
self::assertSame($smtpSslMode, $account->getOutboundSslMode(), 'SMTP SSL mode does not match');
128+
self::assertSame($smtpUser, $account->getOutboundUser(), 'SMTP user does not match');
129+
self::assertSame($uid, $account->getUserId(), 'User ID does not match');
130+
self::assertSame($authMethod, $account->getAuthMethod(), 'Auth method does not match');
131+
}
132+
133+
public function testCreateNewAccountWithPasswordAuth(): void {
134+
$this->mockPasswordEncryption();
135+
136+
$this->logger->expects(self::once())
137+
->method('info')
138+
->with('Setting up manually configured account');
139+
140+
$debugCalls = [];
141+
$this->logger->expects(self::exactly(2))
142+
->method('debug')
143+
->willReturnCallback(function (string $message, array $context = []) use (&$debugCalls): void {
144+
$debugCalls[] = ['message' => $message, 'context' => $context];
145+
});
146+
147+
$this->mockSuccessfulImapConnection();
148+
$this->mockSuccessfulSmtpConnection();
149+
150+
$this->accountService->expects(self::once())
151+
->method('save')
152+
->with(self::callback(function (MailAccount $account): bool {
153+
$this->assertAccountPropertiesMatch(
154+
$account,
155+
self::ACCOUNT_NAME,
156+
self::EMAIL_ADDRESS,
157+
self::IMAP_HOST,
158+
self::IMAP_PORT,
159+
self::IMAP_SSL_MODE,
160+
self::IMAP_USER,
161+
self::SMTP_HOST,
162+
self::SMTP_PORT,
163+
self::SMTP_SSL_MODE,
164+
self::SMTP_USER,
165+
self::USER_ID,
166+
self::AUTH_METHOD_PASSWORD
167+
);
168+
return true;
169+
}));
170+
171+
$this->tagMapper->expects(self::once())
172+
->method('createDefaultTags')
173+
->with(self::isInstanceOf(MailAccount::class));
174+
175+
$result = $this->setupService->createNewAccount(
176+
self::ACCOUNT_NAME,
177+
self::EMAIL_ADDRESS,
178+
self::IMAP_HOST,
179+
self::IMAP_PORT,
180+
self::IMAP_SSL_MODE,
181+
self::IMAP_USER,
182+
self::IMAP_PASSWORD,
183+
self::SMTP_HOST,
184+
self::SMTP_PORT,
185+
self::SMTP_SSL_MODE,
186+
self::SMTP_USER,
187+
self::SMTP_PASSWORD,
188+
self::USER_ID,
189+
self::AUTH_METHOD_PASSWORD
190+
);
191+
192+
self::assertInstanceOf(Account::class, $result);
193+
194+
// Verify debug log calls
195+
self::assertCount(2, $debugCalls);
196+
self::assertSame('Connecting to account {account}', $debugCalls[0]['message']);
197+
self::assertSame(['account' => self::EMAIL_ADDRESS], $debugCalls[0]['context']);
198+
self::assertStringContainsString('account created ', $debugCalls[1]['message']);
199+
self::assertSame([], $debugCalls[1]['context']);
200+
}
201+
202+
public function testCreateNewAccountWithOAuth2(): void {
203+
$this->crypto->expects(self::never())->method('encrypt');
204+
205+
$this->logger->expects(self::once())
206+
->method('info')
207+
->with('Setting up manually configured account');
208+
$this->logger->expects(self::once())
209+
->method('debug')
210+
->with(self::stringContains('account created '));
211+
212+
$this->imapClientFactory->expects(self::never())->method('getClient');
213+
$this->smtpClientFactory->expects(self::never())->method('create');
214+
215+
$this->accountService->expects(self::once())
216+
->method('save')
217+
->with(self::callback(function (MailAccount $account): bool {
218+
return $account->getAuthMethod() === self::AUTH_METHOD_OAUTH2;
219+
}));
220+
221+
$this->tagMapper->expects(self::once())->method('createDefaultTags');
222+
223+
$result = $this->setupService->createNewAccount(
224+
'OAuth2 Account',
225+
226+
self::IMAP_HOST,
227+
self::IMAP_PORT,
228+
self::IMAP_SSL_MODE,
229+
230+
null,
231+
self::SMTP_HOST,
232+
self::SMTP_PORT,
233+
self::SMTP_SSL_MODE,
234+
235+
null,
236+
'user456',
237+
self::AUTH_METHOD_OAUTH2
238+
);
239+
240+
self::assertInstanceOf(Account::class, $result);
241+
}
242+
243+
public function testCreateNewAccountWithInvalidAuthMethod(): void {
244+
$this->expectException(InvalidArgumentException::class);
245+
$this->expectExceptionMessage('Invalid auth method invalid');
246+
247+
$this->setupService->createNewAccount(
248+
self::ACCOUNT_NAME,
249+
self::EMAIL_ADDRESS,
250+
self::IMAP_HOST,
251+
self::IMAP_PORT,
252+
self::IMAP_SSL_MODE,
253+
self::IMAP_USER,
254+
self::IMAP_PASSWORD,
255+
self::SMTP_HOST,
256+
self::SMTP_PORT,
257+
self::SMTP_SSL_MODE,
258+
self::SMTP_USER,
259+
self::SMTP_PASSWORD,
260+
self::USER_ID,
261+
'invalid'
262+
);
263+
}
264+
265+
public function testCreateNewAccountImapConnectionFailure(): void {
266+
$this->expectException(CouldNotConnectException::class);
267+
268+
$this->mockPasswordEncryption();
269+
270+
$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
271+
$imapClient->expects(self::once())
272+
->method('login')
273+
->willThrowException(new Horde_Imap_Client_Exception('Connection failed'));
274+
$imapClient->expects(self::once())
275+
->method('logout');
276+
277+
$this->imapClientFactory->expects(self::once())
278+
->method('getClient')
279+
->willReturn($imapClient);
280+
281+
$this->setupService->createNewAccount(
282+
self::ACCOUNT_NAME,
283+
self::EMAIL_ADDRESS,
284+
self::IMAP_HOST,
285+
self::IMAP_PORT,
286+
self::IMAP_SSL_MODE,
287+
self::IMAP_USER,
288+
self::IMAP_PASSWORD,
289+
self::SMTP_HOST,
290+
self::SMTP_PORT,
291+
self::SMTP_SSL_MODE,
292+
self::SMTP_USER,
293+
self::SMTP_PASSWORD,
294+
self::USER_ID,
295+
self::AUTH_METHOD_PASSWORD
296+
);
297+
}
298+
299+
public function testCreateNewAccountSmtpConnectionFailure(): void {
300+
$this->expectException(CouldNotConnectException::class);
301+
302+
$this->mockPasswordEncryption();
303+
$this->mockSuccessfulImapConnection();
304+
305+
$smtpTransport = $this->createMock(Horde_Mail_Transport_Smtphorde::class);
306+
$smtpTransport->expects(self::once())
307+
->method('getSMTPObject')
308+
->willThrowException(new Horde_Mail_Exception('SMTP connection failed'));
309+
310+
$this->smtpClientFactory->expects(self::once())
311+
->method('create')
312+
->willReturn($smtpTransport);
313+
314+
$this->setupService->createNewAccount(
315+
self::ACCOUNT_NAME,
316+
self::EMAIL_ADDRESS,
317+
self::IMAP_HOST,
318+
self::IMAP_PORT,
319+
self::IMAP_SSL_MODE,
320+
self::IMAP_USER,
321+
self::IMAP_PASSWORD,
322+
self::SMTP_HOST,
323+
self::SMTP_PORT,
324+
self::SMTP_SSL_MODE,
325+
self::SMTP_USER,
326+
self::SMTP_PASSWORD,
327+
self::USER_ID,
328+
self::AUTH_METHOD_PASSWORD
329+
);
330+
}
331+
}

0 commit comments

Comments
 (0)