Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>pull/56181/head
parent
85e1e06a6d
commit
7ba0cb4e2c
@ -0,0 +1,337 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\DAV\Tests\unit\Paginate; |
||||
|
||||
use OCA\DAV\Paginate\PaginateCache; |
||||
use OCA\DAV\Paginate\PaginatePlugin; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Sabre\DAV\Server; |
||||
use Sabre\HTTP\RequestInterface; |
||||
use Sabre\HTTP\ResponseInterface; |
||||
use Sabre\Xml\Service; |
||||
use Test\TestCase; |
||||
|
||||
class PaginatePluginTest extends TestCase { |
||||
|
||||
private PaginateCache&MockObject $cache; |
||||
private PaginatePlugin $plugin; |
||||
private Server&MockObject $server; |
||||
private RequestInterface&MockObject $request; |
||||
private ResponseInterface&MockObject $response; |
||||
|
||||
public function testOnMultiStatusCachesAndUpdatesResponse(): void { |
||||
$this->initializePlugin(); |
||||
|
||||
$fileProperties = [ |
||||
[ |
||||
'href' => '/file1', |
||||
200 => [ |
||||
'{DAV:}displayname' => 'File 1', |
||||
'{DAV:}resourcetype' => null |
||||
], |
||||
], |
||||
[ |
||||
'href' => '/file2', |
||||
200 => [ |
||||
'{DAV:}displayname' => 'File 2', |
||||
'{DAV:}resourcetype' => null |
||||
], |
||||
], |
||||
[ |
||||
'href' => '/file3', |
||||
200 => [ |
||||
'{DAV:}displayname' => 'File 3', |
||||
'{DAV:}resourcetype' => null |
||||
], |
||||
], |
||||
]; |
||||
|
||||
$this->request->expects(self::exactly(2)) |
||||
->method('hasHeader') |
||||
->willReturnMap([ |
||||
[PaginatePlugin::PAGINATE_HEADER, true], |
||||
[PaginatePlugin::PAGINATE_TOKEN_HEADER, false], |
||||
]); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('getUrl') |
||||
->willReturn('url'); |
||||
|
||||
$this->request->expects(self::exactly(2)) |
||||
->method('getHeader') |
||||
->willReturnMap([ |
||||
[PaginatePlugin::PAGINATE_COUNT_HEADER, 2], |
||||
[PaginatePlugin::PAGINATE_OFFSET_HEADER, 0], |
||||
]); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('setHeader') |
||||
->with(PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'); |
||||
|
||||
$this->cache->expects(self::once()) |
||||
->method('store') |
||||
->with( |
||||
'url', |
||||
$this->callback(function ($generator) { |
||||
self::assertInstanceOf(\Generator::class, $generator); |
||||
$items = iterator_to_array($generator); |
||||
self::assertCount(3, $items); |
||||
self::assertStringContainsString($this->getResponseXmlForFile('/dav/file1', 'File 1'), $items[0]); |
||||
self::assertStringContainsString($this->getResponseXmlForFile('/dav/file2', 'File 2'), $items[1]); |
||||
self::assertStringContainsString($this->getResponseXmlForFile('/dav/file3', 'File 3'), $items[2]); |
||||
return true; |
||||
}), |
||||
) |
||||
->willReturn([ |
||||
'token' => 'token', |
||||
'count' => 3, |
||||
]); |
||||
|
||||
$this->expectSequentialCalls( |
||||
$this->response, |
||||
'addHeader', |
||||
[ |
||||
[PaginatePlugin::PAGINATE_HEADER, 'true'], |
||||
[PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'], |
||||
[PaginatePlugin::PAGINATE_TOTAL_HEADER, '3'], |
||||
], |
||||
); |
||||
|
||||
$this->plugin->onMultiStatus($fileProperties); |
||||
|
||||
self::assertInstanceOf(\Iterator::class, $fileProperties); |
||||
// the iterator should be replaced with one that has the amount of |
||||
// items for the page |
||||
$items = iterator_to_array($fileProperties, false); |
||||
$this->assertCount(2, $items); |
||||
} |
||||
|
||||
private function initializePlugin(): void { |
||||
$this->expectSequentialCalls( |
||||
$this->server, |
||||
'on', |
||||
[ |
||||
['beforeMultiStatus', [$this->plugin, 'onMultiStatus'], 100], |
||||
['method:SEARCH', [$this->plugin, 'onMethod'], 1], |
||||
['method:PROPFIND', [$this->plugin, 'onMethod'], 1], |
||||
['method:REPORT', [$this->plugin, 'onMethod'], 1], |
||||
], |
||||
); |
||||
|
||||
$this->plugin->initialize($this->server); |
||||
} |
||||
|
||||
/** |
||||
* @param array<int, array<int, mixed>> $expectedCalls |
||||
*/ |
||||
private function expectSequentialCalls(MockObject $mock, string $method, array $expectedCalls): void { |
||||
$mock->expects(self::exactly(\count($expectedCalls))) |
||||
->method($method) |
||||
->willReturnCallback(function (...$args) use (&$expectedCalls) { |
||||
$expected = array_shift($expectedCalls); |
||||
self::assertNotNull($expected); |
||||
self::assertSame($expected, $args); |
||||
}); |
||||
} |
||||
|
||||
private function getResponseXmlForFile(string $fileName, string $displayName): string { |
||||
return preg_replace('/>\s+</', '><', <<<XML |
||||
<d:response> |
||||
<d:href>$fileName</d:href> |
||||
<d:propstat> |
||||
<d:prop> |
||||
<d:displayname>$displayName</d:displayname> |
||||
<d:resourcetype/> |
||||
</d:prop> |
||||
<d:status>HTTP/1.1 200 OK</d:status> |
||||
</d:propstat> |
||||
</d:response> |
||||
XML |
||||
); |
||||
} |
||||
|
||||
public function testOnMultiStatusSkipsWhenHeadersAndCacheExist(): void { |
||||
$this->initializePlugin(); |
||||
|
||||
$fileProperties = [ |
||||
[ |
||||
'href' => '/file1', |
||||
], |
||||
[ |
||||
'href' => '/file2', |
||||
], |
||||
]; |
||||
|
||||
$this->request->expects(self::exactly(2)) |
||||
->method('hasHeader') |
||||
->willReturnMap([ |
||||
[PaginatePlugin::PAGINATE_HEADER, true], |
||||
[PaginatePlugin::PAGINATE_TOKEN_HEADER, true], |
||||
]); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('getUrl') |
||||
->willReturn(''); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('getHeader') |
||||
->with(PaginatePlugin::PAGINATE_TOKEN_HEADER) |
||||
->willReturn('token'); |
||||
|
||||
$this->cache->expects(self::once()) |
||||
->method('exists') |
||||
->with('', 'token') |
||||
->willReturn(true); |
||||
|
||||
$this->cache->expects(self::never()) |
||||
->method('store'); |
||||
|
||||
$this->plugin->onMultiStatus($fileProperties); |
||||
|
||||
self::assertInstanceOf(\Iterator::class, $fileProperties); |
||||
self::assertSame( |
||||
[ |
||||
['href' => '/file1'], |
||||
['href' => '/file2'], |
||||
], |
||||
iterator_to_array($fileProperties) |
||||
); |
||||
} |
||||
|
||||
public function testOnMethodReturnsCachedResponse(): void { |
||||
$this->initializePlugin(); |
||||
|
||||
$response = $this->createMock(ResponseInterface::class); |
||||
|
||||
$this->request->expects(self::exactly(2)) |
||||
->method('hasHeader') |
||||
->willReturnMap([ |
||||
[PaginatePlugin::PAGINATE_TOKEN_HEADER, true], |
||||
[PaginatePlugin::PAGINATE_OFFSET_HEADER, true], |
||||
]); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('getUrl') |
||||
->willReturn('url'); |
||||
|
||||
$this->request->expects(self::exactly(4)) |
||||
->method('getHeader') |
||||
->willReturnMap([ |
||||
[PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'], |
||||
[PaginatePlugin::PAGINATE_OFFSET_HEADER, '2'], |
||||
[PaginatePlugin::PAGINATE_COUNT_HEADER, '4'], |
||||
]); |
||||
|
||||
$this->cache->expects(self::once()) |
||||
->method('exists') |
||||
->with('url', 'token') |
||||
->willReturn(true); |
||||
|
||||
$this->cache->expects(self::once()) |
||||
->method('get') |
||||
->with('url', 'token', 2, 4) |
||||
->willReturn((function (): \Generator { |
||||
yield $this->getResponseXmlForFile('/file1', 'File 1'); |
||||
yield $this->getResponseXmlForFile('/file2', 'File 2'); |
||||
})()); |
||||
|
||||
$response->expects(self::once()) |
||||
->method('setStatus') |
||||
->with(207); |
||||
|
||||
$response->expects(self::once()) |
||||
->method('addHeader') |
||||
->with(PaginatePlugin::PAGINATE_HEADER, 'true'); |
||||
|
||||
$this->expectSequentialCalls( |
||||
$response, |
||||
'setHeader', |
||||
[ |
||||
['Content-Type', 'application/xml; charset=utf-8'], |
||||
['Vary', 'Brief,Prefer'], |
||||
], |
||||
); |
||||
|
||||
$response->expects(self::once()) |
||||
->method('setBody') |
||||
->with($this->callback(function (string $body) { |
||||
// header of the XML |
||||
self::assertStringContainsString(<<<XML |
||||
<?xml version="1.0"?> |
||||
<d:multistatus xmlns:d="DAV:"> |
||||
XML, |
||||
$body); |
||||
self::assertStringContainsString($this->getResponseXmlForFile('/file1', 'File 1'), $body); |
||||
self::assertStringContainsString($this->getResponseXmlForFile('/file2', 'File 2'), $body); |
||||
// footer of the XML |
||||
self::assertStringContainsString('</d:multistatus>', $body); |
||||
|
||||
return true; |
||||
})); |
||||
|
||||
self::assertFalse($this->plugin->onMethod($this->request, $response)); |
||||
} |
||||
|
||||
public function testOnMultiStatusNoPaginateHeaderShouldSucceed(): void { |
||||
$this->initializePlugin(); |
||||
|
||||
$this->request->expects(self::once()) |
||||
->method('getUrl') |
||||
->willReturn(''); |
||||
|
||||
$this->cache->expects(self::never()) |
||||
->method('exists'); |
||||
$this->cache->expects(self::never()) |
||||
->method('store'); |
||||
|
||||
$this->plugin->onMultiStatus($this->request); |
||||
} |
||||
|
||||
public function testOnMethodNoTokenHeaderShouldSucceed(): void { |
||||
$this->initializePlugin(); |
||||
$this->request->expects(self::once()) |
||||
->method('hasHeader') |
||||
->with(PaginatePlugin::PAGINATE_TOKEN_HEADER) |
||||
->willReturn(false); |
||||
|
||||
$this->cache->expects(self::never()) |
||||
->method('exists'); |
||||
$this->cache->expects(self::never()) |
||||
->method('get'); |
||||
|
||||
$this->plugin->onMethod($this->request, $this->response); |
||||
} |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->cache = $this->createMock(PaginateCache::class); |
||||
|
||||
$this->server = $this->getMockBuilder(Server::class) |
||||
->disableOriginalConstructor() |
||||
->onlyMethods(['on', 'getHTTPPrefer', 'getBaseUri']) |
||||
->getMock(); |
||||
|
||||
$this->request = $this->createMock(RequestInterface::class); |
||||
$this->response = $this->createMock(ResponseInterface::class); |
||||
|
||||
$this->server->httpRequest = $this->request; |
||||
$this->server->httpResponse = $this->response; |
||||
$this->server->xml = new Service(); |
||||
$this->server->xml->namespaceMap = [ 'DAV:' => 'd' ]; |
||||
|
||||
$this->server->method('getHTTPPrefer') |
||||
->willReturn(['return' => null]); |
||||
$this->server->method('getBaseUri') |
||||
->willReturn('/dav/'); |
||||
|
||||
$this->plugin = new PaginatePlugin($this->cache, 2); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue