Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions lib/Controller/MailboxesApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?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\Contracts\IMailManager;
use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\ResponseDefinitions;
use OCA\Mail\Service\AccountService;
use OCP\AppFramework\Db\DoesNotExistException;
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 MailboxesApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private readonly ?string $userId,
private IMailManager $mailManager,
private readonly AccountService $accountService,
private IMailSearch $mailSearch,
) {
parent::__construct($appName, $request);
}

/**
* List all mailboxes of an account of the user which is currently logged-in
*
* @param int $accountId the mail account id
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>
*
* 200: Mailbox list
* 404: User was not logged in or account doesn't exist
*/
#[ApiRoute(verb: 'GET', url: 'ocs/mailboxes')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function list(int $accountId): DataResponse {
$userId = $this->userId;
if ($userId === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

try {
$account = $this->accountService->find($userId, $accountId);
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

$mailboxes = $this->mailManager->getMailboxes($account);
return new DataResponse($mailboxes, Http::STATUS_OK);
}



/**
* List the newest messages in a mailbox of the user which is currently logged-in
*
* @param int $mailboxId the mailbox id
* @param int $cursor the query cursor
* @param string $filter the query filter
* @param int|null $limit the number of messages to be returned, can be left ampty to get all messages
* @param string $view returns messages in requested view ('singleton' or 'threaded')
* @param string|null $v Cache buster version to guarantee unique urls (will trigger HTTP caching if set)
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array{}, array{}>
*
* 200: Message list
* 403: User cannot access this mailbox
* 404: User was not logged in
*/
#[ApiRoute(verb: 'GET', url: 'ocs/mailboxes/{mailboxId}/messages')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function listMessages(int $mailboxId,
?int $cursor = null,
?string $filter = null,
?int $limit = null,
?string $view = null): DataResponse {
$userId = $this->userId;
if ($userId === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
try {
$mailbox = $this->mailManager->getMailbox($userId, $mailboxId);
$account = $this->accountService->find($userId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}

$sort = IMailSearch::ORDER_NEWEST_FIRST;

$view = $view === 'singleton' ? IMailSearch::VIEW_SINGLETON : IMailSearch::VIEW_THREADED;

$messages = $this->mailSearch->findMessages(
$account,
$mailbox,
$sort,
$filter === '' ? null : $filter,
$cursor,
$limit,
$userId,
$view
);
return new DataResponse($messages, Http::STATUS_OK);
}
}
175 changes: 175 additions & 0 deletions tests/Unit/Controller/MailboxesApiControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?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\Contracts\IMailManager;
use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Controller\MailboxesApiController;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message as DbMessage;
use OCA\Mail\Folder;
use OCA\Mail\Service\AccountService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;

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

private MailboxesApiController $controller;

private IRequest&MockObject $request;
private IMailManager|MockObject $mailManager;
private AccountService&MockObject $accountService;
private MockObject|IMailSearch $mailSearch;

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

$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->mailManager = $this->createMock(IMailManager::class);
$this->mailSearch = $this->createMock(IMailSearch::class);

$this->controller = new MailboxesApiController(
'mail',
$this->request,
self::USER_ID,
$this->mailManager,
$this->accountService,
$this->mailSearch,

);
}

public function testListMailboxesWithoutUser() {
$controller = new MailboxesApiController(
'mail',
$this->request,
null,
$this->mailManager,
$this->accountService,
$this->mailSearch,
);

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

$accountId = 28;

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


public function testListMailboxes() {
$account = $this->createMock(Account::class);
$folder = $this->createMock(Folder::class);
$accountId = 42;
$this->accountService->expects($this->once())
->method('find')
->with($this->equalTo(self::USER_ID), $this->equalTo($accountId))
->willReturn($account);
$this->mailManager->expects($this->once())
->method('getMailboxes')
->with($this->equalTo($account))
->willReturn([
$folder
]);
$actual = $this->controller->list($accountId);

$this->assertEquals(Http::STATUS_OK, $actual->getStatus());
$this->assertEquals([$folder], $actual->getData());
}

public function testListMessagesWithoutUser() {
$controller = new MailboxesApiController(
'mail',
$this->request,
null,
$this->mailManager,
$this->accountService,
$this->mailSearch,
);

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

$accountId = 28;

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


public function testListMessages(): void {
$accountId = 100;
$mailboxId = 101;
$mailbox = new Mailbox();
$mailbox->setAccountId($accountId);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with(SELF::USER_ID, $mailboxId)
->willReturn($mailbox);
$mailAccount = new MailAccount();
$account = new Account($mailAccount);
$this->accountService->expects(self::once())
->method('find')
->with(SELF::USER_ID, $accountId)
->willReturn($account);

$messages = [
new DbMessage(),
new DbMessage(),
];
$this->mailSearch->expects(self::once())
->method('findMessages')
->with(
$account,
$mailbox,
'DESC',
null,
null,
null,
SELF::USER_ID,
'threaded',
)->willReturn($messages);

$actual = $this->controller->listMessages($mailboxId);

$this->assertEquals(Http::STATUS_OK, $actual->getStatus());
$this->assertEquals($messages, $actual->getData());
}

public function testListMessagesInvalidMailbox(): void {
$accountId = 100;
$mailboxId = 101;
$mailbox = new Mailbox();
$mailbox->setAccountId($accountId);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with(SELF::USER_ID, $mailboxId)
->willThrowException(new DoesNotExistException(''));
$this->accountService->expects(self::never())
->method('find');


$actual = $this->controller->listMessages($mailboxId);

$this->assertEquals(Http::STATUS_FORBIDDEN, $actual->getStatus());
}



}