Julien Fastré 482c494034
Webdav: fully implements the controller and response
The controller is tested from real request scraped from apache mod_dav implementation. The requests were scraped using a wireshark-like tool. Those requests have been adapted to suit to our xml.
2024-01-15 20:19:02 +01:00

409 lines
15 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Controller;
use Chill\DocStoreBundle\Controller\WebdavController;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use DateTimeInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Ramsey\Uuid\Uuid;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Templating\EngineInterface;
/**
* @internal
* @coversNothing
*/
class WebdavControllerTest extends KernelTestCase
{
use ProphecyTrait;
private \Twig\Environment $engine;
protected function setUp(): void
{
self::bootKernel();
$this->engine = self::$container->get(\Twig\Environment::class);
}
private function buildController(): WebdavController
{
$storedObjectManager = new MockedStoredObjectManager();
return new WebdavController($this->engine, $storedObjectManager);
}
private function buildDocument(): StoredObject
{
$object = (new StoredObject())
->setType('application/vnd.oasis.opendocument.text');
$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,POST,DELETE,TRACE,PROPFIND,PROPPATCH,COPY,MOVE,PUT') 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,POST,DELETE,TRACE,PROPFIND,PROPPATCH,COPY,MOVE,PUT') 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(), $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(), $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 static function generateDataPropfindDocument(): iterable
{
$content =
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:"><prop><resourcetype xmlns="DAV:"/><IsReadOnly xmlns="http://ucb.openoffice.org/dav/props/"/><getcontenttype xmlns="DAV:"/><supportedlock xmlns="DAV:"/></prop></propfind>
XML;
$response =
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:" >
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/d</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:getcontenttype>application/vnd.oasis.opendocument.text</d:getcontenttype>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
<d:propstat>
<d:prop xmlns:ns0="http://ucb.openoffice.org/dav/props/">
<ns0:IsReadOnly/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML;
yield [$content, 207, $response, "get IsReadOnly and contenttype from server"];
$content =
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:">
<prop>
<IsReadOnly xmlns="http://ucb.openoffice.org/dav/props/"/>
</prop>
</propfind>
XML;
$response =
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/d</d:href>
<d:propstat>
<d:prop xmlns:ns0="http://ucb.openoffice.org/dav/props/">
<ns0:IsReadOnly/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML;
yield [$content, 207, $response, "get property IsReadOnly"];
yield [
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:">
<prop>
<BaseURI xmlns="http://ucb.openoffice.org/dav/props/"/>
</prop>
</propfind>
XML,
207,
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/d</d:href>
<d:propstat>
<d:prop xmlns:ns0="http://ucb.openoffice.org/dav/props/">
<ns0:BaseURI/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML,
"Test requesting an unknow property"
];
yield [
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:">
<prop>
<getlastmodified xmlns="DAV:"/>
</prop>
</propfind>
XML,
207,
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/d</d:href>
<d:propstat>
<d:prop>
<!-- the date scraped from a webserver is >Sun, 10 Sep 2023 14:10:23 GMT -->
<d:getlastmodified>Wed, 13 Sep 2023 14:15:00 +0200</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML,
"test getting the last modified date"
];
yield [
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:">
<propname/>
</propfind>
XML,
207,
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/d</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:creationdate/>
<d:getlastmodified>Wed, 13 Sep 2023 14:15:00 +0200</d:getlastmodified>
<!-- <d:getcontentlength/> -->
<d:getcontentlength>5</d:getcontentlength>
<!-- <d:getlastmodified/> -->
<d:getetag>"ab56b4d92b40713acc5af89985d4b786"</d:getetag>
<!--
<d:supportedlock/>
<d:lockdiscovery/>
-->
<!-- <d:getcontenttype/> -->
<d:getcontenttype>application/vnd.oasis.opendocument.text</d:getcontenttype>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML,
"test finding all properties"
];
}
public static function generateDataPropfindDirectory(): iterable
{
yield [
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:"><prop><resourcetype xmlns="DAV:"/><IsReadOnly xmlns="http://ucb.openoffice.org/dav/props/"/><getcontenttype xmlns="DAV:"/><supportedlock xmlns="DAV:"/></prop></propfind>
XML,
207,
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:getcontenttype>httpd/unix-directory</d:getcontenttype>
<!--
<d:supportedlock>
<d:lockentry>
<d:lockscope><d:exclusive/></d:lockscope>
<d:locktype><d:write/></d:locktype>
</d:lockentry>
<d:lockentry>
<d:lockscope><d:shared/></d:lockscope>
<d:locktype><d:write/></d:locktype>
</d:lockentry>
</d:supportedlock>
-->
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
<d:propstat>
<d:prop xmlns:ns0="http://ucb.openoffice.org/dav/props/">
<ns0:IsReadOnly/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML,
"test resourceType and IsReadOnly "
];
yield [
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<propfind xmlns="DAV:"><prop><CreatableContentsInfo xmlns="http://ucb.openoffice.org/dav/props/"/></prop></propfind>
XML,
207,
<<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/get/716e6688-4579-4938-acf3-c4ab5856803b/</d:href>
<d:propstat>
<d:prop xmlns:ns0="http://ucb.openoffice.org/dav/props/" >
<ns0:CreatableContentsInfo/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
XML,
"test creatableContentsInfo"
];
}
}
class MockedStoredObjectManager implements StoredObjectManagerInterface
{
public function getLastModified(StoredObject $document): DateTimeInterface
{
return new \DateTimeImmutable('2023-09-13T14:15');
}
public function getContentLength(StoredObject $document): int
{
return 5;
}
public function read(StoredObject $document): string
{
return 'abcde';
}
public function write(StoredObject $document, string $clearContent): void {}
public function etag(StoredObject $document): string
{
return 'ab56b4d92b40713acc5af89985d4b786';
}
}