diff options
author | Anna Larch <anna@nextcloud.com> | 2023-11-24 01:49:30 +0100 |
---|---|---|
committer | Anna Larch <anna@nextcloud.com> | 2023-11-28 10:28:06 +0100 |
commit | f19645adab404a9c2642b42ec335bf2830dd8aa7 (patch) | |
tree | 3f97f43788aadabce26c1a859c669274639fa695 /apps/dav | |
parent | 53f31498049cf0dcd61c6ef1d840801bd81f055c (diff) | |
download | nextcloud-server-f19645adab404a9c2642b42ec335bf2830dd8aa7.tar.gz nextcloud-server-f19645adab404a9c2642b42ec335bf2830dd8aa7.zip |
enh(userstatus): add OOO automation and remove calendar automation
Signed-off-by: Anna Larch <anna@nextcloud.com>
Diffstat (limited to 'apps/dav')
-rw-r--r-- | apps/dav/lib/BackgroundJob/UserStatusAutomation.php | 169 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Status/Status.php | 17 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Status/StatusService.php | 24 | ||||
-rw-r--r-- | apps/dav/lib/Controller/AvailabilitySettingsController.php | 4 | ||||
-rw-r--r-- | apps/dav/lib/Db/Absence.php | 4 | ||||
-rw-r--r-- | apps/dav/lib/Listener/OutOfOfficeListener.php | 12 | ||||
-rw-r--r-- | apps/dav/lib/Service/AbsenceService.php | 20 | ||||
-rw-r--r-- | apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php | 134 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php | 850 | ||||
-rw-r--r-- | apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php | 12 |
10 files changed, 282 insertions, 964 deletions
diff --git a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php index 53184be6f25..60c53e9bd08 100644 --- a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php +++ b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php @@ -30,6 +30,10 @@ use OCP\BackgroundJob\TimedJob; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; +use OCP\IUser; +use OCP\IUserManager; +use OCP\User\IAvailabilityCoordinator; +use OCP\User\IOutOfOfficeData; use OCP\UserStatus\IManager; use OCP\UserStatus\IUserStatus; use Psr\Log\LoggerInterface; @@ -39,24 +43,15 @@ use Sabre\VObject\Reader; use Sabre\VObject\Recur\RRuleIterator; class UserStatusAutomation extends TimedJob { - protected IDBConnection $connection; - protected IJobList $jobList; - protected LoggerInterface $logger; - protected IManager $manager; - protected IConfig $config; - - public function __construct(ITimeFactory $timeFactory, - IDBConnection $connection, - IJobList $jobList, - LoggerInterface $logger, - IManager $manager, - IConfig $config) { + public function __construct(private ITimeFactory $timeFactory, + private IDBConnection $connection, + private IJobList $jobList, + private LoggerInterface $logger, + private IManager $manager, + private IConfig $config, + private IAvailabilityCoordinator $coordinator, + private IUserManager $userManager) { parent::__construct($timeFactory); - $this->connection = $connection; - $this->jobList = $jobList; - $this->logger = $logger; - $this->manager = $manager; - $this->config = $config; // Interval 0 might look weird, but the last_checked is always moved // to the next time we need this and then it's 0 seconds ago. @@ -74,21 +69,74 @@ class UserStatusAutomation extends TimedJob { } $userId = $argument['userId']; - $automationEnabled = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes'; - if (!$automationEnabled) { - $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the setting is disabled'); - $this->jobList->remove(self::class, $argument); + $user = $this->userManager->get($userId); + if($user === null) { + return; + } + + $ooo = $this->coordinator->getCurrentOutOfOfficeData($user); + + $continue = $this->processOutOfOfficeData($user, $ooo); + if($continue === false) { return; } $property = $this->getAvailabilityFromPropertiesTable($userId); + $hasDndForOfficeHours = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes'; if (!$property) { - $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no availability settings'); + // We found no ooo data and no availability settings, so we need to delete the job because there is no next runtime + $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules and no OOO data set'); $this->jobList->remove(self::class, $argument); + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_VACATION, IUserStatus::DND); return; } + $this->processAvailability($property, $user->getUID(), $hasDndForOfficeHours); + } + + protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void { + $query = $this->connection->getQueryBuilder(); + + $query->update('jobs') + ->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); + $query->executeStatement(); + + $this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId); + } + + /** + * @param string $userId + * @return false|string + */ + protected function getAvailabilityFromPropertiesTable(string $userId) { + $propertyPath = 'calendars/' . $userId . '/inbox'; + $propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability'; + + $query = $this->connection->getQueryBuilder(); + $query->select('propertyvalue') + ->from('properties') + ->where($query->expr()->eq('userid', $query->createNamedParameter($userId))) + ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath))) + ->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName))) + ->setMaxResults(1); + + $result = $query->executeQuery(); + $property = $result->fetchOne(); + $result->closeCursor(); + + return $property; + } + + /** + * @param string $property + * @param $userId + * @param $argument + * @return void + */ + private function processAvailability(string $property, string $userId, bool $hasDndForOfficeHours): void { $isCurrentlyAvailable = false; $nextPotentialToggles = []; @@ -117,7 +165,7 @@ class UserStatusAutomation extends TimedJob { $effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D')); try { - $it = new RRuleIterator((string) $available->RRULE, $effectiveStart); + $it = new RRuleIterator((string)$available->RRULE, $effectiveStart); $it->fastForward($lastMidnight); $startToday = $it->current(); @@ -148,7 +196,7 @@ class UserStatusAutomation extends TimedJob { if (empty($nextPotentialToggles)) { $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set'); - $this->jobList->remove(self::class, $argument); + $this->jobList->remove(self::class, ['userId' => $userId]); $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); return; } @@ -159,46 +207,53 @@ class UserStatusAutomation extends TimedJob { if ($isCurrentlyAvailable) { $this->logger->debug('User is currently available, reverting DND status if applicable'); $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); - } else { - $this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND'); - // The DND status automation is more important than the "Away - In call" so we also restore that one if it exists. - $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY); - $this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true); + $this->logger->debug('User status automation ran'); + return; } - $this->logger->debug('User status automation ran'); - } - - protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void { - $query = $this->connection->getQueryBuilder(); - $query->update('jobs') - ->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT)) - ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); - $query->executeStatement(); + if(!$hasDndForOfficeHours) { + // Office hours are not set to DND, so there is nothing to do. + return; + } - $this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId); + $this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND'); + // The DND status automation is more important than the "Away - In call" so we also restore that one if it exists. + $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY); + $this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true); + $this->logger->debug('User status automation ran'); } - /** - * @param string $userId - * @return false|string - */ - protected function getAvailabilityFromPropertiesTable(string $userId) { - $propertyPath = 'calendars/' . $userId . '/inbox'; - $propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability'; + private function processOutOfOfficeData(IUser $user, ?IOutOfOfficeData $ooo): bool { + if(empty($ooo)) { + // Reset the user status if the absence doesn't exist + $this->logger->debug('User has no OOO period in effect, reverting DND status if applicable'); + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_VACATION, IUserStatus::DND); + // We need to also run the availability automation + return true; + } - $query = $this->connection->getQueryBuilder(); - $query->select('propertyvalue') - ->from('properties') - ->where($query->expr()->eq('userid', $query->createNamedParameter($userId))) - ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath))) - ->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName))) - ->setMaxResults(1); + if(!$this->coordinator->isInEffect($ooo)) { + // Reset the user status if the absence is (no longer) in effect + $this->logger->debug('User has no OOO period in effect, reverting DND status if applicable'); + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_VACATION, IUserStatus::DND); - $result = $query->executeQuery(); - $property = $result->fetchOne(); - $result->closeCursor(); + if($ooo->getStartDate() > $this->time->getTime()) { + // Set the next run to take place at the start of the ooo period if it is in the future + // This might be overwritten if there is an availability setting, but we can't determine + // if this is the case here + $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getStartDate()); + } + return true; + } - return $property; + $this->logger->debug('User is currently in an OOO period, reverting other automated status and setting OOO DND status'); + // Revert both a possible 'CALL - away' and 'office hours - DND' status + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_CALL, IUserStatus::DND); + $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); + $this->manager->setUserStatus($user->getUID(), IUserStatus::MESSAGE_VACATION, IUserStatus::DND, true, $ooo->getShortMessage()); + // Run at the end of an ooo period to return to availability / regular user status + // If it's overwritten by a custom status in the meantime, there's nothing we can do about it + $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getEndDate()); + return false; } } diff --git a/apps/dav/lib/CalDAV/Status/Status.php b/apps/dav/lib/CalDAV/Status/Status.php index d1c35002fcd..da08d3fe285 100644 --- a/apps/dav/lib/CalDAV/Status/Status.php +++ b/apps/dav/lib/CalDAV/Status/Status.php @@ -26,8 +26,7 @@ namespace OCA\DAV\CalDAV\Status; class Status { - - public function __construct(private string $status = '', private ?string $message = null, private ?string $customMessage = null) { + 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 { @@ -54,5 +53,19 @@ class Status { $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; + } } diff --git a/apps/dav/lib/CalDAV/Status/StatusService.php b/apps/dav/lib/CalDAV/Status/StatusService.php index 1dce2c4c3a3..eea4c12db23 100644 --- a/apps/dav/lib/CalDAV/Status/StatusService.php +++ b/apps/dav/lib/CalDAV/Status/StatusService.php @@ -66,7 +66,6 @@ use Sabre\VObject\Component; use Sabre\VObject\Component\VEvent; use Sabre\VObject\Parameter; use Sabre\VObject\Property; -use Sabre\VObject\Reader; class StatusService { public function __construct(private ITimeFactory $timeFactory, @@ -76,7 +75,7 @@ class StatusService { private FreeBusyGenerator $generator) { } - public function processCalendarAvailability(User $user, ?string $availability): ?Status { + public function processCalendarAvailability(User $user): ?Status { $userId = $user->getUID(); $email = $user->getEMailAddress(); if($email === null) { @@ -160,8 +159,7 @@ class StatusService { } // @todo we can cache that - if(empty($availability) && empty($calendarEvents)) { - // No availability settings and no calendar events, we can stop here + if(empty($calendarEvents)) { return null; } @@ -181,15 +179,6 @@ class StatusService { $this->generator->setObjects($calendar); $this->generator->setTimeRange($dtStart, $dtEnd); $this->generator->setTimeZone($calendarTimeZone); - - if (!empty($availability)) { - $this->generator->setVAvailability( - Reader::read( - $availability - ) - ); - } - // Generate the intersection of VAVILABILITY and all VEVENTS in all calendars $result = $this->generator->getResult(); if (!isset($result->VFREEBUSY)) { @@ -200,9 +189,8 @@ class StatusService { $freeBusyComponent = $result->VFREEBUSY; $freeBusyProperties = $freeBusyComponent->select('FREEBUSY'); // If there is no FreeBusy property, the time-range is empty and available - // so set the status to online as otherwise we will never recover from a BUSY status if (count($freeBusyProperties) === 0) { - return new Status(IUserStatus::ONLINE); + return null; } /** @var Property $freeBusyProperty */ @@ -220,12 +208,10 @@ class StatusService { } $fbType = $fbTypeParameter->getValue(); switch ($fbType) { + // Ignore BUSY-UNAVAILABLE, that's for the automation case 'BUSY': - return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, $this->l10n->t('In a meeting')); - case 'BUSY-UNAVAILABLE': - return new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_AVAILABILITY); case 'BUSY-TENTATIVE': - return new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE); + return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, $this->l10n->t('In a meeting')); default: return null; } diff --git a/apps/dav/lib/Controller/AvailabilitySettingsController.php b/apps/dav/lib/Controller/AvailabilitySettingsController.php index 3e10162dd84..daa39df470e 100644 --- a/apps/dav/lib/Controller/AvailabilitySettingsController.php +++ b/apps/dav/lib/Controller/AvailabilitySettingsController.php @@ -36,12 +36,14 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Response; use OCP\IRequest; use OCP\IUserSession; +use OCP\User\IAvailabilityCoordinator; class AvailabilitySettingsController extends Controller { public function __construct( IRequest $request, private ?IUserSession $userSession, private AbsenceService $absenceService, + private IAvailabilityCoordinator $coordinator, ) { parent::__construct(Application::APP_ID, $request); } @@ -75,6 +77,7 @@ class AvailabilitySettingsController extends Controller { $status, $message, ); + $this->coordinator->clearCache($user->getUID()); return new JSONResponse($absence); } @@ -89,6 +92,7 @@ class AvailabilitySettingsController extends Controller { } $this->absenceService->clearAbsence($user); + $this->coordinator->clearCache($user->getUID()); return new JSONResponse([]); } diff --git a/apps/dav/lib/Db/Absence.php b/apps/dav/lib/Db/Absence.php index 3cd8037d57e..dc9afb59653 100644 --- a/apps/dav/lib/Db/Absence.php +++ b/apps/dav/lib/Db/Absence.php @@ -27,7 +27,6 @@ declare(strict_types=1); namespace OCA\DAV\Db; use DateTime; -use DateTimeZone; use Exception; use InvalidArgumentException; use JsonSerializable; @@ -58,6 +57,7 @@ class Absence extends Entity implements JsonSerializable { protected string $lastDay = ''; protected string $status = ''; + protected string $message = ''; public function __construct() { @@ -76,7 +76,7 @@ class Absence extends Entity implements JsonSerializable { throw new Exception('Creating out-of-office data without ID'); } - $tz = new DateTimeZone($timezone); + $tz = new \DateTimeZone($timezone); $startDate = new DateTime($this->getFirstDay(), $tz); $endDate = new DateTime($this->getLastDay(), $tz); $endDate->setTime(23, 59); diff --git a/apps/dav/lib/Listener/OutOfOfficeListener.php b/apps/dav/lib/Listener/OutOfOfficeListener.php index 609c32f5067..3d3bbfb2f42 100644 --- a/apps/dav/lib/Listener/OutOfOfficeListener.php +++ b/apps/dav/lib/Listener/OutOfOfficeListener.php @@ -52,21 +52,21 @@ use function rewind; * @template-implements IEventListener<OutOfOfficeScheduledEvent|OutOfOfficeChangedEvent|OutOfOfficeClearedEvent> */ class OutOfOfficeListener implements IEventListener { - public function __construct(private ServerFactory $serverFactory, + public function __construct( + private ServerFactory $serverFactory, private IConfig $appConfig, - private LoggerInterface $logger) { + private LoggerInterface $logger + ) { } public function handle(Event $event): void { if ($event instanceof OutOfOfficeScheduledEvent) { $userId = $event->getData()->getUser()->getUID(); $principal = "principals/users/$userId"; - $calendarNode = $this->getCalendarNode($principal, $userId); if ($calendarNode === null) { return; } - $tz = $calendarNode->getProperties([])['{urn:ietf:params:xml:ns:caldav}calendar-timezone'] ?? null; $vCalendarEvent = $this->createVCalendarEvent($event->getData(), $tz); $stream = fopen('php://memory', 'rb+'); @@ -83,7 +83,6 @@ class OutOfOfficeListener implements IEventListener { } elseif ($event instanceof OutOfOfficeChangedEvent) { $userId = $event->getData()->getUser()->getUID(); $principal = "principals/users/$userId"; - $calendarNode = $this->getCalendarNode($principal, $userId); if ($calendarNode === null) { return; @@ -110,17 +109,16 @@ class OutOfOfficeListener implements IEventListener { } elseif ($event instanceof OutOfOfficeClearedEvent) { $userId = $event->getData()->getUser()->getUID(); $principal = "principals/users/$userId"; - $calendarNode = $this->getCalendarNode($principal, $userId); if ($calendarNode === null) { return; } - try { $oldEvent = $calendarNode->getChild($this->getEventFileName($event->getData()->getId())); $oldEvent->delete(); } catch (NotFound) { // The user must have deleted it or the default calendar changed -> ignore + return; } } } diff --git a/apps/dav/lib/Service/AbsenceService.php b/apps/dav/lib/Service/AbsenceService.php index 3f5168e386d..874e86f6e1c 100644 --- a/apps/dav/lib/Service/AbsenceService.php +++ b/apps/dav/lib/Service/AbsenceService.php @@ -32,12 +32,16 @@ use OCA\DAV\CalDAV\TimezoneService; use OCA\DAV\Db\Absence; use OCA\DAV\Db\AbsenceMapper; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; +use OCP\Calendar\IManager; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\IUser; use OCP\User\Events\OutOfOfficeChangedEvent; use OCP\User\Events\OutOfOfficeClearedEvent; use OCP\User\Events\OutOfOfficeScheduledEvent; +use OCP\User\IOutOfOfficeData; class AbsenceService { public function __construct( @@ -45,6 +49,9 @@ class AbsenceService { private IEventDispatcher $eventDispatcher, private IJobList $jobList, private TimezoneService $timezoneService, + private ITimeFactory $timeFactory, + private IConfig $appConfig, + private IManager $calendarManager, ) { } @@ -128,4 +135,17 @@ class AbsenceService { ); $this->eventDispatcher->dispatchTyped(new OutOfOfficeClearedEvent($eventData)); } + + public function getAbsence(string $userId): ?Absence { + try { + return $this->absenceMapper->findByUserId($userId); + } catch (DoesNotExistException $e) { + return null; + } + } + + public function isInEffect(IOutOfOfficeData $absence): bool { + $now = $this->timeFactory->getTime(); + return $absence->getStartDate() <= $now && $absence->getEndDate() >= $now; + } } diff --git a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php index 59438c7cd28..fe6919d8dae 100644 --- a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php +++ b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php @@ -26,10 +26,14 @@ declare(strict_types=1); namespace OCA\DAV\Tests\unit\BackgroundJob; +use OC\User\OutOfOfficeData; use OCA\DAV\BackgroundJob\UserStatusAutomation; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use OCP\User\IAvailabilityCoordinator; use OCP\UserStatus\IManager; use OCP\UserStatus\IUserStatus; use PHPUnit\Framework\MockObject\MockObject; @@ -46,6 +50,8 @@ class UserStatusAutomationTest extends TestCase { protected MockObject|LoggerInterface $logger; protected MockObject|IManager $statusManager; protected MockObject|IConfig $config; + private IAvailabilityCoordinator|MockObject $coordinator; + private IUserManager|MockObject $userManager; protected function setUp(): void { parent::setUp(); @@ -55,6 +61,8 @@ class UserStatusAutomationTest extends TestCase { $this->logger = $this->createMock(LoggerInterface::class); $this->statusManager = $this->createMock(IManager::class); $this->config = $this->createMock(IConfig::class); + $this->coordinator = $this->createMock(IAvailabilityCoordinator::class); + $this->userManager = $this->createMock(IUserManager::class); } @@ -67,6 +75,8 @@ class UserStatusAutomationTest extends TestCase { $this->logger, $this->statusManager, $this->config, + $this->coordinator, + $this->userManager, ); } @@ -78,6 +88,8 @@ class UserStatusAutomationTest extends TestCase { $this->logger, $this->statusManager, $this->config, + $this->coordinator, + $this->userManager, ]) ->setMethods($methods) ->getMock(); @@ -95,14 +107,31 @@ class UserStatusAutomationTest extends TestCase { /** * @dataProvider dataRun */ - public function testRun(string $ruleDay, string $currentTime, bool $isAvailable): void { + public function testRunNoOOO(string $ruleDay, string $currentTime, bool $isAvailable): void { + $user = $this->createConfiguredMock(IUser::class, [ + 'getUID' => 'user' + ]); + + $this->userManager->expects(self::once()) + ->method('get') + ->willReturn($user); + $this->coordinator->expects(self::once()) + ->method('getCurrentOutOfOfficeData') + ->willReturn(null); $this->config->method('getUserValue') ->with('user', 'dav', 'user_status_automation', 'no') ->willReturn('yes'); - $this->time->method('getDateTime') ->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC'))); - + $this->logger->expects(self::exactly(4)) + ->method('debug'); + $this->statusManager->expects(self::exactly(2)) + ->method('revertUserStatus'); + if (!$isAvailable) { + $this->statusManager->expects(self::once()) + ->method('setUserStatus') + ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true); + } $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']); $automation->method('getAvailabilityFromPropertiesTable') ->with('user') @@ -141,63 +170,74 @@ END:AVAILABLE END:VAVAILABILITY END:VCALENDAR'); - if ($isAvailable) { - $this->statusManager->expects($this->once()) - ->method('revertUserStatus') - ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); - } else { - $this->statusManager->expects($this->once()) - ->method('revertUserStatus') - ->with('user', IUserStatus::MESSAGE_CALL, IUserStatus::AWAY); - $this->statusManager->expects($this->once()) - ->method('setUserStatus') - ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true); - } - self::invokePrivate($automation, 'run', [['userId' => 'user']]); } - public function testRunNoMoreAvailabilityDefined(): void { + public function testRunNoAvailabilityNoOOO(): void { + $user = $this->createConfiguredMock(IUser::class, [ + 'getUID' => 'user' + ]); + + $this->userManager->expects(self::once()) + ->method('get') + ->willReturn($user); + $this->coordinator->expects(self::once()) + ->method('getCurrentOutOfOfficeData') + ->willReturn(null); $this->config->method('getUserValue') ->with('user', 'dav', 'user_status_automation', 'no') ->willReturn('yes'); - $this->time->method('getDateTime') ->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC'))); - + $this->statusManager->expects($this->exactly(3)) + ->method('revertUserStatus'); + $this->jobList->expects($this->once()) + ->method('remove') + ->with(UserStatusAutomation::class, ['userId' => 'user']); + $this->logger->expects(self::once()) + ->method('debug'); + $this->logger->expects(self::once()) + ->method('info'); $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']); $automation->method('getAvailabilityFromPropertiesTable') ->with('user') - ->willReturn('BEGIN:VCALENDAR -PRODID:Nextcloud DAV app -BEGIN:VTIMEZONE -TZID:Europe/Berlin -BEGIN:STANDARD -TZNAME:CET -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -BEGIN:DAYLIGHT -TZNAME:CEST -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -END:VTIMEZONE -BEGIN:VAVAILABILITY -END:VAVAILABILITY -END:VCALENDAR'); + ->willReturn(false); - $this->statusManager->expects($this->once()) - ->method('revertUserStatus') - ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); + self::invokePrivate($automation, 'run', [['userId' => 'user']]); + } - $this->jobList->expects($this->once()) - ->method('remove') - ->with(UserStatusAutomation::class, ['userId' => 'user']); + public function testRunNoAvailabilityWithOOO(): void { + $user = $this->createConfiguredMock(IUser::class, [ + 'getUID' => 'user' + ]); + $ooo = $this->createConfiguredMock(OutOfOfficeData::class, [ + 'getShortMessage' => 'On Vacation', + 'getEndDate' => 123456, + ]); + + $this->userManager->expects(self::once()) + ->method('get') + ->willReturn($user); + $this->coordinator->expects(self::once()) + ->method('getCurrentOutOfOfficeData') + ->willReturn($ooo); + $this->coordinator->expects(self::once()) + ->method('isInEffect') + ->willReturn(true); + $this->statusManager->expects($this->exactly(2)) + ->method('revertUserStatus'); + $this->statusManager->expects(self::once()) + ->method('setUserStatus') + ->with('user', IUserStatus::MESSAGE_VACATION, IUserStatus::DND, true, $ooo->getShortMessage()); + $this->config->expects(self::never()) + ->method('getUserValue'); + $this->time->method('getDateTime') + ->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC'))); + $this->jobList->expects($this->never()) + ->method('remove'); + $this->logger->expects(self::exactly(2)) + ->method('debug'); + $automation = $this->getAutomationMock([]); self::invokePrivate($automation, 'run', [['userId' => 'user']]); } diff --git a/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php b/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php index ef77d715180..705298de125 100644 --- a/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php @@ -27,14 +27,12 @@ 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\Status; use OCA\DAV\CalDAV\Status\StatusService; use OCA\DAV\Connector\Sabre\Server; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Calendar\IManager; use OCP\IL10N; use OCP\IUser; -use OCP\UserStatus\IUserStatus; use PHPUnit\Framework\MockObject\MockObject; use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; use Sabre\DAV\Exception\NotAuthenticated; @@ -42,7 +40,6 @@ use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL\Exception\NeedPrivileges; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VTimeZone; -use Sabre\VObject\Document; use Sabre\VObject\Reader; use Test\TestCase; @@ -75,7 +72,6 @@ class StatusServiceTest extends TestCase { 'getUID' => 'admin', 'getEMailAddress' => null, ]); - $availability = ''; $user->expects(self::once()) ->method('getUID') @@ -104,11 +100,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -162,11 +156,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -220,11 +212,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -285,11 +275,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -349,11 +337,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -418,11 +404,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -497,11 +481,9 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } @@ -600,380 +582,13 @@ class StatusServiceTest extends TestCase { $this->generator->expects(self::never()) ->method('setTimeZone'); $this->generator->expects(self::never()) - ->method('setVAvailability'); - $this->generator->expects(self::never()) ->method('getResult'); - $status = $this->service->processCalendarAvailability($user, $availability); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } - public function testAvailabilityAndSearchCalendarsNoResults(): 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); - $availability = $this->getVAvailability(); - $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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertNull($status); - } - - public function testAvailabilityAndSearchCalendarsStatusOnline(): 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); - $availability = $this->getVAvailability(); - $result = Reader::read('BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Sabre//Sabre VObject 4.5.3//EN - CALSCALE:GREGORIAN -METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -END:VFREEBUSY -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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertEquals(new Status(IUserStatus::ONLINE), $status); - } - - public function testAvailabilityAndSearchCalendarsStatusBusyNoFBType(): 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); - $availability = $this->getVAvailability(); - $result = Reader::read('BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Sabre//Sabre VObject 4.5.3//EN - CALSCALE:GREGORIAN -METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -FREEBUSY:19700101T000000Z/19700101T003600Z -END:VFREEBUSY -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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertEquals(new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY), $status); - } - - public function testAvailabilityAndSearchCalendarsStatusBusy(): void { + public function testSearchCalendarsNoResults(): void { $user = $this->createConfiguredMock(IUser::class, [ 'getUID' => 'admin', 'getEMailAddress' => 'test@test.com', @@ -992,18 +607,11 @@ END:VCALENDAR'); $timezoneObj = $this->createMock(VTimeZone::class); $calendar = $this->createMock(CalendarImpl::class); $vCalendar = $this->createMock(VCalendar::class); - $availability = $this->getVAvailability(); $result = Reader::read('BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Sabre//Sabre VObject 4.5.3//EN CALSCALE:GREGORIAN METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -FREEBUSY;FBTYPE=BUSY:19700101T000000Z/19700101T003600Z -END:VFREEBUSY END:VCALENDAR'); $user->expects(self::once()) @@ -1072,438 +680,20 @@ END:VCALENDAR'); ->method('searchForPrincipal') ->with($query) ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - $this->l10n->expects(self::once()) - ->method('t') - ->with('In a meeting') - ->willReturn('In a meeting'); - - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertEquals(new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, 'In a meeting'), $status); - } - - public function testAvailabilityAndSearchCalendarsStatusBusyUnavailable(): 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); - $availability = $this->getVAvailability(); - $result = Reader::read('BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Sabre//Sabre VObject 4.5.3//EN - CALSCALE:GREGORIAN -METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:19700101T000000Z/19700101T003600Z -END:VFREEBUSY -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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - $this->l10n->expects(self::never()) - ->method('t'); - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertEquals(new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_AVAILABILITY), $status); - } - - public function testAvailabilityAndSearchCalendarsStatusBusyTentative(): 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); - $availability = $this->getVAvailability(); - $result = Reader::read('BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Sabre//Sabre VObject 4.5.3//EN - CALSCALE:GREGORIAN -METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -FREEBUSY;FBTYPE=BUSY-TENTATIVE:19700101T000000Z/19700101T003600Z -END:VFREEBUSY -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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); + $this->generator->expects(self::never()) + ->method('getVCalendar'); $vCalendar->expects(self::never()) ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - $this->l10n->expects(self::never()) - ->method('t'); - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); - $this->assertEquals(new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE), $status); - } - - public function testAvailabilityAndSearchCalendarsStatusBusyUnknownProperty(): 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); - $availability = $this->getVAvailability(); - $result = Reader::read('BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Sabre//Sabre VObject 4.5.3//EN - CALSCALE:GREGORIAN -METHOD:REQUEST -BEGIN:VFREEBUSY -DTSTART:19700101T000000Z -DTEND:19700101T003600Z -DTSTAMP:19700101T000200Z -FREEBUSY;FBTYPE=X-MEETING:19700101T000000Z/19700101T003600Z -END:VFREEBUSY -END:VCALENDAR'); + $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'); - $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->calendarManager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principal) - ->willReturn([$calendar]); - $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()) - ->method('getDateTime') - ->with('+10 minutes') - ->willReturn($inTenMinutes); - $query->expects(self::once()) - ->method('setTimerangeStart') - ->with($now); - $query->expects(self::once()) - ->method('setTimerangeEnd') - ->with($immutableInTenMinutes); - $this->calendarManager->expects(self::once()) - ->method('searchForPrincipal') - ->with($query) - ->willReturn([]); - $this->generator->expects(self::once()) - ->method('getVCalendar') - ->willReturn($vCalendar); - $vCalendar->expects(self::never()) - ->method('add'); - $this->generator->expects(self::once()) - ->method('setObjects') - ->with($vCalendar); - $this->generator->expects(self::once()) - ->method('setTimeRange') - ->with($now, $immutableInTenMinutes); - $this->generator->expects(self::once()) - ->method('setTimeZone') - ->with($timezone); - $this->generator->expects(self::once()) - ->method('setVAvailability') - ->with($availability); - $this->generator->expects(self::once()) - ->method('getResult') - ->willReturn($result); - $this->l10n->expects(self::never()) - ->method('t'); - $status = $this->service->processCalendarAvailability($user, $availability->serialize()); + $status = $this->service->processCalendarAvailability($user); $this->assertNull($status); } - - private function getVAvailability(): Document { - return Reader::read('BEGIN:VCALENDAR -PRODID:Nextcloud DAV app -BEGIN:VTIMEZONE -TZID:Europe/Vienna -BEGIN:STANDARD -TZNAME:CET -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -BEGIN:DAYLIGHT -TZNAME:CEST -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -END:VTIMEZONE -BEGIN:VAVAILABILITY -BEGIN:AVAILABLE -DTSTART;TZID=Europe/Vienna:20231025T000000 -DTEND;TZID=Europe/Vienna:20231025T235900 -UID:d866782e-e003-4906-9ece-303f270a2c6b -RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU -END:AVAILABLE -END:VAVAILABILITY -END:VCALENDAR'); - } } diff --git a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php index 02227adfbfc..919cf0a9ccf 100644 --- a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php +++ b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php @@ -40,6 +40,7 @@ use OCP\User\Events\OutOfOfficeChangedEvent; use OCP\User\Events\OutOfOfficeClearedEvent; use OCP\User\Events\OutOfOfficeScheduledEvent; use OCP\User\IOutOfOfficeData; +use OCP\UserStatus\IManager; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\NotFound; @@ -55,6 +56,7 @@ class OutOfOfficeListenerTest extends TestCase { private IConfig|MockObject $appConfig; private LoggerInterface|MockObject $loggerInterface; private OutOfOfficeListener $listener; + private IManager|MockObject $manager; protected function setUp(): void { parent::setUp(); @@ -62,11 +64,13 @@ class OutOfOfficeListenerTest extends TestCase { $this->serverFactory = $this->createMock(ServerFactory::class); $this->appConfig = $this->createMock(IConfig::class); $this->loggerInterface = $this->createMock(LoggerInterface::class); + $this->manager = $this->createMock(IManager::class); $this->listener = new OutOfOfficeListener( $this->serverFactory, $this->appConfig, $this->loggerInterface, + $this->manager ); } @@ -389,6 +393,8 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getPlugin') ->with('caldav') ->willReturn($caldavPlugin); + $this->manager->expects(self::never()) + ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -417,6 +423,8 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getNodeForPath') ->with('/home/calendar') ->willThrowException(new NotFound('nope')); + $this->manager->expects(self::never()) + ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -454,6 +462,8 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getChild') ->with('personal-1') ->willThrowException(new NotFound('nope')); + $this->manager->expects(self::never()) + ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -495,6 +505,8 @@ class OutOfOfficeListenerTest extends TestCase { $calendar->expects(self::once()) ->method('getChild') ->willThrowException(new NotFound()); + $this->manager->expects(self::never()) + ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); |