diff --git a/composer.json b/composer.json
index 16e4b0fa00e59c244f33d18be3cc0ee7e3834a2c..01b2d96671848da126d974a01c80d5b0c0b6c8d0 100644
--- a/composer.json
+++ b/composer.json
@@ -10,8 +10,8 @@
         "dbp/relay-core-bundle": "^0.1.56",
         "fgrosse/phpasn1": "^2.0",
         "symfony/event-dispatcher": "^5.4",
-        "symfony/framework-bundle": "^5.4",
         "symfony/mailer": "^5.4",
+        "symfony/mime": "^5.4",
         "symfony/orm-pack": "^2.2",
         "web-token/jwt-checker": "^2.1",
         "web-token/jwt-core": "^2.1",
@@ -72,5 +72,11 @@
         "coverage": [
             "@php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html _coverage"
         ]
-    }
+    },
+    "repositories": [
+        {
+            "type": "vcs",
+            "url": "git@gitlab.tugraz.at:dbp/relay/dbp-relay-blob-connector-filesystem-bundle.git"
+        }
+    ]
 }
diff --git a/composer.lock b/composer.lock
index c8de48a3998f3fccd8eec83f4c1a31d585c8aa82..9cfeb00cccebe905e4f84ca3b7b6e3205929e9c9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -9191,6 +9191,78 @@
             ],
             "time": "2022-02-24T20:20:32+00:00"
         },
+        {
+            "name": "dbp/relay-blob-connector-filesystem-bundle",
+            "version": "dev-main",
+            "source": {
+                "type": "git",
+                "url": "git@gitlab.tugraz.at:dbp/relay/dbp-relay-blob-connector-filesystem-bundle.git",
+                "reference": "d948924db59ad807c0544f35aaa4984a7ef2a94b"
+            },
+            "require": {
+                "api-platform/core": "^2.6",
+                "dbp/relay-blob-bundle": "dev-main",
+                "dbp/relay-core-bundle": "^v0.1.56",
+                "ext-json": "*",
+                "php": ">=7.3",
+                "symfony/framework-bundle": "^5.4"
+            },
+            "require-dev": {
+                "ext-pdo_sqlite": "*",
+                "friendsofphp/php-cs-fixer": "^3.0",
+                "phpstan/phpstan": "^1.0.0",
+                "phpstan/phpstan-phpunit": "^1.0.0",
+                "phpstan/phpstan-symfony": "^1.2",
+                "phpunit/phpunit": "^9",
+                "symfony/browser-kit": "^5.4",
+                "symfony/http-client": "^5.4",
+                "symfony/monolog-bundle": "^3.7",
+                "symfony/phpunit-bridge": "^5.4",
+                "vimeo/psalm": "^4.2.1"
+            },
+            "default-branch": true,
+            "type": "symfony-bundle",
+            "autoload": {
+                "psr-4": {
+                    "Dbp\\Relay\\BlobConnectorFilesystemBundle\\": "src/"
+                }
+            },
+            "autoload-dev": {
+                "psr-4": {
+                    "Dbp\\Relay\\BlobConnectorFilesystemBundle\\Tests\\": "tests/"
+                }
+            },
+            "scripts": {
+                "test": [
+                    "@php vendor/bin/phpunit"
+                ],
+                "phpstan": [
+                    "@php vendor/bin/phpstan analyze --ansi"
+                ],
+                "psalm": [
+                    "@php vendor/bin/psalm"
+                ],
+                "lint": [
+                    "@composer run cs",
+                    "@composer run phpstan",
+                    "@composer run psalm"
+                ],
+                "cs-fix": [
+                    "@php vendor/bin/php-cs-fixer --ansi fix"
+                ],
+                "cs": [
+                    "@php vendor/bin/php-cs-fixer --ansi fix --dry-run --diff"
+                ],
+                "coverage": [
+                    "@php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html _coverage"
+                ]
+            },
+            "license": [
+                "AGPL-3.0-or-later"
+            ],
+            "description": "A template bundle for the Relay API gateway",
+            "time": "2022-12-19T00:08:14+00:00"
+        },
         {
             "name": "dnoegel/php-xdg-base-dir",
             "version": "v0.1.1",
@@ -12310,7 +12382,9 @@
     ],
     "aliases": [],
     "minimum-stability": "stable",
