Skip to content

Commit 4a8e36d

Browse files
test(sharereview): add unit tests for ShareReviewSource
Assisted-by: Claude Code:claude-sonnet-4-6 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent 27ebd86 commit 4a8e36d

3 files changed

Lines changed: 290 additions & 0 deletions

File tree

tests/bootstrap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@
2424

2525
require_once __DIR__ . '/../../../tests/bootstrap.php';
2626
require_once __DIR__ . '/../appinfo/autoload.php';
27+
28+
if (!interface_exists('OCA\ShareReview\Sources\ISource')) {
29+
require_once __DIR__ . '/unit/ShareReview/Stubs.php';
30+
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Deck\Tests\Unit\ShareReview;
11+
12+
use OCA\Deck\ShareReview\ShareReviewSource;
13+
use OCP\Constants;
14+
use OCP\DB\Exception;
15+
use OCP\DB\IResult;
16+
use OCP\DB\QueryBuilder\IExpressionBuilder;
17+
use OCP\DB\QueryBuilder\IQueryBuilder;
18+
use OCP\IDBConnection;
19+
use OCP\Share\IShare;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use PHPUnit\Framework\TestCase;
22+
use Psr\Log\LoggerInterface;
23+
24+
final class ShareReviewSourceTest extends TestCase {
25+
private MockObject $db;
26+
private MockObject $logger;
27+
private ShareReviewSource $source;
28+
29+
protected function setUp(): void {
30+
parent::setUp();
31+
$this->db = $this->createMock(IDBConnection::class);
32+
$this->logger = $this->createMock(LoggerInterface::class);
33+
$this->source = new ShareReviewSource($this->db, $this->logger);
34+
}
35+
36+
private function makeResult(array $rows): MockObject {
37+
$result = $this->createMock(IResult::class);
38+
$result->method('fetchAll')->willReturn($rows);
39+
$result->method('closeCursor')->willReturn(true);
40+
return $result;
41+
}
42+
43+
private function makeQb(array $fetchRows = [], int $statementRows = 0): MockObject {
44+
$expr = $this->createMock(IExpressionBuilder::class);
45+
$expr->method('eq')->willReturn('1=1');
46+
47+
$qb = $this->createMock(IQueryBuilder::class);
48+
$qb->method('select')->willReturnSelf();
49+
$qb->method('addSelect')->willReturnSelf();
50+
$qb->method('from')->willReturnSelf();
51+
$qb->method('leftJoin')->willReturnSelf();
52+
$qb->method('where')->willReturnSelf();
53+
$qb->method('orderBy')->willReturnSelf();
54+
$qb->method('delete')->willReturnSelf();
55+
$qb->method('createNamedParameter')->willReturn('?');
56+
$qb->method('createFunction')->willReturnArgument(0);
57+
$qb->method('expr')->willReturn($expr);
58+
$qb->method('executeQuery')->willReturn($this->makeResult($fetchRows));
59+
$qb->method('executeStatement')->willReturn($statementRows);
60+
61+
return $qb;
62+
}
63+
64+
private function makeThrowingQb(): MockObject {
65+
$expr = $this->createMock(IExpressionBuilder::class);
66+
67+
$qb = $this->createMock(IQueryBuilder::class);
68+
$qb->method('select')->willReturnSelf();
69+
$qb->method('addSelect')->willReturnSelf();
70+
$qb->method('from')->willReturnSelf();
71+
$qb->method('leftJoin')->willReturnSelf();
72+
$qb->method('where')->willReturnSelf();
73+
$qb->method('orderBy')->willReturnSelf();
74+
$qb->method('delete')->willReturnSelf();
75+
$qb->method('createNamedParameter')->willReturn('?');
76+
$qb->method('createFunction')->willReturnArgument(0);
77+
$qb->method('expr')->willReturn($expr);
78+
$qb->method('executeQuery')->willThrowException($this->createMock(Exception::class));
79+
$qb->method('executeStatement')->willThrowException($this->createMock(Exception::class));
80+
return $qb;
81+
}
82+
83+
/** @param array<string, mixed> $overrides */
84+
private function makeShareRow(array $overrides = []): array {
85+
return array_merge([
86+
'id' => 1,
87+
'type' => 0,
88+
'participant' => 'bob',
89+
'board_title' => 'My Board',
90+
'board_owner' => 'alice',
91+
'permission_edit' => 0,
92+
'permission_share' => 0,
93+
'permission_manage' => 0,
94+
], $overrides);
95+
}
96+
97+
public function testGetName(): void {
98+
$this->assertSame('Deck', $this->source->getName());
99+
}
100+
101+
public function testGetSharesEmpty(): void {
102+
$this->db->method('getQueryBuilder')->willReturn($this->makeQb());
103+
104+
$this->assertSame([], $this->source->getShares());
105+
}
106+
107+
public function testGetSharesUserShare(): void {
108+
$this->db->method('getQueryBuilder')->willReturn($this->makeQb([$this->makeShareRow()]));
109+
110+
$shares = $this->source->getShares();
111+
112+
$this->assertCount(1, $shares);
113+
$share = $shares[0];
114+
$this->assertSame(1, $share['id']);
115+
$this->assertSame('Deck', $share['app']);
116+
$this->assertSame('My Board (Board)', $share['object']);
117+
$this->assertSame('alice', $share['initiator']);
118+
$this->assertSame(IShare::TYPE_USER, $share['type']);
119+
$this->assertSame('bob', $share['recipient']);
120+
$this->assertSame(Constants::PERMISSION_READ, $share['permissions']);
121+
$this->assertFalse($share['password']);
122+
$this->assertSame('1970-01-01 01:00:00', $share['time']);
123+
$this->assertSame('', $share['action']);
124+
}
125+
126+
public function testGetSharesGroupShare(): void {
127+
$this->db->method('getQueryBuilder')->willReturn(
128+
$this->makeQb([$this->makeShareRow(['type' => 1, 'participant' => 'developers'])])
129+
);
130+
131+
$shares = $this->source->getShares();
132+
133+
$this->assertCount(1, $shares);
134+
$this->assertSame(IShare::TYPE_GROUP, $shares[0]['type']);
135+
$this->assertSame('developers', $shares[0]['recipient']);
136+
}
137+
138+
public function testGetSharesCircleShare(): void {
139+
$this->db->method('getQueryBuilder')->willReturn(
140+
$this->makeQb([$this->makeShareRow(['type' => 7, 'participant' => 'circle-uid'])])
141+
);
142+
143+
$shares = $this->source->getShares();
144+
145+
$this->assertSame(IShare::TYPE_CIRCLE, $shares[0]['type']);
146+
}
147+
148+
public function testGetSharesRemoteShare(): void {
149+
$this->db->method('getQueryBuilder')->willReturn(
150+
$this->makeQb([$this->makeShareRow(['type' => 6, 'participant' => 'user@remote.example'])])
151+
);
152+
153+
$shares = $this->source->getShares();
154+
155+
$this->assertSame(IShare::TYPE_REMOTE, $shares[0]['type']);
156+
}
157+
158+
public function testGetSharesMissingBoardFallback(): void {
159+
$this->db->method('getQueryBuilder')->willReturn(
160+
$this->makeQb([$this->makeShareRow(['id' => 42, 'board_title' => null, 'board_owner' => null])])
161+
);
162+
163+
$shares = $this->source->getShares();
164+
165+
$this->assertCount(1, $shares);
166+
$this->assertSame('Board 42 (Board)', $shares[0]['object']);
167+
}
168+
169+
public function testGetSharesReturnsEmptyOnDbException(): void {
170+
$this->db->method('getQueryBuilder')->willReturn($this->makeThrowingQb());
171+
$this->logger->expects($this->once())->method('error');
172+
173+
$this->assertSame([], $this->source->getShares());
174+
}
175+
176+
public function testComputePermissionsAllFalse(): void {
177+
$this->db->method('getQueryBuilder')->willReturn(
178+
$this->makeQb([$this->makeShareRow([
179+
'permission_edit' => 0,
180+
'permission_share' => 0,
181+
'permission_manage' => 0,
182+
])])
183+
);
184+
185+
$shares = $this->source->getShares();
186+
187+
$this->assertSame(Constants::PERMISSION_READ, $shares[0]['permissions']);
188+
}
189+
190+
public function testComputePermissionsEditFlag(): void {
191+
$this->db->method('getQueryBuilder')->willReturn(
192+
$this->makeQb([$this->makeShareRow(['permission_edit' => 1])])
193+
);
194+
195+
$shares = $this->source->getShares();
196+
197+
$expected = Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_CREATE | Constants::PERMISSION_DELETE;
198+
$this->assertSame($expected, $shares[0]['permissions']);
199+
}
200+
201+
public function testComputePermissionsShareFlag(): void {
202+
$this->db->method('getQueryBuilder')->willReturn(
203+
$this->makeQb([$this->makeShareRow(['permission_share' => 1])])
204+
);
205+
206+
$shares = $this->source->getShares();
207+
208+
$expected = Constants::PERMISSION_READ | Constants::PERMISSION_SHARE;
209+
$this->assertSame($expected, $shares[0]['permissions']);
210+
}
211+
212+
public function testComputePermissionsManageFlag(): void {
213+
$this->db->method('getQueryBuilder')->willReturn(
214+
$this->makeQb([$this->makeShareRow(['permission_manage' => 1])])
215+
);
216+
217+
$shares = $this->source->getShares();
218+
219+
$expected = Constants::PERMISSION_READ | 32;
220+
$this->assertSame($expected, $shares[0]['permissions']);
221+
}
222+
223+
public function testComputePermissionsAllTrue(): void {
224+
$this->db->method('getQueryBuilder')->willReturn(
225+
$this->makeQb([$this->makeShareRow([
226+
'permission_edit' => 1,
227+
'permission_share' => 1,
228+
'permission_manage' => 1,
229+
])])
230+
);
231+
232+
$shares = $this->source->getShares();
233+
234+
$expected = Constants::PERMISSION_READ
235+
| Constants::PERMISSION_UPDATE
236+
| Constants::PERMISSION_CREATE
237+
| Constants::PERMISSION_DELETE
238+
| Constants::PERMISSION_SHARE
239+
| 32;
240+
$this->assertSame($expected, $shares[0]['permissions']);
241+
}
242+
243+
public function testDeleteShareSuccess(): void {
244+
$this->db->method('getQueryBuilder')->willReturn($this->makeQb([], 1));
245+
$this->logger->expects($this->once())->method('info');
246+
247+
$this->assertTrue($this->source->deleteShare('7'));
248+
}
249+
250+
public function testDeleteShareNotFound(): void {
251+
$this->db->method('getQueryBuilder')->willReturn($this->makeQb([], 0));
252+
$this->logger->expects($this->once())->method('info');
253+
254+
$this->assertFalse($this->source->deleteShare('99'));
255+
}
256+
257+
public function testDeleteShareReturnsFalseOnDbException(): void {
258+
$this->db->method('getQueryBuilder')->willReturn($this->makeThrowingQb());
259+
$this->logger->expects($this->once())->method('info');
260+
$this->logger->expects($this->once())->method('error');
261+
262+
$this->assertFalse($this->source->deleteShare('7'));
263+
}
264+
}

tests/unit/ShareReview/Stubs.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\ShareReview\Sources;
11+
12+
/**
13+
* Runtime stub for the optional grc_sharereview app.
14+
* Only loaded when the real app is not installed.
15+
*/
16+
interface ISource {
17+
public function getName(): string;
18+
19+
public function getShares(): array;
20+
21+
public function deleteShare(string $shareId): bool;
22+
}

0 commit comments

Comments
 (0)