Skip to content

Commit

Permalink
Merge pull request #36852 from nextcloud/bugfix/noid/fix-user-status-…
Browse files Browse the repository at this point in the history
…automation-on-the-day-rules-are-adjusted

fix(user_status): Fix the user status automation on the day availablity rules are adjusted
  • Loading branch information
nickvergessen authored Mar 13, 2023
2 parents ac90fa2 + c7400fa commit 4b79e75
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 4 deletions.
17 changes: 13 additions & 4 deletions apps/dav/lib/BackgroundJob/UserStatusAutomation.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ protected function run($argument) {
$isCurrentlyAvailable = false;
$nextPotentialToggles = [];

$now = new \DateTime('now');
$now = $this->time->getDateTime();
$lastMidnight = (clone $now)->setTime(0, 0);

$vObject = Reader::read($property);
Expand All @@ -105,9 +105,16 @@ protected function run($argument) {
foreach ($availables as $available) {
/** @var Available $available */
if ($available->name === 'AVAILABLE') {
/** @var \DateTimeInterface $effectiveStart */
/** @var \DateTimeInterface $effectiveEnd */
[$effectiveStart, $effectiveEnd] = $available->getEffectiveStartEnd();
/** @var \DateTimeImmutable $originalStart */
/** @var \DateTimeImmutable $originalEnd */
[$originalStart, $originalEnd] = $available->getEffectiveStartEnd();

// Little shenanigans to fix the automation on the day the rules were adjusted
// Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
// So we simply wind back a week and then fastForward to the next occurrence
// since today's midnight, which then also accounts for the week days.
$effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
$effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));

try {
$it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
Expand Down Expand Up @@ -150,8 +157,10 @@ protected function run($argument) {
$this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);

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);
Expand Down
204 changes: 204 additions & 0 deletions apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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\Tests\unit\BackgroundJob;

use OCA\DAV\BackgroundJob\UserStatusAutomation;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\IConfig;
use OCP\UserStatus\IManager;
use OCP\UserStatus\IUserStatus;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;

/**
* @group DB
*/
class UserStatusAutomationTest extends TestCase {

protected MockObject|ITimeFactory $time;
protected MockObject|IJobList $jobList;
protected MockObject|LoggerInterface $logger;
protected MockObject|IManager $statusManager;
protected MockObject|IConfig $config;

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

$this->time = $this->createMock(ITimeFactory::class);
$this->jobList = $this->createMock(IJobList::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->statusManager = $this->createMock(IManager::class);
$this->config = $this->createMock(IConfig::class);

}

protected function getAutomationMock(array $methods): MockObject|UserStatusAutomation {
if (empty($methods)) {
return new UserStatusAutomation(
$this->time,
\OC::$server->getDatabaseConnection(),
$this->jobList,
$this->logger,
$this->statusManager,
$this->config,
);
}

return $this->getMockBuilder(UserStatusAutomation::class)
->setConstructorArgs([
$this->time,
\OC::$server->getDatabaseConnection(),
$this->jobList,
$this->logger,
$this->statusManager,
$this->config,
])
->setMethods($methods)
->getMock();
}

public function dataRun(): array {
return [
['20230217', '2023-02-24 10:49:36.613834', true],
['20230224', '2023-02-24 10:49:36.613834', true],
['20230217', '2023-02-24 13:58:24.479357', false],
['20230224', '2023-02-24 13:58:24.479357', false],
];
}

/**
* @dataProvider dataRun
*/
public function testRun(string $ruleDay, string $currentTime, bool $isAvailable): void {
$this->config->method('getUserValue')
->with('user', 'dav', 'user_status_automation', 'no')
->willReturn('yes');

$this->time->method('getDateTime')
->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC')));

$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
BEGIN:AVAILABLE
DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T170000
UID:3e6feeec-8e00-4265-b822-b73174e8b39f
RRULE:FREQ=WEEKLY;BYDAY=TH
END:AVAILABLE
BEGIN:AVAILABLE
DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T120000
UID:8a634e99-07cf-443b-b480-005a0e1db323
RRULE:FREQ=WEEKLY;BYDAY=FR
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 {
$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')));

$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');

$this->statusManager->expects($this->once())
->method('revertUserStatus')
->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);

$this->jobList->expects($this->once())
->method('remove')
->with(UserStatusAutomation::class, ['userId' => 'user']);

self::invokePrivate($automation, 'run', [['userId' => 'user']]);
}
}

0 comments on commit 4b79e75

Please sign in to comment.