Skip to content

Commit

Permalink
feat(ocs): list accounts and aliases of current user
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
  • Loading branch information
st3iny committed Feb 27, 2025
1 parent cc97a50 commit 8122306
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 3 deletions.
70 changes: 70 additions & 0 deletions lib/Controller/AccountApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Controller;

use OCA\Mail\Account;
use OCA\Mail\Db\Alias;
use OCA\Mail\ResponseDefinitions;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;

/**
* @psalm-import-type MailAccountListResponse from ResponseDefinitions
*/
class AccountApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private readonly ?string $userId,
private readonly AccountService $accountService,
private readonly AliasesService $aliasesService,
) {
parent::__construct($appName, $request);
}

/**
* List all email accounts and their aliases of the user which is currently logged-in
*
* @return DataResponse<Http::STATUS_OK, MailAccountListResponse[], array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>
*
* 200: Account list
* 404: User was not logged in
*/
#[ApiRoute(verb: 'GET', url: '/account/list')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function list(): DataResponse {
$userId = $this->userId;
if ($userId === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

$accounts = $this->accountService->findByUserId($userId);
return new DataResponse(array_map(function (Account $account) use ($userId) {
$aliases = $this->aliasesService->findAll($account->getId(), $userId);
return [
'id' => $account->getId(),
'email' => $account->getEmail(),
'aliases' => array_map(static fn (Alias $alias) => [
'id' => $alias->getId(),
'email' => $alias->getAlias(),
'name' => $alias->getName(),
], $aliases),
];
}, $accounts));
}
}
6 changes: 3 additions & 3 deletions lib/Db/Alias.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
/**
* @method void setAccountId(int $accountId)
* @method int getAccountId()
* @method void setName(string $name)
* @method string getName()
* @method void setName(string|null $name)
* @method string|null getName()
* @method void setAlias(string $alias)
* @method string getAlias()
* @method void setSignature(string|null $signature)
Expand All @@ -37,7 +37,7 @@ class Alias extends Entity implements JsonSerializable {
/** @var int */
protected $accountId;

/** @var string */
/** @var string|null */
protected $name;

/** @var string */
Expand Down
10 changes: 10 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@
* }
*
* @psalm-type MailMessageApiAttachment = array{ name: string, mime: string, size: int<0, max>, content: string}
*
* @psalm-type MailAccountListResponse = array{
* id: int,
* email: string,
* aliases: array{
* id: int,
* email: string,
* name: ?string,
* }[]
* }
*/
class ResponseDefinitions {
}
168 changes: 168 additions & 0 deletions tests/Unit/Controller/AccountApiControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace Unit\Controller;

use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Account;
use OCA\Mail\Controller\AccountApiController;
use OCA\Mail\Db\Alias;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;

class AccountApiControllerTest extends TestCase {
private const USER_ID = 'user';

private AccountApiController $controller;

private IRequest&MockObject $request;
private AccountService&MockObject $accountService;
private AliasesService&MockObject $aliasesService;

protected function setUp(): void {
parent::setUp();

$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->aliasesService = $this->createMock(AliasesService::class);

$this->controller = new AccountApiController(
'mail',
$this->request,
self::USER_ID,
$this->accountService,
$this->aliasesService,
);
}

public function testListWithoutUser() {
$controller = new AccountApiController(
'mail',
$this->request,
null,
$this->accountService,
$this->aliasesService,
);

$this->accountService->expects(self::never())
->method('findByUserId');

$this->aliasesService->expects(self::never())
->method('findAll');

$actual = $controller->list();
$this->assertEquals(Http::STATUS_NOT_FOUND, $actual->getStatus());
}

public function testList() {
$mailAccount = new MailAccount();
$mailAccount->setId(42);
$mailAccount->setEmail('foo@bar.com');

$account = new Account($mailAccount);
$this->accountService->expects(self::once())
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);

$alias = new Alias();
$alias->setId(10);
$alias->setName('Baz');
$alias->setAlias('baz@bar.com');
$this->aliasesService->expects(self::once())
->method('findAll')
->with(42, self::USER_ID)
->willReturn([$alias]);

$actual = $this->controller->list();
$this->assertEquals(Http::STATUS_OK, $actual->getStatus());
$this->assertEquals([
[
'id' => 42,
'email' => 'foo@bar.com',
'aliases' => [
[
'id' => 10,
'email' => 'baz@bar.com',
'name' => 'Baz',
],
],
]
], $actual->getData());
}

public function testListWithAliasWithoutName() {
$mailAccount = new MailAccount();
$mailAccount->setId(42);
$mailAccount->setEmail('foo@bar.com');

$account = new Account($mailAccount);
$this->accountService->expects(self::once())
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);

$alias = new Alias();
$alias->setId(10);
$alias->setName(null);
$alias->setAlias('baz@bar.com');
$this->aliasesService->expects(self::once())
->method('findAll')
->with(42, self::USER_ID)
->willReturn([$alias]);

$actual = $this->controller->list();
$this->assertEquals(Http::STATUS_OK, $actual->getStatus());
$this->assertEquals([
[
'id' => 42,
'email' => 'foo@bar.com',
'aliases' => [
[
'id' => 10,
'email' => 'baz@bar.com',
'name' => null,
],
],
]
], $actual->getData());
}

public function testListWithoutAliases() {
$mailAccount = new MailAccount();
$mailAccount->setId(42);
$mailAccount->setEmail('foo@bar.com');

$account = new Account($mailAccount);
$this->accountService->expects(self::once())
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);

$this->aliasesService->expects(self::once())
->method('findAll')
->with(42, self::USER_ID)
->willReturn([]);

$actual = $this->controller->list();
$this->assertEquals(Http::STATUS_OK, $actual->getStatus());
$this->assertEquals([
[
'id' => 42,
'email' => 'foo@bar.com',
'aliases' => [],
]
], $actual->getData());
}

}

0 comments on commit 8122306

Please sign in to comment.