Skip to content

Commit 5fd80a0

Browse files
test(service): add unit tests for SetupService
Added comprehensive unit tests for the SetupService class, covering account creation with various authentication methods, handling of connectivity tests, and validation of authentication methods. This enhances test coverage and ensures the reliability of the account setup functionality. Signed-off-by: Misha M.-Kupriyanov <[email protected]>
1 parent c37fc3e commit 5fd80a0

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
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+
return $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+
}));
169+
170+
$this->tagMapper->expects(self::once())
171+
->method('createDefaultTags')
172+
->with(self::isInstanceOf(MailAccount::class));
173+
174+
$result = $this->setupService->createNewAccount(
175+
self::ACCOUNT_NAME,
176+
self::EMAIL_ADDRESS,
177+
self::IMAP_HOST,
178+
self::IMAP_PORT,
179+
self::IMAP_SSL_MODE,
180+
self::IMAP_USER,
181+
self::IMAP_PASSWORD,
182+
self::SMTP_HOST,
183+
self::SMTP_PORT,
184+
self::SMTP_SSL_MODE,
185+
self::SMTP_USER,
186+
self::SMTP_PASSWORD,
187+
self::USER_ID,
188+
self::AUTH_METHOD_PASSWORD
189+
);
190+
191+
self::assertInstanceOf(Account::class, $result);
192+
193+
// Verify debug log calls
194+
self::assertCount(2, $debugCalls);
195+
self::assertSame('Connecting to account {account}', $debugCalls[0]['message']);
196+
self::assertSame(['account' => self::EMAIL_ADDRESS], $debugCalls[0]['context']);
197+
self::assertStringContainsString('account created ', $debugCalls[1]['message']);
198+
self::assertSame([], $debugCalls[1]['context']);
199+
}
200+
201+
public function testCreateNewAccountWithOAuth2(): void {
202+
$this->crypto->expects(self::never())->method('encrypt');
203+
204+
$this->logger->expects(self::once())
205+
->method('info')
206+
->with('Setting up manually configured account');
207+
$this->logger->expects(self::once())
208+
->method('debug')
209+
->with(self::stringContains('account created '));
210+
211+
$this->imapClientFactory->expects(self::never())->method('getClient');
212+
$this->smtpClientFactory->expects(self::never())->method('create');
213+
214+
$this->accountService->expects(self::once())
215+
->method('save')
216+
->with(self::callback(function (MailAccount $account): bool {
217+
return $account->getAuthMethod() === self::AUTH_METHOD_OAUTH2;
218+
}));
219+
220+
$this->tagMapper->expects(self::once())->method('createDefaultTags');
221+
222+
$result = $this->setupService->createNewAccount(
223+
'OAuth2 Account',
224+
225+
self::IMAP_HOST,
226+
self::IMAP_PORT,
227+
self::IMAP_SSL_MODE,
228+
229+
null,
230+
self::SMTP_HOST,
231+
self::SMTP_PORT,
232+
self::SMTP_SSL_MODE,
233+
234+
null,
235+
'user456',
236+
self::AUTH_METHOD_OAUTH2
237+
);
238+
239+
self::assertInstanceOf(Account::class, $result);
240+
}
241+
242+
public function testCreateNewAccountWithInvalidAuthMethod(): void {
243+
$this->expectException(InvalidArgumentException::class);
244+
$this->expectExceptionMessage('Invalid auth method invalid');
245+
246+
$this->setupService->createNewAccount(
247+
self::ACCOUNT_NAME,
248+
self::EMAIL_ADDRESS,
249+
self::IMAP_HOST,
250+
self::IMAP_PORT,
251+
self::IMAP_SSL_MODE,
252+
self::IMAP_USER,
253+
self::IMAP_PASSWORD,
254+
self::SMTP_HOST,
255+
self::SMTP_PORT,
256+
self::SMTP_SSL_MODE,
257+
self::SMTP_USER,
258+
self::SMTP_PASSWORD,
259+
self::USER_ID,
260+
'invalid'
261+
);
262+
}
263+
264+
public function testCreateNewAccountImapConnectionFailure(): void {
265+
$this->expectException(CouldNotConnectException::class);
266+
267+
$this->mockPasswordEncryption();
268+
269+
$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
270+
$imapClient->expects(self::once())
271+
->method('login')
272+
->willThrowException(new Horde_Imap_Client_Exception('Connection failed'));
273+
$imapClient->expects(self::once())
274+
->method('logout');
275+
276+
$this->imapClientFactory->expects(self::once())
277+
->method('getClient')
278+
->willReturn($imapClient);
279+
280+
$this->setupService->createNewAccount(
281+
self::ACCOUNT_NAME,
282+
self::EMAIL_ADDRESS,
283+
self::IMAP_HOST,
284+
self::IMAP_PORT,
285+
self::IMAP_SSL_MODE,
286+
self::IMAP_USER,
287+
self::IMAP_PASSWORD,
288+
self::SMTP_HOST,
289+
self::SMTP_PORT,
290+
self::SMTP_SSL_MODE,
291+
self::SMTP_USER,
292+
self::SMTP_PASSWORD,
293+
self::USER_ID,
294+
self::AUTH_METHOD_PASSWORD
295+
);
296+
}
297+
298+
public function testCreateNewAccountSmtpConnectionFailure(): void {
299+
$this->expectException(CouldNotConnectException::class);
300+
301+
$this->mockPasswordEncryption();
302+
$this->mockSuccessfulImapConnection();
303+
304+
$smtpTransport = $this->createMock(Horde_Mail_Transport_Smtphorde::class);
305+
$smtpTransport->expects(self::once())
306+
->method('getSMTPObject')
307+
->willThrowException(new Horde_Mail_Exception('SMTP connection failed'));
308+
309+
$this->smtpClientFactory->expects(self::once())
310+
->method('create')
311+
->willReturn($smtpTransport);
312+
313+
$this->setupService->createNewAccount(
314+
self::ACCOUNT_NAME,
315+
self::EMAIL_ADDRESS,
316+
self::IMAP_HOST,
317+
self::IMAP_PORT,
318+
self::IMAP_SSL_MODE,
319+
self::IMAP_USER,
320+
self::IMAP_PASSWORD,
321+
self::SMTP_HOST,
322+
self::SMTP_PORT,
323+
self::SMTP_SSL_MODE,
324+
self::SMTP_USER,
325+
self::SMTP_PASSWORD,
326+
self::USER_ID,
327+
self::AUTH_METHOD_PASSWORD
328+
);
329+
}
330+
}

0 commit comments

Comments
 (0)