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> $expectedCalls */ private function expectSequentialCalls(MockObject $mock, string $method, array $expectedCalls): void { $mock->expects(self::exactly(\count($expectedCalls))) ->method($method) ->willReturnCallback(function (...$args) use (&$expectedCalls): void { $expected = array_shift($expectedCalls); self::assertNotNull($expected); self::assertSame($expected, $args); }); } private function getResponseXmlForFile(string $fileName, string $displayName): string { return preg_replace('/>\s+<', << $fileName $displayName HTTP/1.1 200 OK 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, $body); self::assertStringContainsString($this->getResponseXmlForFile('/file1', 'File 1'), $body); self::assertStringContainsString($this->getResponseXmlForFile('/file2', 'File 2'), $body); // footer of the XML self::assertStringContainsString('', $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); } }