Merge pull request #42309 from nextcloud/enh/meeting-status-automation

fix(userstatus): set user status to 'In a meeting' if calendar is busy
pull/42329/head
Anna 5 months ago committed by GitHub
commit 94a5e6c7b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -99,7 +99,6 @@ return array(
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => $baseDir . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
'OCA\\DAV\\CalDAV\\Status\\Status' => $baseDir . '/../lib/CalDAV/Status/Status.php',
'OCA\\DAV\\CalDAV\\Status\\StatusService' => $baseDir . '/../lib/CalDAV/Status/StatusService.php',
'OCA\\DAV\\CalDAV\\TimezoneService' => $baseDir . '/../lib/CalDAV/TimezoneService.php',
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',

@ -114,7 +114,6 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
'OCA\\DAV\\CalDAV\\Status\\Status' => __DIR__ . '/..' . '/../lib/CalDAV/Status/Status.php',
'OCA\\DAV\\CalDAV\\Status\\StatusService' => __DIR__ . '/..' . '/../lib/CalDAV/Status/StatusService.php',
'OCA\\DAV\\CalDAV\\TimezoneService' => __DIR__ . '/..' . '/../lib/CalDAV/TimezoneService.php',
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',

@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Anna Larch <anna.larch@gmx.net>
*
* @author Anna Larch <anna.larch@gmx.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Status;
class Status {
public function __construct(private string $status = '', private ?string $message = null, private ?string $customMessage = null, private ?int $timestamp = null, private ?string $customEmoji = null) {
}
public function getStatus(): string {
return $this->status;
}
public function setStatus(string $status): void {
$this->status = $status;
}
public function getMessage(): ?string {
return $this->message;
}
public function setMessage(?string $message): void {
$this->message = $message;
}
public function getCustomMessage(): ?string {
return $this->customMessage;
}
public function setCustomMessage(?string $customMessage): void {
$this->customMessage = $customMessage;
}
public function setEndTime(?int $timestamp): void {
$this->timestamp = $timestamp;
}
public function getEndTime(): ?int {
return $this->timestamp;
}
public function getCustomEmoji(): ?string {
return $this->customEmoji;
}
public function setCustomEmoji(?string $emoji): void {
$this->customEmoji = $emoji;
}
}

@ -25,83 +25,123 @@ declare(strict_types=1);
*/
namespace OCA\DAV\CalDAV\Status;
use DateTimeImmutable;
use OC\Calendar\CalendarQuery;
use OCA\DAV\CalDAV\CalendarImpl;
use OCA\DAV\CalDAV\FreeBusy\FreeBusyGenerator;
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
use OCA\DAV\CalDAV\Schedule\Plugin as SchedulePlugin;
use OCA\UserStatus\Service\StatusService as UserStatusService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\IManager;
use OCP\IL10N;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IUser as User;
use OCP\IUserManager;
use OCP\User\IAvailabilityCoordinator;
use OCP\UserStatus\IUserStatus;
use Psr\Log\LoggerInterface;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAVACL\Exception\NeedPrivileges;
use Sabre\DAVACL\Plugin as AclPlugin;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Parameter;
use Sabre\VObject\Property;
class StatusService {
private ICache $cache;
public function __construct(private ITimeFactory $timeFactory,
private IManager $calendarManager,
private InvitationResponseServer $server,
private IL10N $l10n,
private FreeBusyGenerator $generator) {
private IUserManager $userManager,
private UserStatusService $userStatusService,
private IAvailabilityCoordinator $availabilityCoordinator,
private ICacheFactory $cacheFactory,
private LoggerInterface $logger) {
$this->cache = $cacheFactory->createLocal('CalendarStatusService');
}
public function processCalendarAvailability(User $user): ?Status {
$userId = $user->getUID();
$email = $user->getEMailAddress();
if($email === null) {
return null;
public function processCalendarStatus(string $userId): void {
$user = $this->userManager->get($userId);
if($user === null) {
return;
}
$server = $this->server->getServer();
$availability = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
if($availability !== null && $this->availabilityCoordinator->isInEffect($availability)) {
$this->logger->debug('An Absence is in effect, skipping calendar status check', ['user' => $userId]);
return;
}
/** @var SchedulePlugin $schedulingPlugin */
$schedulingPlugin = $server->getPlugin('caldav-schedule');
$caldavNS = '{'.$schedulingPlugin::NS_CALDAV.'}';
$calendarEvents = $this->cache->get($userId);
if($calendarEvents === null) {
$calendarEvents = $this->getCalendarEvents($user);
$this->cache->set($userId, $calendarEvents, 300);
}
/** @var AclPlugin $aclPlugin */
$aclPlugin = $server->getPlugin('acl');
if ('mailto:' === substr($email, 0, 7)) {
$email = substr($email, 7);
if(empty($calendarEvents)) {
$this->userStatusService->revertUserStatus($userId, IUserStatus::MESSAGE_CALENDAR_BUSY);
$this->logger->debug('No calendar events found for status check', ['user' => $userId]);
return;
}
$result = $aclPlugin->principalSearch(
['{http://sabredav.org/ns}email-address' => $email],
[
'{DAV:}principal-URL',
$caldavNS.'calendar-home-set',
$caldavNS.'schedule-inbox-URL',
'{http://sabredav.org/ns}email-address',
]
);
$userStatusTimestamp = null;
$currentStatus = null;
try {
$currentStatus = $this->userStatusService->findByUserId($userId);
$userStatusTimestamp = $currentStatus->getIsUserDefined() ? $currentStatus->getStatusTimestamp() : null;
} catch (DoesNotExistException) {
}
if (!count($result) || !isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
return null;
if($currentStatus !== null && $currentStatus->getMessageId() === IUserStatus::MESSAGE_CALL
|| $currentStatus !== null && $currentStatus->getStatus() === IUserStatus::DND
|| $currentStatus !== null && $currentStatus->getStatus() === IUserStatus::INVISIBLE) {
// We don't overwrite the call status, DND status or Invisible status
$this->logger->debug('Higher priority status detected, skipping calendar status change', ['user' => $userId]);
return;
}
$inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
// Filter events to see if we have any that apply to the calendar status
$applicableEvents = array_filter($calendarEvents, function (array $calendarEvent) use ($userStatusTimestamp) {
$component = $calendarEvent['objects'][0];
if(isset($component['X-NEXTCLOUD-OUT-OF-OFFICE'])) {
return false;
}
if(isset($component['DTSTART']) && $userStatusTimestamp !== null) {
/** @var DateTimeImmutable $dateTime */
$dateTime = $component['DTSTART'][0];
$timestamp = $dateTime->getTimestamp();
if($userStatusTimestamp > $timestamp) {
return false;
}
}
// Ignore events that are transparent
if(isset($component['TRANSP']) && strcasecmp($component['TRANSP'][0], 'TRANSPARENT') === 0) {
return false;
}
return true;
});
// Do we have permission?
try {
$aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
} catch (NeedPrivileges | NotAuthenticated $exception) {
return null;
if(empty($applicableEvents)) {
$this->userStatusService->revertUserStatus($userId, IUserStatus::MESSAGE_CALENDAR_BUSY);
$this->logger->debug('No status relevant events found, skipping calendar status change', ['user' => $userId]);
return;
}
$now = $this->timeFactory->now();
$calendarTimeZone = $now->getTimezone();
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId);
// One event that fulfills all status conditions is enough
// 1. Not an OOO event
// 2. Current user status was not set after the start of this event
// 3. Event is not set to be transparent
$count = count($applicableEvents);
$this->logger->debug("Found $count applicable event(s), changing user status", ['user' => $userId]);
$this->userStatusService->setUserStatus(
$userId,
IUserStatus::AWAY,
IUserStatus::MESSAGE_CALENDAR_BUSY,
true
);
}
private function getCalendarEvents(User $user): array {
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $user->getUID());
if(empty($calendars)) {
return null;
return [];
}
$query = $this->calendarManager->newQuery('principals/users/' . $userId);
$query = $this->calendarManager->newQuery('principals/users/' . $user->getUID());
foreach ($calendars as $calendarObject) {
// We can only work with a calendar if it exposes its scheduling information
if (!$calendarObject instanceof CalendarImpl) {
@ -114,83 +154,20 @@ class StatusService {
// ignore it for free-busy purposes.
continue;
}
/** @var Component\VTimeZone|null $ctz */
$ctz = $calendarObject->getSchedulingTimezone();
if ($ctz !== null) {
$calendarTimeZone = $ctz->getTimeZone();
}
$query->addSearchCalendar($calendarObject->getUri());
}
$calendarEvents = [];
$dtStart = $now;
$dtEnd = \DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime('+10 minutes'));
$dtStart = DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime());
$dtEnd = DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime('+5 minutes'));
// Only query the calendars when there's any to search
if($query instanceof CalendarQuery && !empty($query->getCalendarUris())) {
// Query the next hour
$query->setTimerangeStart($dtStart);
$query->setTimerangeEnd($dtEnd);
$calendarEvents = $this->calendarManager->searchForPrincipal($query);
return $this->calendarManager->searchForPrincipal($query);
}
// @todo we can cache that
if(empty($calendarEvents)) {
return null;
}
$calendar = $this->generator->getVCalendar();
foreach ($calendarEvents as $calendarEvent) {
$vEvent = new VEvent($calendar, 'VEVENT');
foreach($calendarEvent['objects'] as $component) {
foreach ($component as $key => $value) {
$vEvent->add($key, $value[0]);
}
}
$calendar->add($vEvent);
}
$calendar->METHOD = 'REQUEST';
$this->generator->setObjects($calendar);
$this->generator->setTimeRange($dtStart, $dtEnd);
$this->generator->setTimeZone($calendarTimeZone);
$result = $this->generator->getResult();
if (!isset($result->VFREEBUSY)) {
return null;
}
/** @var Component $freeBusyComponent */
$freeBusyComponent = $result->VFREEBUSY;
$freeBusyProperties = $freeBusyComponent->select('FREEBUSY');
// If there is no FreeBusy property, the time-range is empty and available
if (count($freeBusyProperties) === 0) {
return null;
}
/** @var Property $freeBusyProperty */
$freeBusyProperty = $freeBusyProperties[0];
if (!$freeBusyProperty->offsetExists('FBTYPE')) {
// If there is no FBTYPE, it means it's busy from a regular event
return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY);
}
// If we can't deal with the FBTYPE (custom properties are a possibility)
// we should ignore it and leave the current status
$fbTypeParameter = $freeBusyProperty->offsetGet('FBTYPE');
if (!($fbTypeParameter instanceof Parameter)) {
return null;
}
$fbType = $fbTypeParameter->getValue();
switch ($fbType) {
// Ignore BUSY-UNAVAILABLE, that's for the automation
case 'BUSY':
case 'BUSY-TENTATIVE':
return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, $this->l10n->t('In a meeting'));
default:
return null;
}
return [];
}
}

