Skip to content

Commit 45ec9aa

Browse files
committed
fix(dav): avoid dropping labels on unresolved category sync
1 parent a39d7b7 commit 45ec9aa

File tree

2 files changed

+189
-3
lines changed

2 files changed

+189
-3
lines changed

lib/DAV/DeckCalendarBackend.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ private function syncCardCategories(int $cardId, array $categories): void {
739739

740740
$targetLabelIds = [];
741741
$createdLabels = 0;
742+
$hasUnresolvedCategories = false;
742743
foreach ($categories as $category) {
743744
$title = trim($category);
744745
$key = mb_strtolower($title);
@@ -752,6 +753,7 @@ private function syncCardCategories(int $cardId, array $categories): void {
752753
}
753754
}
754755
if (!isset($boardLabelsByTitle[$key])) {
756+
$hasUnresolvedCategories = true;
755757
continue;
756758
}
757759
$targetLabelIds[$boardLabelsByTitle[$key]->getId()] = true;
@@ -763,9 +765,11 @@ private function syncCardCategories(int $cardId, array $categories): void {
763765
$currentLabelIds[$label->getId()] = true;
764766
}
765767

766-
foreach (array_keys($currentLabelIds) as $labelId) {
767-
if (!isset($targetLabelIds[$labelId])) {
768-
$this->cardService->removeLabel($cardId, $labelId);
768+
if (!$hasUnresolvedCategories) {
769+
foreach (array_keys($currentLabelIds) as $labelId) {
770+
if (!isset($targetLabelIds[$labelId])) {
771+
$this->cardService->removeLabel($cardId, $labelId);
772+
}
769773
}
770774
}
771775

tests/unit/DAV/DeckCalendarBackendTest.php

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,85 @@ public function testUpdateCardDoesNotAutoCreateLabelsWithoutManagePermission():
998998
$this->backend->updateCalendarObject($sourceCard, $calendarData);
999999
}
10001000

1001+
public function testUpdateCardKeepsExistingLabelsWhenCategoriesCannotBeCreated(): void {
1002+
$sourceCard = new Card();
1003+
$sourceCard->setId(123);
1004+
1005+
$currentLabel = new Label();
1006+
$currentLabel->setId(99);
1007+
$currentLabel->setTitle('Existing');
1008+
1009+
$existingCard = new Card();
1010+
$existingCard->setId(123);
1011+
$existingCard->setTitle('Card');
1012+
$existingCard->setDescription('Old description');
1013+
$existingCard->setStackId(42);
1014+
$existingCard->setType('plain');
1015+
$existingCard->setOrder(0);
1016+
$existingCard->setOwner('admin');
1017+
$existingCard->setDeletedAt(0);
1018+
$existingCard->setArchived(false);
1019+
$existingCard->setDone(null);
1020+
$existingCard->setLabels([$currentLabel]);
1021+
1022+
$updatedCard = new Card();
1023+
$updatedCard->setId(123);
1024+
1025+
$this->cardService->expects($this->exactly(2))
1026+
->method('find')
1027+
->with(123)
1028+
->willReturnOnConsecutiveCalls($existingCard, $existingCard);
1029+
1030+
$currentStack = new Stack();
1031+
$currentStack->setId(42);
1032+
$currentStack->setBoardId(12);
1033+
$this->stackService->expects($this->exactly(2))
1034+
->method('find')
1035+
->with(42)
1036+
->willReturn($currentStack);
1037+
1038+
$this->cardService->expects($this->once())
1039+
->method('update')
1040+
->willReturn($updatedCard);
1041+
1042+
$board = new Board();
1043+
$board->setId(12);
1044+
$board->setLabels([]);
1045+
1046+
$this->boardMapper->expects($this->once())
1047+
->method('find')
1048+
->with(12, true, false)
1049+
->willReturn($board);
1050+
1051+
$this->permissionService->expects($this->once())
1052+
->method('getPermissions')
1053+
->with(12)
1054+
->willReturn([
1055+
\OCA\Deck\Db\Acl::PERMISSION_MANAGE => false,
1056+
]);
1057+
1058+
$this->labelService->expects($this->never())
1059+
->method('create');
1060+
$this->cardService->expects($this->never())
1061+
->method('assignLabel');
1062+
$this->cardService->expects($this->never())
1063+
->method('removeLabel');
1064+
1065+
$calendarData = <<<ICS
1066+
BEGIN:VCALENDAR
1067+
VERSION:2.0
1068+
BEGIN:VTODO
1069+
UID:deck-card-123
1070+
SUMMARY:Card
1071+
DESCRIPTION:Updated description
1072+
CATEGORIES:Alpha
1073+
END:VTODO
1074+
END:VCALENDAR
1075+
ICS;
1076+
1077+
$this->backend->updateCalendarObject($sourceCard, $calendarData);
1078+
}
1079+
10011080
public function testUpdateCardLimitsAutoCreatedLabelsPerSync(): void {
10021081
$sourceCard = new Card();
10031082
$sourceCard->setId(123);
@@ -1097,6 +1176,109 @@ public function testUpdateCardLimitsAutoCreatedLabelsPerSync(): void {
10971176
$this->backend->updateCalendarObject($sourceCard, $calendarData);
10981177
}
10991178

1179+
public function testUpdateCardKeepsExistingLabelsWhenAutoCreateLimitIsExceeded(): void {
1180+
$sourceCard = new Card();
1181+
$sourceCard->setId(123);
1182+
1183+
$currentLabel = new Label();
1184+
$currentLabel->setId(99);
1185+
$currentLabel->setTitle('Existing');
1186+
1187+
$existingCard = new Card();
1188+
$existingCard->setId(123);
1189+
$existingCard->setTitle('Card');
1190+
$existingCard->setDescription('Old description');
1191+
$existingCard->setStackId(42);
1192+
$existingCard->setType('plain');
1193+
$existingCard->setOrder(0);
1194+
$existingCard->setOwner('admin');
1195+
$existingCard->setDeletedAt(0);
1196+
$existingCard->setArchived(false);
1197+
$existingCard->setDone(null);
1198+
$existingCard->setLabels([$currentLabel]);
1199+
1200+
$updatedCard = new Card();
1201+
$updatedCard->setId(123);
1202+
1203+
$this->cardService->expects($this->exactly(2))
1204+
->method('find')
1205+
->with(123)
1206+
->willReturnOnConsecutiveCalls($existingCard, $existingCard);
1207+
1208+
$currentStack = new Stack();
1209+
$currentStack->setId(42);
1210+
$currentStack->setBoardId(12);
1211+
$this->stackService->expects($this->exactly(2))
1212+
->method('find')
1213+
->with(42)
1214+
->willReturn($currentStack);
1215+
1216+
$this->cardService->expects($this->once())
1217+
->method('update')
1218+
->willReturn($updatedCard);
1219+
1220+
$board = new Board();
1221+
$board->setId(12);
1222+
$board->setLabels([]);
1223+
1224+
$this->boardMapper->expects($this->once())
1225+
->method('find')
1226+
->with(12, true, false)
1227+
->willReturn($board);
1228+
1229+
$this->permissionService->expects($this->once())
1230+
->method('getPermissions')
1231+
->with(12)
1232+
->willReturn([
1233+
\OCA\Deck\Db\Acl::PERMISSION_MANAGE => true,
1234+
]);
1235+
1236+
$labels = [];
1237+
for ($i = 1; $i <= 5; $i++) {
1238+
$label = new Label();
1239+
$label->setId($i);
1240+
$label->setTitle('Tag ' . $i);
1241+
$labels[] = $label;
1242+
}
1243+
1244+
$this->labelService->expects($this->exactly(5))
1245+
->method('create')
1246+
->withConsecutive(
1247+
['Tag 1', '31CC7C', 12],
1248+
['Tag 2', '31CC7C', 12],
1249+
['Tag 3', '31CC7C', 12],
1250+
['Tag 4', '31CC7C', 12],
1251+
['Tag 5', '31CC7C', 12],
1252+
)
1253+
->willReturnOnConsecutiveCalls(...$labels);
1254+
1255+
$this->cardService->expects($this->exactly(5))
1256+
->method('assignLabel')
1257+
->withConsecutive(
1258+
[123, 1],
1259+
[123, 2],
1260+
[123, 3],
1261+
[123, 4],
1262+
[123, 5],
1263+
);
1264+
$this->cardService->expects($this->never())
1265+
->method('removeLabel');
1266+
1267+
$calendarData = <<<ICS
1268+
BEGIN:VCALENDAR
1269+
VERSION:2.0
1270+
BEGIN:VTODO
1271+
UID:deck-card-123
1272+
SUMMARY:Card
1273+
DESCRIPTION:Updated description
1274+
CATEGORIES:Tag 1,Tag 2,Tag 3,Tag 4,Tag 5,Tag 6
1275+
END:VTODO
1276+
END:VCALENDAR
1277+
ICS;
1278+
1279+
$this->backend->updateCalendarObject($sourceCard, $calendarData);
1280+
}
1281+
11001282
public function testGetObjectRevisionFingerprintUsesKnownBoardContextWithoutStackLookup(): void {
11011283
$card = new Card();
11021284
$card->setId(123);

0 commit comments

Comments
 (0)