Skip to content

Commit a17c574

Browse files
committed
test(files_sharing): add tests for fetching remote avatars
Signed-off-by: Kent Delante <kent@delante.me> Assisted-by: ClaudeCode:claude-sonnet-5
1 parent a5dd91b commit a17c574

2 files changed

Lines changed: 208 additions & 13 deletions

File tree

tests/lib/Avatar/AvatarManagerTest.php

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010

1111
use OC\Avatar\AvatarManager;
1212
use OC\Avatar\PlaceholderAvatar;
13+
use OC\Avatar\RemoteAvatar;
1314
use OC\Avatar\UserAvatar;
1415
use OC\KnownUser\KnownUserService;
1516
use OC\User\Manager;
1617
use OC\User\User;
1718
use OCP\Accounts\IAccount;
1819
use OCP\Accounts\IAccountManager;
1920
use OCP\Accounts\IAccountProperty;
21+
use OCP\Federation\ICloudId;
22+
use OCP\Federation\ICloudIdManager;
2023
use OCP\Files\IAppData;
2124
use OCP\Files\SimpleFS\ISimpleFolder;
2225
use OCP\IConfig;
@@ -73,19 +76,6 @@ protected function setUp(): void {
7376
);
7477
}
7578

76-
public function testGetAvatarInvalidUser(): void {
77-
$this->expectException(\Exception::class);
78-
$this->expectExceptionMessage('user does not exist');
79-
80-
$this->userManager
81-
->expects($this->once())
82-
->method('get')
83-
->with('invalidUser')
84-
->willReturn(null);
85-
86-
$this->avatarManager->getAvatar('invalidUser');
87-
}
88-
8979
public function testGetAvatarForSelf(): void {
9080
$user = $this->createMock(User::class);
9181
$user
@@ -276,4 +266,73 @@ public function testGetAvatarScopes($avatarScope, $isPublicCall, $isKnownUser, $
276266
}
277267
$this->assertEquals($expected, $this->avatarManager->getAvatar('valid-user'));
278268
}
269+
270+
public function testGetAvatarInvalidUser(): void {
271+
$this->expectException(\Exception::class);
272+
$this->expectExceptionMessage('user does not exist');
273+
274+
$this->userManager
275+
->expects($this->once())
276+
->method('get')
277+
->with('invalidUser')
278+
->willReturn(null);
279+
280+
$this->avatarManager->getAvatar('invalidUser');
281+
}
282+
283+
public function testGetAvatarForRemoteUser(): void {
284+
$cloudId = 'user@https://remote.example.com';
285+
286+
$this->userManager
287+
->expects($this->once())
288+
->method('get')
289+
->with($cloudId)
290+
->willReturn(null);
291+
292+
$resolvedCloudId = $this->createMock(ICloudId::class);
293+
$resolvedCloudId->method('getUser')->willReturn('user');
294+
$resolvedCloudId->method('getRemote')->willReturn('https://remote.example.com');
295+
$resolvedCloudId->method('getDisplayId')->willReturn('user@remote.example.com');
296+
297+
$cloudIdManager = $this->createMock(ICloudIdManager::class);
298+
$cloudIdManager->expects($this->once())
299+
->method('isValidCloudId')
300+
->with($cloudId)
301+
->willReturn(true);
302+
$cloudIdManager->method('resolveCloudId')
303+
->with($cloudId)
304+
->willReturn($resolvedCloudId);
305+
$this->overwriteService(ICloudIdManager::class, $cloudIdManager);
306+
307+
// the remote branch must not touch local avatar storage
308+
$this->appData->expects($this->never())->method('getFolder');
309+
$this->accountManager->expects($this->never())->method('getAccount');
310+
311+
$avatar = $this->avatarManager->getAvatar($cloudId);
312+
313+
self::assertInstanceOf(RemoteAvatar::class, $avatar);
314+
self::assertTrue($avatar->exists());
315+
self::assertTrue($avatar->isCustomAvatar());
316+
self::assertSame('user@remote.example.com', $avatar->getDisplayName());
317+
}
318+
319+
public function testGetAvatarThrowsForUnknownUserThatIsNotACloudId(): void {
320+
$this->expectException(\Exception::class);
321+
$this->expectExceptionMessage('user does not exist');
322+
323+
$this->userManager
324+
->expects($this->once())
325+
->method('get')
326+
->with('invalidUser')
327+
->willReturn(null);
328+
329+
$cloudIdManager = $this->createMock(ICloudIdManager::class);
330+
$cloudIdManager->expects($this->once())
331+
->method('isValidCloudId')
332+
->with('invalidUser')
333+
->willReturn(false);
334+
$this->overwriteService(ICloudIdManager::class, $cloudIdManager);
335+
336+
$this->avatarManager->getAvatar('invalidUser');
337+
}
279338
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-only
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Test\Avatar;
11+
12+
use OC\Avatar\RemoteAvatar;
13+
use OCP\Federation\ICloudId;
14+
use OCP\Federation\ICloudIdManager;
15+
use OCP\Files\SimpleFS\InMemoryFile;
16+
use OCP\Http\Client\IClient;
17+
use OCP\Http\Client\IClientService;
18+
use OCP\Http\Client\IResponse;
19+
use OCP\IConfig;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use Psr\Log\LoggerInterface;
22+
use Test\TestCase;
23+
24+
class RemoteAvatarTest extends TestCase {
25+
private const CLOUD_ID = 'user@https://remote.example.com';
26+
27+
private IConfig&MockObject $config;
28+
private LoggerInterface&MockObject $logger;
29+
private ICloudIdManager&MockObject $cloudIdManager;
30+
private IClientService&MockObject $clientService;
31+
private RemoteAvatar $avatar;
32+
33+
#[\Override]
34+
protected function setUp(): void {
35+
parent::setUp();
36+
37+
$this->config = $this->createMock(IConfig::class);
38+
$this->logger = $this->createMock(LoggerInterface::class);
39+
40+
$cloudId = $this->createMock(ICloudId::class);
41+
$cloudId->method('getUser')->willReturn('user');
42+
$cloudId->method('getRemote')->willReturn('https://remote.example.com');
43+
$cloudId->method('getDisplayId')->willReturn('user@remote.example.com');
44+
45+
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
46+
$this->cloudIdManager->method('resolveCloudId')
47+
->with(self::CLOUD_ID)
48+
->willReturn($cloudId);
49+
$this->overwriteService(ICloudIdManager::class, $this->cloudIdManager);
50+
51+
$this->clientService = $this->createMock(IClientService::class);
52+
$this->overwriteService(IClientService::class, $this->clientService);
53+
54+
$this->avatar = new RemoteAvatar(self::CLOUD_ID, $this->config, $this->logger);
55+
}
56+
57+
/**
58+
* Stubs the client returned by IClientService::newClient() to respond to
59+
* a single GET request, optionally asserting the requested URL/options.
60+
*
61+
* @param string|resource|false $body
62+
*/
63+
private function mockRemoteClient(string $contentType, $body, ?string $expectedUrl = null, ?array $expectedOptions = null): void {
64+
$response = $this->createMock(IResponse::class);
65+
$response->method('getHeader')->with('Content-Type')->willReturn($contentType);
66+
$response->method('getBody')->willReturn($body);
67+
68+
$client = $this->createMock(IClient::class);
69+
$matcher = $client->expects(self::once())->method('get');
70+
if ($expectedUrl !== null) {
71+
$matcher->with($expectedUrl, $expectedOptions ?? self::anything());
72+
}
73+
$matcher->willReturn($response);
74+
75+
$this->clientService->method('newClient')->willReturn($client);
76+
}
77+
78+
public function testExists(): void {
79+
self::assertTrue($this->avatar->exists());
80+
}
81+
82+
public function testGetDisplayName(): void {
83+
self::assertSame('user@remote.example.com', $this->avatar->getDisplayName());
84+
}
85+
86+
public function testSetIsANoop(): void {
87+
$this->avatar->set('some-data');
88+
$this->addToAssertionCount(1);
89+
}
90+
91+
public function testGetFileFetchesTheAvatarFromTheRemoteInstance(): void {
92+
$this->config->method('getSystemValueBool')
93+
->with('sharing.federation.allowSelfSignedCertificates', false)
94+
->willReturn(false);
95+
96+
$this->mockRemoteClient(
97+
'image/png',
98+
'png-bytes',
99+
'https://remote.example.com/index.php/avatar/user/64',
100+
['verify' => true],
101+
);
102+
103+
$file = $this->avatar->getFile(64);
104+
self::assertInstanceOf(InMemoryFile::class, $file);
105+
self::assertSame('avatar.png', $file->getName());
106+
self::assertSame('png-bytes', $file->getContent());
107+
}
108+
109+
public function testGetFileRequestsTheDarkVariant(): void {
110+
$this->mockRemoteClient(
111+
'image/png',
112+
'png-bytes',
113+
'https://remote.example.com/index.php/avatar/user/512/dark',
114+
);
115+
116+
$this->avatar->getFile(512, true);
117+
}
118+
119+
public function testGetFileThrowsOnUnexpectedContentType(): void {
120+
$this->mockRemoteClient('text/html', '<html></html>');
121+
122+
$this->expectException(\Exception::class);
123+
$this->expectExceptionMessage('Unknown filetype');
124+
125+
$this->avatar->getFile(64);
126+
}
127+
128+
public function testUserChangedIsANoop(): void {
129+
$this->avatar->userChanged('displayName', 'old', 'new');
130+
$this->addToAssertionCount(1);
131+
}
132+
133+
public function testIsCustomAvatar(): void {
134+
self::assertTrue($this->avatar->isCustomAvatar());
135+
}
136+
}

0 commit comments

Comments
 (0)