@ -24,31 +24,31 @@ namespace OCA\DAV\Tests\unit\CalDAV\Status;
use OC\Calendar\CalendarQuery;
use OCA\DAV\CalDAV\CalendarImpl;
use OCA\DAV\CalDAV\FreeBusy\FreeBusyGenerator;
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
use OCA\DAV\CalDAV\Schedule\Plugin;
use OCA\DAV\CalDAV\Status\StatusService;
use OCA\DAV\Connector\Sabre\Server;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Service\StatusService as UserStatusService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\IManager;
use OCP\IL10N;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IUser;
use OCP\IUserManager;
use OCP\User\IAvailabilityCoordinator;
use OCP\User\IOutOfOfficeData;
use OCP\UserStatus\IUserStatus;
use PHPUnit\Framework\MockObject\MockObject;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL\Exception\NeedPrivileges;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VTimeZone;
use Sabre\VObject\Reader;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class StatusServiceTest extends TestCase {
private ITimeFactory|MockObject $timeFactory;
private IManager|MockObject $calendarManager;
private InvitationResponseServer|MockObject $server;
private IL10N|MockObject $l10n;
private FreeBusyGenerator|MockObject $generator;
private IUserManager|MockObject $userManager;
private UserStatusService|MockObject $userStatusService;
private IAvailabilityCoordinator|MockObject $availabilityCoordinator;
private ICacheFactory|MockObject $cacheFactory;
private LoggerInterface|MockObject $logger;
private StatusService $service;
protected function setUp(): void {
@ -56,644 +56,360 @@ class StatusServiceTest extends TestCase {
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->calendarManager = $this->createMock(IManager::class);
$this->server = $this->createMock(InvitationResponseServer::class);
$this->l10n = $this->createMock(IL10N::class);
$this->generator = $this->createMock(FreeBusyGenerator::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->userStatusService = $this->createMock(UserStatusService::class);
$this->availabilityCoordinator = $this->createMock(IAvailabilityCoordinator::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->cache = $this->createMock(ICache::class);
$this->cacheFactory->expects(self::once())
->method('createLocal')
->with('CalendarStatusService')
->willReturn($this->cache);
$this->service = new StatusService($this->timeFactory,
$this->calendarManager,
$this->server,
$this->l10n,
$this->generator);
$this->userManager,
$this->userStatusService,
$this->availabilityCoordinator,
$this->cacheFactory,
$this->logger,
);
}
public function testNoEmail(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => null,
]);
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
public function testNoUser(): void {
$this->userManager->expects(self::once())
->method('get')
->willReturn(null);
$this->server->expects(self::never())
->method('getServer');
$this->timeFactory->expects(self::never())
->method('now');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->availabilityCoordinator->expects(self::never())
->method('getCurrentOutOfOfficeData');
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->logger->expects(self::never())
->method('debug');
$this->cache->expects(self::never())
->method('get');
$this->cache->expects(self::never())
->method('set');
$this->calendarManager->expects(self::never())
->method('getCalendarsForPrincipal');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->userStatusService->expects(self::never())
->method('findByUserId');
$this->service->processCalendarStatus('admin');
}
public function testNoAcl(): void {
public function testOOOInEffect(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn([]);
$aclPlugin->expects(self::never())
->method('checkPrivileges');
$this->timeFactory->expects(self::never())
->method('now');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn($this->createMock(IOutOfOfficeData::class));
$this->availabilityCoordinator->expects(self::once())
->method('isInEffect')
->willReturn(true);
$this->logger->expects(self::once())
->method('debug');
$this->cache->expects(self::never())
->method('get');
$this->cache->expects(self::never())
->method('set');
$this->calendarManager->expects(self::never())
->method('getCalendarsForPrincipal');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->userStatusService->expects(self::never())
->method('findByUserId');
$this->service->processCalendarStatus('admin');
}
public function testNoInbox(): void {
public function testNoCalendars(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->willReturn([]);
$aclPlugin->expects(self::never())
->method('checkPrivileges');
$this->timeFactory->expects(self::never())
->method('now');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('getCalendarsForPrincipal');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
}
public function testNoPrivilegesAcl(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$principal = 'principals/users/admin';
$calendarHome = $this->createMock(LocalHref::class);
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willThrowException(new NeedPrivileges($principal, ['{DAV:}all']));
$this->timeFactory->expects(self::never())
->method('now');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('getCalendarsForPrincipal');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
$this->userStatusService->expects(self::once())
->method('revertUserStatus');
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->userStatusService->expects(self::never())
->method('findByUserId');
$this->service->processCalendarStatus('admin');
}
public function testNotAuthenticated(): void {
public function testNoCalendarEvents(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$calendarHome = $this->createMock(LocalHref::class);
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willThrowException(new NotAuthenticated());
$this->timeFactory->expects(self::never())
->method('now');
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('getCalendarsForPrincipal');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->willReturn([$this->createMock(CalendarImpl::class)]);
$this->calendarManager->expects(self::once())
->method('newQuery')
->willReturn(new CalendarQuery('admin'));
$this->timeFactory->expects(self::exactly(2))
->method('getDateTime')
->willReturn(new \DateTime());
$this->calendarManager->expects(self::once())
->method('searchForPrincipal')
->willReturn([]);
$this->userStatusService->expects(self::once())
->method('revertUserStatus');
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->userStatusService->expects(self::never())
->method('findByUserId');
$this->service->processCalendarStatus('admin');
}
public function testNoCalendars(): void {
public function testCalendarEvent(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$calendarHome = $this->createMock(LocalHref::class);
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$now = new \DateTimeImmutable('1970-1-1', new \DateTimeZone('UTC'));
$principal = 'principals/users/admin';
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('now')
->willReturn($now);
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->with($principal)
->willReturn([]);
$this->timeFactory->expects(self::never())
->method('getDateTime');
$this->calendarManager->expects(self::never())
->method('newQuery');
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
->willReturn([$this->createMock(CalendarImpl::class)]);
$this->calendarManager->expects(self::once())
->method('newQuery')
->willReturn(new CalendarQuery('admin'));
$this->timeFactory->expects(self::exactly(2))
->method('getDateTime')
->willReturn(new \DateTime());
$this->userStatusService->expects(self::once())
->method('findByUserId')
->willThrowException(new DoesNotExistException(''));
$this->calendarManager->expects(self::once())
->method('searchForPrincipal')
->willReturn([['objects' => [[]]]]);
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::once())
->method('setUserStatus');
$this->service->processCalendarStatus('admin');
}
public function testEmptyAvailabilityAndNoSearchCalendars(): void {
public function testCallStatus(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$calendarHome = $this->createMock(LocalHref::class);
$now = new \DateTimeImmutable('1970-1-1', new \DateTimeZone('UTC'));
$inTenMinutes = new \DateTime('1970-1-1 01:00');
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$principal = 'principals/users/admin';
$calendar = $this->createMock(CalendarImpl::class);
$query = $this->createMock(CalendarQuery::class);
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('now')
->willReturn($now);
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->with($principal)
->willReturn([$calendar]);
->willReturn([$this->createMock(CalendarImpl::class)]);
$this->calendarManager->expects(self::once())
->method('newQuery')
->with($principal)
->willReturn($query);
$calendar->expects(self::once())
->method('getSchedulingTransparency')
->willReturn(new ScheduleCalendarTransp('transparent'));
$this->timeFactory->expects(self::once())
->willReturn(new CalendarQuery('admin'));
$this->timeFactory->expects(self::exactly(2))
->method('getDateTime')
->with('+10 minutes')
->willReturn($inTenMinutes);
$this->calendarManager->expects(self::never())
->method('searchForPrincipal');
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
->willReturn(new \DateTime());
$this->calendarManager->expects(self::once())
->method('searchForPrincipal')
->willReturn([['objects' => [[]]]]);
$userStatus = new UserStatus();
$userStatus->setMessageId(IUserStatus::MESSAGE_CALL);
$userStatus->setStatusTimestamp(123456);
$this->userStatusService->expects(self::once())
->method('findByUserId')
->willReturn($userStatus);
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->service->processCalendarStatus('admin');
}
public function testEmptyAvailabilityAndSearchCalendarsNoResults(): void {
public function testInvisibleStatus(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$availability = '';
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$calendarHome = $this->createMock(LocalHref::class);
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$now = new \DateTimeImmutable('1970-1-1 00:00', new \DateTimeZone('UTC'));
$inTenMinutes = new \DateTime('1970-1-1 01:00');
$immutableInTenMinutes = \DateTimeImmutable::createFromMutable($inTenMinutes);
$principal = 'principals/users/admin';
$query = $this->createMock(CalendarQuery::class);
$timezone = new \DateTimeZone('UTC');
$timezoneObj = $this->createMock(VTimeZone::class);
$calendar = $this->createMock(CalendarImpl::class);
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with(['{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('now')
->willReturn($now);
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->with($principal)
->willReturn([$calendar]);
->willReturn([$this->createMock(CalendarImpl::class)]);
$this->calendarManager->expects(self::once())
->method('newQuery')
->with($principal)
->willReturn($query);
$calendar->expects(self::once())
->method('getSchedulingTransparency')
->willReturn(new ScheduleCalendarTransp('opaque'));
$calendar->expects(self::once())
->method('getSchedulingTimezone')
->willReturn($timezoneObj);
$timezoneObj->expects(self::once())
->method('getTimeZone')
->willReturn($timezone);
$calendar->expects(self::once())
->method('getUri');
$query->expects(self::once())
->method('addSearchCalendar');
$query->expects(self::once())
->method('getCalendarUris')
->willReturn([$calendar]);
$this->timeFactory->expects(self::once())
->willReturn(new CalendarQuery('admin'));
$this->timeFactory->expects(self::exactly(2))
->method('getDateTime')
->with('+10 minutes')
->willReturn($inTenMinutes);
$query->expects(self::once())
->method('setTimerangeStart')
->with($now);
$query->expects(self::once())
->method('setTimerangeEnd')
->with($immutableInTenMinutes);
->willReturn(new \DateTime());
$this->calendarManager->expects(self::once())
->method('searchForPrincipal')
->with($query)
->willReturn([]);
$this->generator->expects(self::never())
->method('getVCalendar');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
->willReturn([['objects' => [[]]]]);
$userStatus = new UserStatus();
$userStatus->setStatus(IUserStatus::INVISIBLE);
$userStatus->setStatusTimestamp(123456);
$this->userStatusService->expects(self::once())
->method('findByUserId')
->willReturn($userStatus);
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->service->processCalendarStatus('admin');
}
public function testSearchCalendarsNoResults(): void {
public function testDNDStatus(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$server = $this->createMock(Server::class);
$schedulingPlugin = $this->createMock(Plugin::class);
$aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
$calendarHome = $this->createMock(LocalHref::class);
$acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
$now = new \DateTimeImmutable('1970-1-1 00:00', new \DateTimeZone('UTC'));
$inTenMinutes = new \DateTime('1970-1-1 01:00');
$immutableInTenMinutes = \DateTimeImmutable::createFromMutable($inTenMinutes);
$principal = 'principals/users/admin';
$query = $this->createMock(CalendarQuery::class);
$timezone = new \DateTimeZone('UTC');
$timezoneObj = $this->createMock(VTimeZone::class);
$calendar = $this->createMock(CalendarImpl::class);
$vCalendar = $this->createMock(VCalendar::class);
$result = Reader::read('BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 4.5.3//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
END:VCALENDAR');
$user->expects(self::once())
->method('getUID')
->willReturn('admin');
$user->expects(self::once())
->method('getEMailAddress')
->willReturn('test@test.com');
$this->server->expects(self::once())
->method('getServer')
->willReturn($server);
$server->expects(self::exactly(2))
->method('getPlugin')
->withConsecutive(
['caldav-schedule'],
['acl'],
)->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
$aclPlugin->expects(self::once())
->method('principalSearch')
->with(['{http://sabredav.org/ns}email-address' => 'test@test.com'])
->willReturn($acl);
$calendarHome->expects(self::once())
->method('getHref')
->willReturn('calendars/admin/inbox/');
$aclPlugin->expects(self::once())
->method('checkPrivileges')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('now')
->willReturn($now);
$this->userManager->expects(self::once())
->method('get')
->willReturn($user);
$this->availabilityCoordinator->expects(self::once())
->method('getCurrentOutOfOfficeData')
->willReturn(null);
$this->availabilityCoordinator->expects(self::never())
->method('isInEffect');
$this->cache->expects(self::once())
->method('get')
->willReturn(null);
$this->cache->expects(self::once())
->method('set');
$this->calendarManager->expects(self::once())
->method('getCalendarsForPrincipal')
->with($principal)
->willReturn([$calendar]);
->willReturn([$this->createMock(CalendarImpl::class)]);
$this->calendarManager->expects(self::once())
->method('newQuery')
->with($principal)
->willReturn($query);
$calendar->expects(self::once())
->method('getSchedulingTransparency')
->willReturn(new ScheduleCalendarTransp('opaque'));
$calendar->expects(self::once())
->method('getSchedulingTimezone')
->willReturn($timezoneObj);
$timezoneObj->expects(self::once())
->method('getTimeZone')
->willReturn($timezone);
$calendar->expects(self::once())
->method('getUri');
$query->expects(self::once())
->method('addSearchCalendar');
$query->expects(self::once())
->method('getCalendarUris')
->willReturn([$calendar]);
$this->timeFactory->expects(self::once())
->willReturn(new CalendarQuery('admin'));
$this->timeFactory->expects(self::exactly(2))
->method('getDateTime')
->with('+10 minutes')
->willReturn($inTenMinutes);
$query->expects(self::once())
->method('setTimerangeStart')
->with($now);
$query->expects(self::once())
->method('setTimerangeEnd')
->with($immutableInTenMinutes);
->willReturn(new \DateTime());
$this->calendarManager->expects(self::once())
->method('searchForPrincipal')
->with($query)
->willReturn([]);
$this->generator->expects(self::never())
->method('getVCalendar');
$vCalendar->expects(self::never())
->method('add');
$this->generator->expects(self::never())
->method('setObjects');
$this->generator->expects(self::never())
->method('setTimeRange');
$this->generator->expects(self::never())
->method('setTimeZone');
$this->generator->expects(self::never())
->method('getResult');
$status = $this->service->processCalendarAvailability($user);
$this->assertNull($status);
->willReturn([['objects' => [[]]]]);
$userStatus = new UserStatus();
$userStatus->setStatus(IUserStatus::DND);
$userStatus->setStatusTimestamp(123456);
$this->userStatusService->expects(self::once())
->method('findByUserId')
->willReturn($userStatus);
$this->logger->expects(self::once())
->method('debug');
$this->userStatusService->expects(self::never())
->method('revertUserStatus');
$this->userStatusService->expects(self::never())
->method('setUserStatus');
$this->service->processCalendarStatus('admin');
}
}

@ -28,6 +28,7 @@ declare(strict_types=1);
*/
namespace OCA\UserStatus\Controller;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Exception\InvalidClearAtException;
use OCA\UserStatus\Exception\InvalidMessageIdException;
@ -55,6 +56,7 @@ class UserStatusController extends OCSController {
private string $userId,
private LoggerInterface $logger,
private StatusService $service,
private CalendarStatusService $calendarStatusService,
) {
parent::__construct($appName, $request);
}
@ -71,6 +73,7 @@ class UserStatusController extends OCSController {
*/
public function getStatus(): DataResponse {
try {
$this->calendarStatusService->processCalendarStatus($this->userId);
$userStatus = $this->service->findByUserId($this->userId);
} catch (DoesNotExistException $ex) {
throw new OCSNotFoundException('No status for the current user');

@ -25,6 +25,7 @@ declare(strict_types=1);
*/
namespace OCA\UserStatus\Listener;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Connector\UserStatus as ConnectorUserStatus;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Db\UserStatusMapper;
@ -48,7 +49,8 @@ class UserLiveStatusListener implements IEventListener {
public function __construct(UserStatusMapper $mapper,
StatusService $statusService,
ITimeFactory $timeFactory) {
ITimeFactory $timeFactory,
private CalendarStatusService $calendarStatusService) {
$this->mapper = $mapper;
$this->statusService = $statusService;
$this->timeFactory = $timeFactory;
@ -65,6 +67,7 @@ class UserLiveStatusListener implements IEventListener {
$user = $event->getUser();
try {
$this->calendarStatusService->processCalendarStatus($user->getUID());
$userStatus = $this->statusService->findByUserId($user->getUID());
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
@ -81,6 +84,12 @@ class UserLiveStatusListener implements IEventListener {
return;
}
// Don't overwrite the "away" calendar status if it's set
if($userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY) {
$event->setUserStatus(new ConnectorUserStatus($userStatus));
return;
}
$needsUpdate = false;
// If the current status is older than 5 minutes,

@ -27,8 +27,6 @@ declare(strict_types=1);
*/
namespace OCA\UserStatus\Service;
use OCA\DAV\CalDAV\Status\Status as CalendarStatus;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Db\UserStatusMapper;
use OCA\UserStatus\Exception\InvalidClearAtException;
@ -89,8 +87,7 @@ class StatusService {
private PredefinedStatusService $predefinedStatusService,
private IEmojiHelper $emojiHelper,
private IConfig $config,
private IUserManager $userManager,
private CalendarStatusService $calendarStatusService) {
private IUserManager $userManager) {
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
@ -558,30 +555,4 @@ class StatusService {
// For users that matched restore the previous status
$this->mapper->restoreBackupStatuses($restoreIds);
}
/**
* Calculate a users' status according to their calendar events
*
* There are 4 predefined types of FBTYPE - 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE',
* but 'X-' properties are possible
*
* @link https://icalendar.org/iCalendar-RFC-5545/3-2-9-free-busy-time-type.html
*
* The status will be changed for types
* - 'BUSY'
* - 'BUSY-TENTATIVE' (ex.: an event has been accepted tentatively)
* and all FREEBUSY components without a type (implicitly a 'BUSY' status)
*
* 'X-' properties and BUSY-UNAVAILABLE is not handled
*
* @param string $userId
* @return CalendarStatus|null
*/
public function getCalendarStatus(string $userId): ?CalendarStatus {
$user = $this->userManager->get($userId);
if ($user === null) {
return null;
}
return $this->calendarStatusService->processCalendarAvailability($user);
}
}

@ -26,6 +26,7 @@ declare(strict_types=1);
*/
namespace OCA\UserStatus\Tests\Controller;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Controller\UserStatusController;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Exception\InvalidClearAtException;
@ -47,7 +48,10 @@ class UserStatusControllerTest extends TestCase {
private $logger;
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
private $service;
private $statusService;
/** @var CalendarStatusService|\PHPUnit\Framework\MockObject\MockObject $calendarStatusService */
private $calendarStatusService;
/** @var UserStatusController */
private $controller;
@ -58,15 +62,23 @@ class UserStatusControllerTest extends TestCase {
$request = $this->createMock(IRequest::class);
$userId = 'john.doe';
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = $this->createMock(StatusService::class);
$this->controller = new UserStatusController('user_status', $request, $userId, $this->logger, $this->service);
$this->statusService = $this->createMock(StatusService::class);
$this->calendarStatusService = $this->createMock(CalendarStatusService::class);
$this->controller = new UserStatusController(
'user_status',
$request,
$userId,
$this->logger,
$this->statusService,
$this->calendarStatusService,
);
}
public function testGetStatus(): void {
$userStatus = $this->getUserStatus();
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('findByUserId')
->with('john.doe')
->willReturn($userStatus);
@ -85,7 +97,10 @@ class UserStatusControllerTest extends TestCase {
}
public function testGetStatusDoesNotExist(): void {
$this->service->expects($this->once())
$this->calendarStatusService->expects(self::once())
->method('processCalendarStatus')
->with('john.doe');
$this->statusService->expects($this->once())
->method('findByUserId')
->with('john.doe')
->willThrowException(new DoesNotExistException(''));
@ -121,12 +136,12 @@ class UserStatusControllerTest extends TestCase {
$userStatus = $this->getUserStatus();
if ($expectException) {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setStatus')
->with('john.doe', $statusType, null, true)
->willThrowException($exception);
} else {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setStatus')
->with('john.doe', $statusType, null, true)
->willReturn($userStatus);
@ -187,12 +202,12 @@ class UserStatusControllerTest extends TestCase {
$userStatus = $this->getUserStatus();
if ($expectException) {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setPredefinedMessage')
->with('john.doe', $messageId, $clearAt)
->willThrowException($exception);
} else {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setPredefinedMessage')
->with('john.doe', $messageId, $clearAt)
->willReturn($userStatus);
@ -259,28 +274,28 @@ class UserStatusControllerTest extends TestCase {
$userStatus = $this->getUserStatus();
if ($expectException) {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setCustomMessage')
->with('john.doe', $statusIcon, $message, $clearAt)
->willThrowException($exception);
} else {
if ($expectSuccessAsReset) {
$this->service->expects($this->never())
$this->statusService->expects($this->never())
->method('setCustomMessage');
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('clearMessage')
->with('john.doe');
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('findByUserId')
->with('john.doe')
->willReturn($userStatus);
} else {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('setCustomMessage')
->with('john.doe', $statusIcon, $message, $clearAt)
->willReturn($userStatus);
$this->service->expects($this->never())
$this->statusService->expects($this->never())
->method('clearMessage');
}
}
@ -326,7 +341,7 @@ class UserStatusControllerTest extends TestCase {
}
public function testClearMessage(): void {
$this->service->expects($this->once())
$this->statusService->expects($this->once())
->method('clearMessage')
->with('john.doe');

@ -26,6 +26,7 @@ declare(strict_types=1);
*/
namespace OCA\UserStatus\Tests\Listener;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Db\UserStatusMapper;
use OCA\UserStatus\Listener\UserDeletedListener;
@ -36,27 +37,37 @@ use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\GenericEvent;
use OCP\IUser;
use OCP\User\Events\UserLiveStatusEvent;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class UserLiveStatusListenerTest extends TestCase {
/** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */
/** @var UserStatusMapper|MockObject */
private $mapper;
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
/** @var StatusService|MockObject */
private $statusService;
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
/** @var ITimeFactory|MockObject */
private $timeFactory;
/** @var UserDeletedListener */
private $listener;
private CalendarStatusService|MockObject $calendarStatusService;
protected function setUp(): void {
parent::setUp();
$this->mapper = $this->createMock(UserStatusMapper::class);
$this->statusService = $this->createMock(StatusService::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->listener = new UserLiveStatusListener($this->mapper, $this->statusService, $this->timeFactory);
$this->calendarStatusService = $this->createMock(CalendarStatusService::class);
$this->listener = new UserLiveStatusListener(
$this->mapper,
$this->statusService,
$this->timeFactory,
$this->calendarStatusService,
);
}
/**

@ -28,9 +28,6 @@ namespace OCA\UserStatus\Tests\Service;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\DB\Exceptions\DbalException;
use OC\User\User;
use OCA\DAV\CalDAV\Status\Status;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Db\UserStatusMapper;
use OCA\UserStatus\Exception\InvalidClearAtException;
@ -45,7 +42,6 @@ use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\IConfig;
use OCP\IEmojiHelper;
use OCP\IUser;
use OCP\IUserManager;
use OCP\UserStatus\IUserStatus;
use PHPUnit\Framework\MockObject\MockObject;
@ -71,9 +67,6 @@ class StatusServiceTest extends TestCase {
/** @var IUserManager|MockObject */
private $userManager;
/** @var CalendarStatusService|MockObject */
private $calendarStatusService;
private StatusService $service;
protected function setUp(): void {
@ -84,8 +77,6 @@ class StatusServiceTest extends TestCase {
$this->predefinedStatusService = $this->createMock(PredefinedStatusService::class);
$this->emojiHelper = $this->createMock(IEmojiHelper::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->calendarStatusService = $this->createMock(CalendarStatusService::class);
$this->config = $this->createMock(IConfig::class);
$this->config->method('getAppValue')
@ -99,8 +90,7 @@ class StatusServiceTest extends TestCase {
$this->predefinedStatusService,
$this->emojiHelper,
$this->config,
$this->userManager,
$this->calendarStatusService,
$this->userManager
);
}
@ -156,8 +146,7 @@ class StatusServiceTest extends TestCase {
$this->predefinedStatusService,
$this->emojiHelper,
$this->config,
$this->userManager,
$this->calendarStatusService,
$this->userManager
);
$this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
@ -176,8 +165,7 @@ class StatusServiceTest extends TestCase {
$this->predefinedStatusService,
$this->emojiHelper,
$this->config,
$this->userManager,
$this->calendarStatusService,
$this->userManager
);
$this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
@ -837,35 +825,4 @@ class StatusServiceTest extends TestCase {
$this->service->revertMultipleUserStatus(['john', 'nobackup', 'backuponly', 'nobackupanddnd'], 'call');
}
public function testCalendarAvailabilityNoUser(): void {
$userId = 'admin';
$this->userManager->expects(self::once())
->method('get')
->with($userId)
->willReturn(null);
$this->calendarStatusService->expects(self::never())
->method('processCalendarAvailability');
$this->service->getCalendarStatus($userId);
}
public function testCalendarAvailabilityNoStatus(): void {
$user = $this->createConfiguredMock(IUser::class, [
'getUID' => 'admin',
'getEMailAddress' => 'test@test.com',
]);
$this->userManager->expects(self::once())
->method('get')
->with($user->getUID())
->willReturn($user);
$this->calendarStatusService->expects(self::once())
->method('processCalendarAvailability')
->with($user)
->willReturn(null);
$this->service->getCalendarStatus($user->getUID());
}
}

Loading…
Cancel
Save