engine = self::getContainer()->get(\Twig\Environment::class); } private function buildController(?EntityManagerInterface $entityManager = null, ?StoredObjectManagerInterface $storedObjectManager = null): WebdavController { if (null === $storedObjectManager) { $storedObjectManager = new MockedStoredObjectManager(); } if (null === $entityManager) { $entityManager = $this->createMock(EntityManagerInterface::class); } $security = $this->prophesize(Security::class); $security->isGranted(Argument::in(['SEE_AND_EDIT', 'SEE']), Argument::type(StoredObject::class)) ->willReturn(true); return new WebdavController($this->engine, $storedObjectManager, $security->reveal(), $entityManager); } private function buildDocument(): StoredObject { $object = (new StoredObject()) ->registerVersion(type: 'application/vnd.oasis.opendocument.text') ->getStoredObject(); $reflectionObject = new \ReflectionClass($object); $reflectionObjectUuid = $reflectionObject->getProperty('uuid'); $reflectionObjectUuid->setValue($object, Uuid::fromString('716e6688-4579-4938-acf3-c4ab5856803b')); return $object; } public function testGet(): void { $controller = $this->buildController(); $response = $controller->getDocument($this->buildDocument()); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('abcde', $response->getContent()); self::assertContains('etag', $response->headers->keys()); self::assertStringContainsString('ab56b4', $response->headers->get('etag')); } public function testOptionsOnDocument(): void { $controller = $this->buildController(); $response = $controller->optionsDocument($this->buildDocument()); self::assertEquals(200, $response->getStatusCode()); self::assertContains('allow', $response->headers->keys()); foreach (explode(',', 'OPTIONS,GET,HEAD,PROPFIND') as $method) { self::assertStringContainsString($method, $response->headers->get('allow')); } self::assertContains('dav', $response->headers->keys()); self::assertStringContainsString('1', $response->headers->get('dav')); } public function testOptionsOnDirectory(): void { $controller = $this->buildController(); $response = $controller->optionsDirectory($this->buildDocument()); self::assertEquals(200, $response->getStatusCode()); self::assertContains('allow', $response->headers->keys()); foreach (explode(',', 'OPTIONS,GET,HEAD,PROPFIND') as $method) { self::assertStringContainsString($method, $response->headers->get('allow')); } self::assertContains('dav', $response->headers->keys()); self::assertStringContainsString('1', $response->headers->get('dav')); } /** * @dataProvider generateDataPropfindDocument */ public function testPropfindDocument(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void { $controller = $this->buildController(); $request = new Request([], [], [], [], [], [], $requestContent); $request->setMethod('PROPFIND'); $response = $controller->propfindDocument($this->buildDocument(), '1234', $request); self::assertEquals($expectedStatusCode, $response->getStatusCode()); self::assertContains('content-type', $response->headers->keys()); self::assertStringContainsString('text/xml', $response->headers->get('content-type')); self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml'); self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message); } /** * @dataProvider generateDataPropfindDirectory */ public function testPropfindDirectory(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void { $controller = $this->buildController(); $request = new Request([], [], [], [], [], [], $requestContent); $request->setMethod('PROPFIND'); $request->headers->add(['Depth' => '0']); $response = $controller->propfindDirectory($this->buildDocument(), '1234', $request); self::assertEquals($expectedStatusCode, $response->getStatusCode()); self::assertContains('content-type', $response->headers->keys()); self::assertStringContainsString('text/xml', $response->headers->get('content-type')); self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml'); self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message); } public function testHeadDocument(): void { $controller = $this->buildController(); $response = $controller->headDocument($this->buildDocument()); self::assertEquals(200, $response->getStatusCode()); self::assertContains('content-length', $response->headers->keys()); self::assertContains('content-type', $response->headers->keys()); self::assertContains('etag', $response->headers->keys()); self::assertEquals('ab56b4d92b40713acc5af89985d4b786', $response->headers->get('etag')); self::assertEquals('application/vnd.oasis.opendocument.text', $response->headers->get('content-type')); self::assertEquals(5, $response->headers->get('content-length')); } public function testPutDocument(): void { $document = $this->buildDocument(); $entityManager = $this->createMock(EntityManagerInterface::class); $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); // entity manager must be flushed $entityManager->expects($this->once()) ->method('flush'); // object must be written by StoredObjectManager $storedObjectManager->expects($this->once()) ->method('write') ->with($this->identicalTo($document), $this->identicalTo('1234')); $controller = $this->buildController($entityManager, $storedObjectManager); $request = new Request(content: '1234'); $response = $controller->putDocument($document, $request); self::assertEquals(204, $response->getStatusCode()); self::assertEquals('', $response->getContent()); } public static function generateDataPropfindDocument(): iterable { $content = <<<'XML' XML; $response = <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/d application/vnd.oasis.opendocument.text HTTP/1.1 200 OK HTTP/1.1 404 Not Found XML; yield [$content, 207, $response, 'get IsReadOnly and contenttype from server']; $content = <<<'XML' XML; $response = <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/d HTTP/1.1 404 Not Found XML; yield [$content, 207, $response, 'get property IsReadOnly']; yield [ <<<'XML' XML, 207, <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/d HTTP/1.1 404 Not Found XML, 'Test requesting an unknow property', ]; yield [ <<<'XML' XML, 207, <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/d Wed, 13 Sep 2023 14:15:00 +0200 HTTP/1.1 200 OK XML, 'test getting the last modified date', ]; yield [ <<<'XML' XML, 207, <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/d Wed, 13 Sep 2023 14:15:00 +0200 5 "ab56b4d92b40713acc5af89985d4b786" application/vnd.oasis.opendocument.text HTTP/1.1 200 OK XML, 'test finding all properties', ]; } public static function generateDataPropfindDirectory(): iterable { yield [ <<<'XML' XML, 207, <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/ httpd/unix-directory HTTP/1.1 200 OK HTTP/1.1 404 Not Found XML, 'test resourceType and IsReadOnly ', ]; yield [ <<<'XML' XML, 207, <<<'XML' /dav/1234/get/716e6688-4579-4938-acf3-c4ab5856803b/ HTTP/1.1 404 Not Found XML, 'test creatableContentsInfo', ]; } } class MockedStoredObjectManager implements StoredObjectManagerInterface { public function getLastModified(StoredObject|StoredObjectVersion $document): \DateTimeInterface { return new \DateTimeImmutable('2023-09-13T14:15', new \DateTimeZone('+02:00')); } public function getContentLength(StoredObject|StoredObjectVersion $document): int { return 5; } public function read(StoredObject|StoredObjectVersion $document): string { return 'abcde'; } public function write(StoredObject $document, string $clearContent, ?string $contentType = null): StoredObjectVersion { return $document->registerVersion(); } public function etag(StoredObject|StoredObjectVersion $document): string { return 'ab56b4d92b40713acc5af89985d4b786'; } public function clearCache(): void {} public function delete(StoredObjectVersion $storedObjectVersion): void {} public function exists(StoredObject|StoredObjectVersion $document): bool { return true; } }