Skip to content

Commit 4b79e75

Browse files
Merge pull request #36852 from nextcloud/bugfix/noid/fix-user-status-automation-on-the-day-rules-are-adjusted
fix(user_status): Fix the user status automation on the day availablity rules are adjusted
2 parents ac90fa2 + c7400fa commit 4b79e75

2 files changed

Lines changed: 217 additions & 4 deletions

File tree

apps/dav/lib/BackgroundJob/UserStatusAutomation.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ protected function run($argument) {
9292
$isCurrentlyAvailable = false;
9393
$nextPotentialToggles = [];
9494

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

9898
$vObject = Reader::read($property);
@@ -105,9 +105,16 @@ protected function run($argument) {
105105
foreach ($availables as $available) {
106106
/** @var Available $available */
107107
if ($available->name === 'AVAILABLE') {
108-
/** @var \DateTimeInterface $effectiveStart */
109-
/** @var \DateTimeInterface $effectiveEnd */
110-
[$effectiveStart, $effectiveEnd] = $available->getEffectiveStartEnd();
108+
/** @var \DateTimeImmutable $originalStart */
109+
/** @var \DateTimeImmutable $originalEnd */
110+
[$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
111+
112+
// Little shenanigans to fix the automation on the day the rules were adjusted
113+
// Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
114+
// So we simply wind back a week and then fastForward to the next occurrence
115+
// since today's midnight, which then also accounts for the week days.
116+
$effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
117+
$effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
111118

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

152159
if ($isCurrentlyAvailable) {
160+
$this->logger->debug('User is currently available, reverting DND status if applicable');
153161
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
154162
} else {
163+
$this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND');
155164
// The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
156165
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
157166
$this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
7+
*
8+
* @author Joas Schilling <coding@schilljs.com>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\DAV\Tests\unit\BackgroundJob;
28+
29+
use OCA\DAV\BackgroundJob\UserStatusAutomation;
30+
use OCP\AppFramework\Utility\ITimeFactory;
31+
use OCP\BackgroundJob\IJobList;
32+
use OCP\IConfig;
33+
use OCP\UserStatus\IManager;
34+
use OCP\UserStatus\IUserStatus;
35+
use PHPUnit\Framework\MockObject\MockObject;
36+
use Psr\Log\LoggerInterface;
37+
use Test\TestCase;
38+
39+
/**
40+
* @group DB
41+
*/
42+
class UserStatusAutomationTest extends TestCase {
43+
44+
protected MockObject|ITimeFactory $time;
45+
protected MockObject|IJobList $jobList;
46+
protected MockObject|LoggerInterface $logger;
47+
protected MockObject|IManager $statusManager;
48+
protected MockObject|IConfig $config;
49+
50+
protected function setUp(): void {
51+
parent::setUp();
52+
53+
$this->time = $this->createMock(ITimeFactory::class);
54+
$this->jobList = $this->createMock(IJobList::class);
55+
$this->logger = $this->createMock(LoggerInterface::class);
56+
$this->statusManager = $this->createMock(IManager::class);
57+
$this->config = $this->createMock(IConfig::class);
58+
59+
}
60+
61+
protected function getAutomationMock(array $methods): MockObject|UserStatusAutomation {
62+
if (empty($methods)) {
63+
return new UserStatusAutomation(
64+
$this->time,
65+
\OC::$server->getDatabaseConnection(),
66+
$this->jobList,
67+
$this->logger,
68+
$this->statusManager,
69+
$this->config,
70+
);
71+
}
72+
73+
return $this->getMockBuilder(UserStatusAutomation::class)
74+
->setConstructorArgs([
75+
$this->time,
76+
\OC::$server->getDatabaseConnection(),
77+
$this->jobList,
78+
$this->logger,
79+
$this->statusManager,
80+
$this->config,
81+
])
82+
->setMethods($methods)
83+
->getMock();
84+
}
85+
86+
public function dataRun(): array {
87+
return [
88+
['20230217', '2023-02-24 10:49:36.613834', true],
89+
['20230224', '2023-02-24 10:49:36.613834', true],
90+
['20230217', '2023-02-24 13:58:24.479357', false],
91+
['20230224', '2023-02-24 13:58:24.479357', false],
92+
];
93+
}
94+
95+
/**
96+
* @dataProvider dataRun
97+
*/
98+
public function testRun(string $ruleDay, string $currentTime, bool $isAvailable): void {
99+
$this->config->method('getUserValue')
100+
->with('user', 'dav', 'user_status_automation', 'no')
101+
->willReturn('yes');
102+
103+
$this->time->method('getDateTime')
104+
->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC')));
105+
106+
$automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
107+
$automation->method('getAvailabilityFromPropertiesTable')
108+
->with('user')
109+
->willReturn('BEGIN:VCALENDAR
110+
PRODID:Nextcloud DAV app
111+
BEGIN:VTIMEZONE
112+
TZID:Europe/Berlin
113+
BEGIN:STANDARD
114+
TZNAME:CET
115+
TZOFFSETFROM:+0200
116+
TZOFFSETTO:+0100
117+
DTSTART:19701025T030000
118+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
119+
END:STANDARD
120+
BEGIN:DAYLIGHT
121+
TZNAME:CEST
122+
TZOFFSETFROM:+0100
123+
TZOFFSETTO:+0200
124+
DTSTART:19700329T020000
125+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
126+
END:DAYLIGHT
127+
END:VTIMEZONE
128+
BEGIN:VAVAILABILITY
129+
BEGIN:AVAILABLE
130+
DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
131+
DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T170000
132+
UID:3e6feeec-8e00-4265-b822-b73174e8b39f
133+
RRULE:FREQ=WEEKLY;BYDAY=TH
134+
END:AVAILABLE
135+
BEGIN:AVAILABLE
136+
DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
137+
DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T120000
138+
UID:8a634e99-07cf-443b-b480-005a0e1db323
139+
RRULE:FREQ=WEEKLY;BYDAY=FR
140+
END:AVAILABLE
141+
END:VAVAILABILITY
142+
END:VCALENDAR');
143+
144+
if ($isAvailable) {
145+
$this->statusManager->expects($this->once())
146+
->method('revertUserStatus')
147+
->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
148+
} else {
149+
$this->statusManager->expects($this->once())
150+
->method('revertUserStatus')
151+
->with('user', IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
152+
$this->statusManager->expects($this->once())
153+
->method('setUserStatus')
154+
->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
155+
}
156+
157+
self::invokePrivate($automation, 'run', [['userId' => 'user']]);
158+
}
159+
160+
public function testRunNoMoreAvailabilityDefined(): void {
161+
$this->config->method('getUserValue')
162+
->with('user', 'dav', 'user_status_automation', 'no')
163+
->willReturn('yes');
164+
165+
$this->time->method('getDateTime')
166+
->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC')));
167+
168+
$automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
169+
$automation->method('getAvailabilityFromPropertiesTable')
170+
->with('user')
171+
->willReturn('BEGIN:VCALENDAR
172+
PRODID:Nextcloud DAV app
173+
BEGIN:VTIMEZONE
174+
TZID:Europe/Berlin
175+
BEGIN:STANDARD
176+
TZNAME:CET
177+
TZOFFSETFROM:+0200
178+
TZOFFSETTO:+0100
179+
DTSTART:19701025T030000
180+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
181+
END:STANDARD
182+
BEGIN:DAYLIGHT
183+
TZNAME:CEST
184+
TZOFFSETFROM:+0100
185+
TZOFFSETTO:+0200
186+
DTSTART:19700329T020000
187+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
188+
END:DAYLIGHT
189+
END:VTIMEZONE
190+
BEGIN:VAVAILABILITY
191+
END:VAVAILABILITY
192+
END:VCALENDAR');
193+
194+
$this->statusManager->expects($this->once())
195+
->method('revertUserStatus')
196+
->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
197+
198+
$this->jobList->expects($this->once())
199+
->method('remove')
200+
->with(UserStatusAutomation::class, ['userId' => 'user']);
201+
202+
self::invokePrivate($automation, 'run', [['userId' => 'user']]);
203+
}
204+
}

0 commit comments

Comments
 (0)