diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index 5ba0a1e529de7..375f5925a3f8e 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -152,7 +152,12 @@ public function updateUser(IUser $user): void { if (is_null($vCard)) { $this->backend->deleteCard($addressBookId, $cardId); } else { - $this->backend->updateCard($addressBookId, $cardId, $vCard->serialize()); + $cardData = $vCard->serialize(); + // Writing an identical card would still bump the address book + // sync token and make every client re-download the card + if ($card['carddata'] !== $cardData) { + $this->backend->updateCard($addressBookId, $cardId, $cardData); + } } } }, $this->dbConnection); diff --git a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php index e76d5d929f83b..d817cda6df680 100644 --- a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php @@ -459,6 +459,45 @@ public function testUpdateAndDeleteUser(bool $activated, int $createCalls, int $ $ss->deleteUser($user); } + public function testUpdateUserSkipsUnchangedCard(): void { + $vCard = new VCard(); + $vCard->VERSION = '3.0'; + $vCard->UID = 'test-user'; + $vCard->FN = 'test-user'; + + /** @var CardDavBackend&MockObject $backend */ + $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $logger = $this->createMock(LoggerInterface::class); + + $backend->expects($this->never())->method('createCard'); + $backend->expects($this->never())->method('updateCard'); + $backend->expects($this->never())->method('deleteCard'); + + $backend->method('getCard')->willReturn(['carddata' => $vCard->serialize()]); + $backend->method('getAddressBooksByUri') + ->with('principals/system/system', 'system') + ->willReturn(['id' => -1]); + + $user = $this->createMock(IUser::class); + $user->method('getBackendClassName')->willReturn('unittest'); + $user->method('getUID')->willReturn('test-user'); + $user->method('isEnabled')->willReturn(true); + + $converter = $this->createMock(Converter::class); + $converter->method('createCardFromUser')->willReturn($vCard); + + $ss = new SyncService( + $this->createMock(IClientService::class), + $this->createMock(IConfig::class), + $backend, + $this->createMock(IUserManager::class), + $this->createMock(IDBConnection::class), + $logger, + $converter, + ); + $ss->updateUser($user); + } + public function testSyncInstance(): void { /** @var CardDavBackend | MockObject $backend */ $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();