-    "stability-flags": [],
+    "stability-flags": {
+        "dbp/relay-blob-connector-filesystem-bundle": 20
+    },
     "prefer-stable": false,
     "prefer-lowest": false,
     "platform": {
diff --git a/src/Controller/CreateFileDataAction.php b/src/Controller/CreateFileDataAction.php
index da26c2051ec119175476ceffd6a7445c5233aba6..483ba927f2e8e99a552f318326089efaced4ae99 100644
--- a/src/Controller/CreateFileDataAction.php
+++ b/src/Controller/CreateFileDataAction.php
@@ -23,6 +23,7 @@ final class CreateFileDataAction extends BaseBlobController
     public function __construct(BlobService $blobService)
     {
         $this->blobService = $blobService;
+//        dump('CreateFileDataAction::__construct()');
     }
 
     /**
@@ -31,13 +32,16 @@ final class CreateFileDataAction extends BaseBlobController
      */
     public function __invoke(Request $request): FileData
     {
-        $sig = $request->headers->get('x-dbp-signature','');
+//        dump('CreateFileDataAction::invoke()');
+        $sig = $request->headers->get('x-dbp-signature', '');
         if (!$sig) {
             throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
         }
-        $bucketId = (string) $request->query->get('bucketID', '');
-        $creationTime = (string) $request->query->get('creationTime', '');
+        $bucketId = $request->query->get('bucketID', '');
+        assert(is_string($bucketId));
+        $creationTime = $request->query->get('creationTime', 0);
         $prefix = $request->query->get('prefix', '');
+        assert(is_string($prefix));
 
         if (!$bucketId || !$creationTime) {
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:createFileData-unset-sig-params');
@@ -50,14 +54,14 @@ final class CreateFileDataAction extends BaseBlobController
         $secret = $bucket->getPublicKey();
 
         $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
-        dump($data);
+//        dump($data);
 
         // check if signed params aer equal to request params
         if ($data['bucketID'] !== $bucketId) {
             dump($data['bucketID'], $bucketId);
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'BucketId change forbidden', 'blob:bucketid-change-forbidden');
         }
-        if ((int)$data['creationTime'] !== (int)$creationTime) {
+        if ((int) $data['creationTime'] !== (int) $creationTime) {
             dump($data['creationTime'], $creationTime);
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Creation Time change forbidden', 'blob:creationtime-change-forbidden');
         }
@@ -74,6 +78,10 @@ final class CreateFileDataAction extends BaseBlobController
 
         // Set exists until time
         $fileData->setExistsUntil($fileData->getDateCreated()->add(new \DateInterval($fileData->getRetentionDuration())));
+        // Set everything else...
+        $fileData->setFileName($data['fileName']);
+        $fileData->setNotifyEmail($data['notifyEmail'] ?? '');
+        $fileData->setAdditionalMetadata($data['additionalMetadata'] ?? '');
 
         // Use given service for bucket
         if (!$bucket->getService()) {
@@ -82,7 +90,7 @@ final class CreateFileDataAction extends BaseBlobController
 
         /** @var ?UploadedFile $uploadedFile */
         $uploadedFile = $fileData->getFile();
-        $fileData->setExtension($uploadedFile->guessExtension());
+        $fileData->setExtension($uploadedFile->guessExtension() ?? substr($fileData->getFileName(), -3, 3));
         $hash = hash('sha256', $uploadedFile->getContent());
 
         // check hash of file
@@ -93,7 +101,7 @@ final class CreateFileDataAction extends BaseBlobController
 
         // Check quota
         $bucketsizeByte = (int) $this->blobService->getQuotaOfBucket($fileData->getBucketID())['bucketSize'];
-        $bucketQuotaByte = $fileData->getBucket()->getQuota() * 1024 *1024; // Convert mb to Byte
+        $bucketQuotaByte = $fileData->getBucket()->getQuota() * 1024 * 1024; // Convert mb to Byte
         $newBucketSizeByte = $bucketsizeByte + $fileData->getFileSize();
         if ($newBucketSizeByte > $bucketQuotaByte) {
             $this->blobService->sendNotifyQuota($bucket);
diff --git a/src/Controller/DeleteFileDatasByPrefix.php b/src/Controller/DeleteFileDatasByPrefix.php
index 94f2cfd77abb43d7d7fae7315b182320813080ba..deecc53cbf270a08a81421e85408778183f4b8c5 100644
--- a/src/Controller/DeleteFileDatasByPrefix.php
+++ b/src/Controller/DeleteFileDatasByPrefix.php
@@ -28,9 +28,12 @@ class DeleteFileDatasByPrefix extends BaseBlobController
         if (!$sig) {
             throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
         }
+
         $bucketId = $request->query->get('bucketID', '');
-        $creationTime = $request->query->get('creationTime', '');
+        assert(is_string($bucketId));
+        $creationTime = $request->query->get('creationTime', 0);
         $prefix = $request->query->get('prefix', '');
+        assert(is_string($prefix));
 
         if (!$bucketId || !$creationTime || !$prefix) {
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:delete-files-per-prefix-unset-sig-params');
@@ -43,13 +46,13 @@ class DeleteFileDatasByPrefix extends BaseBlobController
 
         $secret = $bucket->getPublicKey();
         $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
-        dump($data);
+//        dump($data);
 
         // check if signed params are equal to request params
         if ($data['bucketID'] !== $bucketId) {
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'BucketId change forbidden', 'blob:bucketid-change-forbidden');
         }
-        if ((int)$data['creationTime'] !== (int)$creationTime) {
+        if ((int) $data['creationTime'] !== (int) $creationTime) {
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Creation Time change forbidden', 'blob:creationtime-change-forbidden');
         }
         if ($data['prefix'] !== $prefix) {
diff --git a/src/DataPersister/FileDataDataPersister.php b/src/DataPersister/FileDataDataPersister.php
index c75f91ac499a804d7a3e16f5182ee35306cc7a90..c68d8294fbbde3adbd61b5aec9759970499cf77f 100644
--- a/src/DataPersister/FileDataDataPersister.php
+++ b/src/DataPersister/FileDataDataPersister.php
@@ -28,6 +28,11 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa
         return $data instanceof FileData;
     }
 
+    /**
+     * @param Filedata $data
+     *
+     * @return FileData|void
+     */
     public function persist($data, array $context = [])
     {
         // no need to check, because signature is checked by getting the data
@@ -55,8 +60,6 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa
 
     /**
      * @param mixed $data
-     * @param array $context
-     * @return void
      */
     public function remove($data, array $context = []): void
     {
diff --git a/src/DataProvider/FileDataDataProvider.php b/src/DataProvider/FileDataDataProvider.php
index a05a93391481e344072a2d8afeff0471009fcd20..427f34163d0fabdae9cc3290bc306f93affa8ba6 100644
--- a/src/DataProvider/FileDataDataProvider.php
+++ b/src/DataProvider/FileDataDataProvider.php
@@ -53,11 +53,12 @@ class FileDataDataProvider extends AbstractDataProvider
 
     protected function getFileDataById($id, array $filters): object
     {
-        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature','');
+        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature', '');
         if (!$sig) {
             throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
         }
         $bucketId = $filters['bucketID'] ?? '';
+        assert(is_string($bucketId));
         if (!$bucketId) {
             throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID');
         }
@@ -89,11 +90,12 @@ class FileDataDataProvider extends AbstractDataProvider
 
     protected function getPage(int $currentPageNumber, int $maxNumItemsPerPage, array $filters = [], array $options = []): array
     {
-        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature','');
+        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature', '');
         if (!$sig) {
             throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
         }
         $bucketId = $filters['bucketID'] ?? '';
+        assert(is_string($bucketId));
         if (!$bucketId) {
             throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID');
         }
@@ -125,15 +127,13 @@ class FileDataDataProvider extends AbstractDataProvider
     }
 
     /**
-     * Check dbp-signature on GET request
+     * Check dbp-signature on GET request.
      *
-     * @param string $secret
-     * @param array $filters
      * @throws \JsonException
      */
     private function checkSignature(string $secret, array $filters): void
     {
-        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature','');
+        $sig = $this->requestStack->getCurrentRequest()->headers->get('x-dbp-signature', '');
         if (!$sig) {
             throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
         }
@@ -145,18 +145,17 @@ class FileDataDataProvider extends AbstractDataProvider
         }
 
         $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
-        dump($data);
+//        dump($data);
 
         // check if signed params aer equal to request params
         if ($data['bucketID'] !== $bucketId) {
             dump($data['bucketID'], $bucketId);
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'BucketId change forbidden', 'blob:bucketid-change-forbidden');
         }
-        if ((int)$data['creationTime'] !== (int)$creationTime) {
+        if ((int) $data['creationTime'] !== (int) $creationTime) {
             dump($data['creationTime'], $creationTime);
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Creation Time change forbidden', 'blob:creationtime-change-forbidden');
         }
         // TODO check if request is NOT too old
-
     }
 }
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 7832154486958f43fb9fd8f1526e86dde3f3b98a..bdfb1e091824704fe8fca73f4d77e447ec185b5f 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -18,8 +18,8 @@ class Configuration implements ConfigurationInterface
                 ->children()
                     ->scalarNode('database_url')
                         ->isRequired()
-                        ->cannotBeEmpty()
-                        ->defaultValue('%env(resolve:DATABASE_URL)%')
+                        //->cannotBeEmpty()
+                        ->defaultValue('%env(resolve:BLOB_DATABASE_NAME)%')
                     ->end()
                     ->arrayNode('buckets')
                         ->isRequired()
diff --git a/src/DependencyInjection/DbpRelayBlobExtension.php b/src/DependencyInjection/DbpRelayBlobExtension.php
index 97e79704e177fe0447d8432e38774490e9a0c7dd..641ce210f2dbd8cfa61f595e192d165e8819555f 100644
--- a/src/DependencyInjection/DbpRelayBlobExtension.php
+++ b/src/DependencyInjection/DbpRelayBlobExtension.php
@@ -50,7 +50,7 @@ class DbpRelayBlobExtension extends ConfigurableExtension implements PrependExte
             'dbal' => [
                 'connections' => [
                     'dbp_relay_blob_bundle' => [
-                        'url' => $config['database_url'] ?? '',
+                        'url' => $config['database_url'] ?? 'sqlite:///var/dbp_relay_blob_test.db',
                     ],
                 ],
             ],
diff --git a/src/Helper/DenyAccessUnlessCheckSignature.php b/src/Helper/DenyAccessUnlessCheckSignature.php
index 0a92a8363661265666629529ac5faeb0f8a45290..82ab32c77a9a9690d2760e692b9b0801185c0804 100644
--- a/src/Helper/DenyAccessUnlessCheckSignature.php
+++ b/src/Helper/DenyAccessUnlessCheckSignature.php
@@ -18,10 +18,11 @@ use Symfony\Component\HttpFoundation\Response;
 class DenyAccessUnlessCheckSignature
 {
     /**
-     * Create a JWS token
+     * Create a JWS token.
+     *
+     * @param string $secret  to create the (symmetric) JWK from
+     * @param array  $payload to create the token from
      *
-     * @param string $secret to create the (symmetric) JWK from
-     * @param array $payload to create the token from
      * @throws \JsonException
      */
     public static function create(string $secret, array $payload): string
@@ -32,11 +33,13 @@ class DenyAccessUnlessCheckSignature
     }
 
     /**
-     * Verify a JWS token
+     * Verify a JWS token.
      *
      * @param string $secret to create the (symmetric) JWK from
-     * @param string $token to verify
+     * @param string $token  to verify
+     *
      * @return array extracted payload from token
+     *
      * @throws \JsonException
      * @throws ApiError
      */
@@ -46,6 +49,7 @@ class DenyAccessUnlessCheckSignature
         $payload = [];
 
         if (!self::verifyToken($jwk, $token, $payload)) {
+            dump(['token' => $token, 'payload' => $payload, 'secret' => $secret]);
             throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature invalid', 'blob:signature-invalid');
         }
 
@@ -56,7 +60,6 @@ class DenyAccessUnlessCheckSignature
      * Create the JWK from a shared secret.
      *
      * @param string $secret to create the (symmetric) JWK from
-     * @return JWK
      */
     public static function createJWK(string $secret): JWK
     {
@@ -72,9 +75,11 @@ class DenyAccessUnlessCheckSignature
     /**
      * Generate the token.
      *
-     * @param JWK $jwk json web key
+     * @param JWK   $jwk     json web key
      * @param array $payload as json string to secure
+     *
      * @return string secure token
+     *
      * @throws \JsonException
      */
     public static function generateToken(JWK $jwk, array $payload): string
@@ -94,10 +99,10 @@ class DenyAccessUnlessCheckSignature
     /**
      * Verify a JWS token.
      *
-     * @param JWK $jwk
-     * @param string $token the JWS token as string
-     * @param array $payload to extract from token on success
+     * @param string $token   the JWS token as string
+     * @param array  $payload to extract from token on success
      * @return bool
+     *
      * @throws \JsonException
      */
     public static function verifyToken(JWK $jwk, string $token, array &$payload): bool
@@ -110,6 +115,9 @@ class DenyAccessUnlessCheckSignature
         if ($ok = $jwsVerifier->verifyWithKey($jws, $jwk, 0)) {
             $payload = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR);
         }
+//        $ok = $jwsVerifier->verifyWithKey($jws, $jwk, 0);
+//        $payload = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR);
+
         return $ok;
     }
 }
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index d84b7f47bebc29ff37e42b2f948357aebe2b086f..966b2d7beddb9c114e31f088e058780656cb8bc5 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -2,6 +2,8 @@ services:
   Dbp\Relay\BlobBundle\Service\BlobService:
     autowire: true
     autoconfigure: true
+    arguments:
+      $em: '@doctrine.orm.dbp_relay_blob_bundle_entity_manager'
 
   Dbp\Relay\BlobBundle\Controller\:
     tags: [ 'controller.service_arguments' ]
diff --git a/src/Resources/config/services_test.yaml b/src/Resources/config/services_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1a6ccd30e99345c7983d1c624439576203f73990
--- /dev/null
+++ b/src/Resources/config/services_test.yaml
@@ -0,0 +1,29 @@
+services:
+  Dbp\Relay\BlobConnectorFilesystemBundle\Cron\CleanupCronJob:
+    autowire: true
+    autoconfigure: true
+
+  Dbp\Relay\BlobConnectorFilesystemBundle\Service\FilesystemService:
+    autowire: true
+    autoconfigure: true
+    public: true
+
+  Dbp\Relay\BlobConnectorFilesystemBundle\Service\ShareLinkPersistenceService:
+    autowire: true
+    autoconfigure: true
+    public: true
+
+  Dbp\Relay\BlobConnectorFilesystemBundle\Service\ConfigurationService:
+    autowire: true
+    autoconfigure: true
+
+  Dbp\Relay\BlobConnectorFilesystemBundle\Controller\:
+    tags: [ 'controller.service_arguments' ]
+    resource: '../../../../relay-blob-connector-filesystem-bundle/src/Controller/'
+    autowire: true
+    autoconfigure: true
+
+  Dbp\Relay\BlobBundle\Tests\DummyFileSystemService:
+    autowire: true
+    autoconfigure: true
+    public: true
diff --git a/src/Service/BlobService.php b/src/Service/BlobService.php
index 685142209658bf5cd435f39d9869573bd301b420..55a7edb61fb9719e86fcb2b67feec51117b4c244 100644
--- a/src/Service/BlobService.php
+++ b/src/Service/BlobService.php
@@ -7,8 +7,8 @@ namespace Dbp\Relay\BlobBundle\Service;
 use Dbp\Relay\BlobBundle\Entity\Bucket;
 use Dbp\Relay\BlobBundle\Entity\FileData;
 use Dbp\Relay\CoreBundle\Exception\ApiError;
+use Doctrine\ORM\EntityManager;
 use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\Persistence\ManagerRegistry;
 use PHPUnit\TextUI\XmlConfiguration\File;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 use Symfony\Component\HttpFoundation\Request;
@@ -39,16 +39,18 @@ class BlobService
      */
     private $datasystemService;
 
-    public function __construct(ManagerRegistry $managerRegistry, ConfigurationService $configurationService, DatasystemProviderService $datasystemService)
+    public function __construct(EntityManagerInterface $em, ConfigurationService $configurationService, DatasystemProviderService $datasystemService)
     {
-        $manager = $managerRegistry->getManager('dbp_relay_blob_bundle');
-        assert($manager instanceof EntityManagerInterface);
-        $this->em = $manager;
-
+        $this->em = $em;
         $this->configurationService = $configurationService;
         $this->datasystemService = $datasystemService;
     }
 
+    public function setDatasystemService(DatasystemProviderService $datasystemService): void
+    {
+        $this->datasystemService = $datasystemService;
+    }
+
     public function checkConnection()
     {
         $this->em->getConnection()->connect();
@@ -126,6 +128,7 @@ class BlobService
         $fileData->setLastAccess($time);
 
         try {
+//            dump($fileData);
             $this->em->persist($fileData);
             $this->em->flush();
         } catch (\Exception $e) {
@@ -350,7 +353,7 @@ class BlobService
 
     private function sendEmail(array $config, array $context)
     {
-        $loader = new FilesystemLoader(dirname(__FILE__).'/../Resources/views/');
+        $loader = new FilesystemLoader(__DIR__ . '/../Resources/views/');
         $twig = new Environment($loader);
 
         $template = $twig->load($config['html_template']);
diff --git a/tests/CurlGetTest.php b/tests/CurlGetTest.php
index 7fd8560d3aad63c0e46d8280eeb963a86fc322a7..29f24b1b2b3dbbe11048c17110d53260041ad78d 100644
--- a/tests/CurlGetTest.php
+++ b/tests/CurlGetTest.php
@@ -1,20 +1,82 @@
-<?php
-
-declare(strict_types=1);
+<?php declare(strict_types=1);
 
 namespace Dbp\Relay\BlobBundle\Tests;
 
+use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
+use Dbp\Relay\BlobBundle\Controller\CreateFileDataAction;
+use Dbp\Relay\BlobBundle\Controller\DeleteFileDatasByPrefix;
+use Dbp\Relay\BlobBundle\Entity\FileData;
 use Dbp\Relay\BlobBundle\Helper\DenyAccessUnlessCheckSignature;
+use Dbp\Relay\BlobBundle\Helper\PoliciesStruct;
+use Dbp\Relay\BlobBundle\Service\BlobService;
+use Dbp\Relay\BlobBundle\Service\ConfigurationService;
+use Dbp\Relay\BlobBundle\Service\DatasystemProviderServiceInterface;
+use Dbp\Relay\CoreBundle\TestUtils\UserAuthTrait;
+use Doctrine\ORM\Tools\SchemaTool;
 use Exception;
-use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelInterface;
+use function uuid_is_valid;
 
-class CurlGetTest extends TestCase
+class DummyFileSystemService implements DatasystemProviderServiceInterface
 {
+    public function saveFile(FileData &$fileData): ?FileData
+    {
+        return $fileData;
+    }
+
+    public function renameFile(FileData &$fileData): ?FileData
+    {
+        return $fileData;
+    }
+
+    public function getLink(FileData &$fileData, PoliciesStruct $policiesStruct): ?FileData
+    {
+        return $fileData;
+    }
+
+    public function removeFile(FileData &$fileData): bool
+    {
+        return true;
+    }
+}
+
+class CurlGetTest extends ApiTestCase
+{
+    use UserAuthTrait;
+
+    /** @var \Doctrine\ORM\EntityManagerInterface $entityManager */
+    private $entityManager;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        /** @var KernelInterface $kernel */
+        $kernel = self::bootKernel();
+
+        if ('test' !== $kernel->getEnvironment()) {
+            throw new \RuntimeException('Execution only in Test environment possible!');
+        }
+
+        $this->entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');
+        $metaData = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        $schemaTool = new SchemaTool($this->entityManager);
+        $schemaTool->updateSchema($metaData);
+    }
+
     public function testGet(): void
     {
         try {
-            $secret = '08d848fd868d83646778b87dd0695b10f59c78e23b286e9884504d1bb43cce93';
-            $bucketId = '1234';
+            $client = $this->withUser('foobar');
+            $configService = $client->getContainer()->get(ConfigurationService::class);
+
+            $bucket = $configService->getBuckets()[0];
+            $secret = $bucket->getPublicKey();
+            $bucketId = $bucket->getIdentifier();
             $creationTime = date('U');
             $prefix = 'playground';
             $payload = [
@@ -23,32 +85,304 @@ class CurlGetTest extends TestCase
                 'prefix' => $prefix,
             ];
 
-            $uri = "http://127.0.0.1:8000/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime";
+            $token = DenyAccessUnlessCheckSignature::create($secret, $payload);
+
+            $url = "/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime";
+            $options = [
+                'headers' => [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+                    'x-dbp-signature' => $token,
+                ],
+            ];
+
+            /** @noinspection PhpInternalEntityUsedInspection */
+            $client->getKernelBrowser()->followRedirects();
+
+            /** @var Response $response */
+            $response = $client->request('GET', $url, $options);
+
+            $this->assertEquals(200, $response->getStatusCode());
+
+            $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
+            $this->assertArrayHasKey('hydra:view', $data);
+            $this->assertArrayHasKey('hydra:member', $data);
+            $this->assertCount(0, $data['hydra:member'], 'More files than expected');
+        } catch (\Throwable $e) {
+            echo $e->getTraceAsString() . "\n";
+            $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * Integration test for a full life cycle: create, use and destroy a blob
+     *  - create blob no 1
+     *  - get all blobs: blob no 1 is available
+     *  - create blob no 2
+     *  - get all blobs: two blobs are available
+     *  - delete all blobs for the prefix: no entries in database
+     *  - get all blobs: no blobs available
+     *
+     * @return void
+     */
+    public function testPostGetDelete(): void
+    {
+        try {
+
+            $files = [
+                0 => [
+                    'name' => $n = 'Test.php',
+                    'path' => $p = __DIR__ . '/' . $n,
+                    'content' => $c = file_get_contents($p),
+                    'hash' => hash('sha256', $c),
+                    'size' => strlen($c),
+                    'mime' => 'application/x-php',
+                    'retention' => 'P1W',
+                ],
+                1 => [
+                    'name' => $n = 'Kernel.php',
+                    'path' => $p = __DIR__ . '/' . $n,
+                    'content' => $c = file_get_contents($p),
+                    'hash' => hash('sha256', $c),
+                    'size' => strlen($c),
+                    'mime' => 'application/x-php',
+                    'retention' => 'P1M',
+                ],
+            ];
+
+            $client = $this->withUser('foobar');
+            $blobService = $client->getContainer()->get(BlobService::class);
+            $configService = $client->getContainer()->get(ConfigurationService::class);
+
+            $bucket = $configService->getBuckets()[0];
+            $secret = $bucket->getPublicKey();
+            $bucketId = $bucket->getIdentifier();
+            $creationTime = date('U');
+            $prefix = 'playground';
+            $notifyEmail = 'eugen.neuber@tugraz.at';
+
+            $url = "/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime";
+
+            // =======================================================
+            // POST a file
+            // =======================================================
+            $payload = [
+                'bucketID' => $bucketId,
+                'creationTime' => $creationTime,
+                'prefix' => $prefix,
+                'fileName' => $files[0]['name'],
+                'fileHash' => $files[0]['hash'],
+                'notifyEmail' => $notifyEmail,
+                'retentionDuration' => $files[0]['retention'],
+                'additionalMetadata' => '',
+            ];
+
+            $token = DenyAccessUnlessCheckSignature::create($secret, $payload);
+
+            $requestPost = Request::create($url, 'POST', [], [],
+                [
+                    'file' => new UploadedFile($files[0]['path'], $files[0]['name'], $files[0]['mime'])
+                ],
+                [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+//                    'x-dbp-signature' => $token,
+                    'HTTP_X_DBP_SIGNATURE' => $token,
+                ],
+                "HTTP_ACCEPT: application/ld+json\r\n"
+                    . "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
+                    . "file=" . base64_encode($files[0]['content'])
+                    . "&fileName={$files[0]['name']}&prefix=$prefix&bucketID=$bucketId"
+            );
+            $c = new CreateFileDataAction($blobService);
+            try {
+                $fileData = $c->__invoke($requestPost);
+            } catch (\Throwable $e) {
+                echo $e->getTraceAsString() . "\n";
+                $this->fail($e->getMessage());
+            }
+
+            $this->assertNotNull($fileData);
+            $this->assertEquals($prefix, $fileData->getPrefix(), 'File data prefix not correct.');
+            $this->assertObjectHasAttribute('identifier', $fileData, 'File data has no identifier.');
+            $this->assertTrue(uuid_is_valid($fileData->getIdentifier()), 'File data identifier is not a valid UUID.');
+            $this->assertEquals($files[0]['name'], $fileData->getFileName(), 'File name not correct.');
+            $files[0]['uuid'] = $fileData->getIdentifier();
+            $files[0]['created'] = $fileData->getDateCreated();
+            $files[0]['until'] = $fileData->getExistsUntil();
+            $this->assertEquals(
+                $files[0]['created']->format('c'),
+                date('c', (int)$creationTime),
+                'File creation time not correct.'
+            );
+
+            // =======================================================
+            // GET a file
+            // =======================================================
+            $options = [
+                'headers' => [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+                    'x-dbp-signature' => $token,
+                ],
+            ];
+
+            /** @noinspection PhpInternalEntityUsedInspection */
+            $client->getKernelBrowser()->followRedirects();
+
+            $response = $client->request('GET', $url, $options);
+
+            $this->assertEquals(200, $response->getStatusCode());
+
+            $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
+            $this->assertArrayHasKey('hydra:view', $data);
+            $this->assertArrayHasKey('hydra:member', $data);
+            $this->assertArrayHasKey(0, $data['hydra:member']);
+            $resultFile = $data['hydra:member'][0];
+            $this->assertEquals($prefix, $resultFile['prefix'], 'File data prefix not correct.');
+            $this->assertEquals($files[0]['name'], $resultFile['fileName'], 'File name not correct.');
+            $this->assertEquals($files[0]['size'], $resultFile['fileSize'], 'File size not correct.');
+            $this->assertEquals($files[0]['uuid'], $resultFile['identifier'], 'File identifier not correct.');
+            $this->assertEquals($notifyEmail, $resultFile['notifyEmail'], 'File data notify email not correct.');
+            $this->assertCount(1, $data['hydra:member'], 'More files than expected');
+//            dump($data);
+
+            // =======================================================
+            // POST another file
+            // =======================================================
+            $payload = [
+                'bucketID' => $bucketId,
+                'creationTime' => $creationTime,
+                'prefix' => $prefix,
+                'fileName' => $files[1]['name'],
+                'fileHash' => $files[1]['hash'],
+                'notifyEmail' => $notifyEmail,
+                'retentionDuration' => $files[1]['retention'],
+                'additionalMetadata' => '',
+            ];
 
             $token = DenyAccessUnlessCheckSignature::create($secret, $payload);
 
-//            echo "Signatur: $token\n";
+            $requestPost = Request::create($url, 'POST', [], [],
+                [
+                    'file' => new UploadedFile($files[1]['path'], $files[1]['name'], $files[1]['mime'])
+                ],
+                [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+//                    'x-dbp-signature' => $token,
+                    'HTTP_X_DBP_SIGNATURE' => $token,
+                ],
+                "HTTP_ACCEPT: application/ld+json\r\n"
+                    . "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
+                    . "file=" . base64_encode($files[1]['content'])
+                    . "&fileName={$files[1]['name']}&prefix=$prefix&bucketID=$bucketId"
+            );
+            $c = new CreateFileDataAction($blobService);
+            try {
+                $fileData = $c->__invoke($requestPost);
+            } catch (\Throwable $e) {
+                echo $e->getTraceAsString() . "\n";
+                $this->fail($e->getMessage());
+            }
+
+            $this->assertNotNull($fileData);
+            $this->assertEquals($prefix, $fileData->getPrefix(), 'File data prefix not correct.');
+            $this->assertObjectHasAttribute('identifier', $fileData, 'File data has no identifier.');
+            $this->assertTrue(uuid_is_valid($fileData->getIdentifier()), 'File data identifier is not a valid UUID.');
+            $this->assertEquals($files[1]['name'], $fileData->getFileName(), 'File name not correct.');
+            $files[1]['uuid'] = $fileData->getIdentifier();
+            $files[1]['created'] = $fileData->getDateCreated();
+            $files[1]['until'] = $fileData->getExistsUntil();
+            dump($fileData);
 
-            $header = [
-                'x-dbp-signature: '.$token,
+            // =======================================================
+            // GET all files
+            // =======================================================
+            $options = [
+                'headers' => [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+                    'x-dbp-signature' => $token,
+                ],
             ];
-            $ch = curl_init();
-            curl_setopt($ch, CURLOPT_URL, $uri);
-            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
-            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
-            curl_setopt($ch, CURLOPT_HEADER, 0);
-            curl_setopt($ch, CURLOPT_NOBODY, 0);
-            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
-            $result = curl_exec($ch);
-            $info = curl_getinfo($ch);
-            curl_close($ch);
-            $data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
-//            print_r($result);
-
-            $this->assertEquals(200, $info['http_code']);
+
+            /** @noinspection PhpInternalEntityUsedInspection */
+            $client->getKernelBrowser()->followRedirects();
+
+            $response = $client->request('GET', $url, $options);
+
+            $this->assertEquals(200, $response->getStatusCode());
+
+            $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
+            $this->assertArrayHasKey('hydra:view', $data);
+            $this->assertArrayHasKey('hydra:member', $data);
+            $this->assertCount(2, $data['hydra:member'], 'More files than expected');
+            foreach ($data['hydra:member'] as $resultFile) {
+                $found = false;
+                foreach ($files as $file) {
+                    if ($file['uuid'] === $resultFile['identifier']) {
+                        $found = true;
+                        $this->assertEquals($prefix, $resultFile['prefix'], 'File prefix not correct.');
+                        $this->assertEquals($file['name'], $resultFile['fileName'], 'File name not correct.');
+                        $this->assertEquals($file['size'], $resultFile['fileSize'], 'File size not correct.');
+                        $this->assertEquals(
+                            $file['created']->format('c'),
+                            date('c', (int)$creationTime),
+                            'File creation time not correct.'
+                        );
+                        $until = $file['created']->add(new \DateInterval($file['retention']));
+                        dump([$until->format('c'), $resultFile['existsUntil']]);
+                        $this->assertEquals(
+                            $until->format('c'),
+                            $resultFile['existsUntil'],
+                            'File retention time not correct.'
+                        );
+
+                        break;
+                    }
+                }
+                $this->assertTrue($found, 'Uploaded file not found.');
+            }
+//            dump($data['hydra:member']);
+//            dump($data);
+
+            // =======================================================
+            // DELETE all files
+            // =======================================================
+            $requestDelete = Request::create($url, 'DELETE', [], [], [],
+                [
+                    'HTTP_ACCEPT' => 'application/ld+json',
+                    'HTTP_X_DBP_SIGNATURE' => $token,
+                ],
+                "HTTP_ACCEPT: application/ld+json\r\n"
+                . "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
+            );
+            $d = new DeleteFileDatasByPrefix($blobService);
+            try {
+                $d->__invoke($requestDelete);
+            } catch (\Throwable $e) {
+                echo $e->getTraceAsString() . "\n";
+                $this->fail($e->getMessage());
+            }
+
+            $query = $this->entityManager->getConnection()->createQueryBuilder();
+            $files = $query->select('*')
+                ->from('blob_files')
+                ->where("prefix = '$prefix' AND bucket_id = '$bucketId'")
+                ->fetchAllAssociativeIndexed();
+            $this->assertEmpty($files, 'Files not deleted');
+
+            // =======================================================
+            // GET all files
+            // =======================================================
+            /** @var Response $response */
+            $response = $client->request('GET', $url, $options);
+
+            $this->assertEquals(200, $response->getStatusCode());
+
+            $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
             $this->assertArrayHasKey('hydra:view', $data);
-        } catch (Exception $e) {
+            $this->assertArrayHasKey('hydra:member', $data);
+            $this->assertCount(0, $data['hydra:member'], 'More files than expected');
+
+        } catch (\Throwable $e) {
+            echo $e->getTraceAsString() . "\n";
             $this->fail($e->getMessage());
         }
     }
diff --git a/tests/Kernel.php b/tests/Kernel.php
index 50044909f8cb2d991bcdad9940a2e9e33e9d0d23..8acf6f1bbc45241392da5c5f7eceb457e0a687d8 100644
--- a/tests/Kernel.php
+++ b/tests/Kernel.php
@@ -7,6 +7,8 @@ namespace Dbp\Relay\BlobBundle\Tests;
 use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
 use Dbp\Relay\BlobBundle\DbpRelayBlobBundle;
 use Dbp\Relay\CoreBundle\DbpRelayCoreBundle;
+use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
+use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
 use Nelmio\CorsBundle\NelmioCorsBundle;
 use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
 use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
@@ -29,6 +31,8 @@ class Kernel extends BaseKernel
         yield new TwigBundle();
         yield new NelmioCorsBundle();
         yield new MonologBundle();
+        yield new DoctrineBundle();
+        yield new DoctrineMigrationsBundle();
         yield new ApiPlatformBundle();
         yield new DbpRelayBlobBundle();
         yield new DbpRelayCoreBundle();
@@ -42,11 +46,49 @@ class Kernel extends BaseKernel
     protected function configureContainer(ContainerConfigurator $container, LoaderInterface $loader)
     {
         $container->import('@DbpRelayCoreBundle/Resources/config/services_test.yaml');
+        $container->import('@DbpRelayBlobBundle/Resources/config/services_test.yaml');
         $container->extension('framework', [
             'test' => true,
             'secret' => '',
         ]);
 
-        $container->extension('dbp_relay_blob', []);
+        $container->extension('dbp_relay_blob', [
+            'database_url' => 'sqlite:///var/dbp_relay_blob_test.db',
+            'buckets' => [
+                'test_bucket' => [
+                    'service' => 'Dbp\Relay\BlobBundle\Tests\DummyFileSystemService',
+                    'bucket_id' => '1234',
+                    'bucket_name' => 'Test bucket',
+                    'public_key' => '08d848fd868d83646778b87dd0695b10f59c78e23b286e9884504d1bb43cce93',
+                    'path' => 'testpath',
+                    'quota' => 500, // in MB
+                    'bucket_owner' => 'tamara.steiwnender@tugraz.at',
+                    'max_retention_duration' => 'P1Y',
+                    'link_expire_time' => 'P7D',
+                    'policies' => [
+                        'create' => true,
+                        'delete' => true,
+                        'open' => true,
+                        'download' => true,
+                        'rename' => true,
+                        'work' => true,
+                    ],
+                    'notify_quota' => [
+                        'dsn' => 'smtp:localhost',
+                        'from' => 'noreply@tugraz.at',
+                        'to' => 'tamara.steinwender@tugraz.at',
+                        'subject' => 'Blob notify quota',
+                        'html_template' => 'emails/notify-quota.html.twig',
+                    ],
+                    'reporting' => [
+                        'dsn' => 'smtp:localhost',
+                        'from' => 'noreply@tugraz.at',
+                        'to' => 'tamara.steinwender@tugraz.at',
+                        'subject' => 'Blob file deletion reporting',
+                        'html_template' => 'emails/reporting.html.twig',
+                    ],
+                ],
+            ],
+        ]);
     }
 }