diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index d5d0e0b147b46c30e985b78426eb7f86b470835a..0000000000000000000000000000000000000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-on:
-  - push
-  - pull_request
-
-name: Lint check
-jobs:
-  pre_job:
-    # continue-on-error: true # Uncomment once integration is finished
-    runs-on: ubuntu-latest
-    # Map a step output to a job output
-    outputs:
-      should_skip: ${{ steps.skip_check.outputs.should_skip }}
-    steps:
-      - id: skip_check
-        uses: fkirc/skip-duplicate-actions@master
-        with:
-          # All of these options are optional, so you can remove them if you are happy with the defaults
-          concurrent_skipping: 'same_content'
-          skip_after_successful_duplicate: 'true'
-          paths_ignore: '["**/README.md"]'
-
-  clippy_check:
-    needs: pre_job
-    if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v1
-      - run: rustup component add clippy
-      - uses: actions-rs/clippy-check@v1
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          args: --all-features -p rustus -- -W clippy::all -W clippy::pedantic -D warnings
-  fmt_check:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v1
-      - run: cargo fmt -- --check
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..300c4683fd57ea8bb0959bbd4fe80c06c0ddf438
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,97 @@
+on:
+  - pull_request
+
+name: Lint check
+jobs:
+  pre_job:
+    # continue-on-error: true # Uncomment once integration is finished
+    runs-on: ubuntu-latest
+    # Map a step output to a job output
+    outputs:
+      should_skip: ${{ steps.skip_check.outputs.should_skip }}
+    steps:
+      - id: skip_check
+        uses: fkirc/skip-duplicate-actions@master
+        with:
+          # All of these options are optional, so you can remove them if you are happy with the defaults
+          concurrent_skipping: 'same_content'
+          skip_after_successful_duplicate: 'true'
+          paths_ignore: '["**/README.md"]'
+
+  fmt_check:
+    needs: pre_job
+    if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Install stable toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+      - name: Adding component
+        run: rustup component add rustfmt
+      - name: Checking code format
+        run: cargo fmt -- --check
+
+  code_check:
+    needs: pre_job
+    if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Install stable toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+      - run: rustup component add clippy
+      - uses: actions-rs/clippy-check@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          args: --all-features -p rustus -- -W clippy::all -W clippy::pedantic -D warnings
+
+  tests:
+    needs: pre_job
+    if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
+    runs-on: ubuntu-latest
+    services:
+      redis:
+        image: redis:6.2-alpine3.15
+        ports:
+          - 6379/tcp
+      pg:
+        image: postgres:13.1
+        ports:
+          - 5432/tcp
+        env:
+          POSTGRES_PASSWORD: "rustus"
+          POSTGRES_USER: "rustus"
+          POSTGRES_DB: "rustus"
+      rabbit:
+        image: rabbitmq:3.8.27-alpine
+        ports:
+          - 5672/tcp
+        env:
+          RABBITMQ_DEFAULT_USER: "guest"
+          RABBITMQ_DEFAULT_PASS: "guest"
+          RABBITMQ_DEFAULT_VHOST: "/"
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: Install nightly toolchain
+        run: rustup toolchain install nightly --component llvm-tools-preview
+      - name: Install cargo-llvm-cov
+        uses: taiki-e/install-action@cargo-llvm-cov
+      - name: Generate code coverage
+        run: cargo llvm-cov --features=all,integration_tests --lcov --output-path lcov.info -- --test-threads 1
+        env:
+          TEST_REDIS_URL: redis://localhost:${{ job.services.redis.ports['6379'] }}/0
+          TEST_DB_URL: postgresql://rustus:rustus@localhost:${{ job.services.pg.ports['5432'] }}/rustus
+          TEST_AMQP_URL: amqp://guest:guest@localhost:${{ job.services.rabbit.ports['5672'] }}
+      - name: Coveralls
+        uses: coverallsapp/github-action@master
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          path-to-lcov: lcov.info
diff --git a/.gitignore b/.gitignore
index 155c48169534dff53ee2599d1b02916e9e22ef29..31bd9be85d7d6656702340c2683eb0deb978e48a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 .idea/
 /target
-data/
\ No newline at end of file
+data/
+tarpaulin-report.html
+lcov.info
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 14f6fe7f711f8c720f58bc116270836810134763..ffc5f67b4cb07df1375a4ab92a51bcd55dc73b3d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,17 +6,6 @@ repos:
   - repo: local
     hooks:
 
-      - id: build
-        types:
-          - rust
-        name: cargo build
-        language: system
-        entry: cargo
-        pass_filenames: false
-        args:
-          - build
-          - --features=all
-
       - id: fmt
         types:
           - rust
@@ -46,4 +35,15 @@ repos:
           - -W
           - clippy::pedantic
           - -D
-          - warnings
\ No newline at end of file
+          - warnings
+
+      - id: build
+        types:
+          - rust
+        name: cargo build
+        language: system
+        entry: cargo
+        pass_filenames: false
+        args:
+          - build
+          - --features=all
diff --git a/Cargo.lock b/Cargo.lock
index b1636ea7842b39583f171daf3314318a0067f10a..602fd875db043d77e955d7e59a82cfe2136d0c30 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,9 +21,9 @@ dependencies = [
 
 [[package]]
 name = "actix-files"
-version = "0.6.0-beta.13"
+version = "0.6.0-beta.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21889a4682d4886d81e3a32eef419519a62a7990bdfb8295110b94729884f8fd"
+checksum = "0b49f1b48724a52605ba40b67ede24f5a6cbc246817f9278d280d393a28e8b0e"
 dependencies = [
  "actix-http",
  "actix-service",
@@ -44,9 +44,9 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.0.0-beta.18"
+version = "3.0.0-rc.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b95871724d27ac9a23d6db3246b23e4e42cd44a23145e1c6b04b78fb5271da"
+checksum = "fb0185d65352deeea60d92231708068c04dc64f1ab307a1a307206a47d5a45d3"
 dependencies = [
  "actix-codec",
  "actix-rt",
@@ -55,7 +55,7 @@ dependencies = [
  "ahash",
  "base64",
  "bitflags",
- "brotli2",
+ "brotli",
  "bytes",
  "bytestring",
  "derive_more",
@@ -73,7 +73,7 @@ dependencies = [
  "mime",
  "percent-encoding",
  "pin-project-lite",
- "rand",
+ "rand 0.8.5",
  "sha-1 0.10.0",
  "smallvec",
  "zstd",
@@ -91,9 +91,9 @@ dependencies = [
 
 [[package]]
 name = "actix-router"
-version = "0.5.0-rc.2"
+version = "0.5.0-rc.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e0b59ad08167ffbb686ddb495846707231e96908b829b1fc218198ec581e2ad"
+checksum = "cb6506dbef336634ff35d994d58daa0a412ea23751f15f9b4dcac4d594b1ed1f"
 dependencies = [
  "bytestring",
  "firestorm",
@@ -155,9 +155,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.0.0-beta.20"
+version = "4.0.0-rc.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa8ba5081e9f8d0016cf34df516c699198158fd8c77990aa284115b055ead61b"
+checksum = "83e3c85bc4116b69913b03f16cff8cade1212508fcd321847d9cfe3d3e41f991"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -188,15 +188,15 @@ dependencies = [
  "serde_urlencoded",
  "smallvec",
  "socket2",
- "time 0.3.5",
+ "time 0.3.7",
  "url",
 ]
 
 [[package]]
 name = "actix-web-codegen"
-version = "0.5.0-rc.1"
+version = "0.5.0-rc.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98a793e4a7bd059e06e1bc1bd9943b57a47f806de3599d2437441682292c333e"
+checksum = "4d0976042e6ddc82c7d0dedd64d39959bc26d9bba098b2f6c32a73fbef784eaf"
 dependencies = [
  "actix-router",
  "proc-macro2",
@@ -230,6 +230,21 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
 [[package]]
 name = "amq-protocol"
 version = "6.1.0"
@@ -376,23 +391,6 @@ dependencies = [
  "event-listener",
 ]
 
-[[package]]
-name = "async-process"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
-dependencies = [
- "async-io",
- "blocking",
- "cfg-if",
- "event-listener",
- "futures-lite",
- "libc",
- "once_cell",
- "signal-hook",
- "winapi",
-]
-
 [[package]]
 name = "async-std"
 version = "1.10.0"
@@ -422,9 +420,9 @@ dependencies = [
 
 [[package]]
 name = "async-task"
-version = "4.0.3"
+version = "4.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8"
 
 [[package]]
 name = "async-trait"
@@ -465,15 +463,18 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
+dependencies = [
+ "autocfg 1.1.0",
+]
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "base-x"
@@ -543,9 +544,9 @@ dependencies = [
 
 [[package]]
 name = "block-buffer"
-version = "0.10.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -574,30 +575,42 @@ dependencies = [
 ]
 
 [[package]]
-name = "brotli-sys"
-version = "0.3.2"
+name = "brotli"
+version = "3.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
+checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39"
 dependencies = [
- "cc",
- "libc",
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
 ]
 
 [[package]]
-name = "brotli2"
-version = "0.3.2"
+name = "brotli-decompressor"
+version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
 dependencies = [
- "brotli-sys",
- "libc",
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
 ]
 
 [[package]]
 name = "bumpalo"
-version = "3.8.0"
+version = "3.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
+checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
 
 [[package]]
 name = "byte-tools"
@@ -689,9 +702,9 @@ dependencies = [
 
 [[package]]
 name = "combine"
-version = "4.6.2"
+version = "4.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5"
+checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
 dependencies = [
  "bytes",
  "futures-core",
@@ -735,7 +748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
 dependencies = [
  "percent-encoding",
- "time 0.3.5",
+ "time 0.3.7",
  "version_check",
 ]
 
@@ -747,9 +760,9 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
 
 [[package]]
 name = "core-foundation"
-version = "0.9.2"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -787,18 +800,18 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
 
 [[package]]
 name = "crc32fast"
-version = "1.3.0"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -806,9 +819,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-queue"
-version = "0.3.2"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9"
+checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -816,9 +829,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.5"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
 dependencies = [
  "cfg-if",
  "lazy_static",
@@ -831,15 +844,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
 dependencies = [
  "generic-array 0.14.5",
- "rand_core",
+ "rand_core 0.6.3",
  "subtle",
 ]
 
 [[package]]
 name = "crypto-common"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -917,13 +930,12 @@ dependencies = [
 
 [[package]]
 name = "digest"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
 dependencies = [
- "block-buffer 0.10.0",
+ "block-buffer 0.10.2",
  "crypto-common",
- "generic-array 0.14.5",
 ]
 
 [[package]]
@@ -984,9 +996,9 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "2.5.1"
+version = "2.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
 
 [[package]]
 name = "fake-simd"
@@ -996,9 +1008,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 
 [[package]]
 name = "fastrand"
-version = "1.6.0"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2"
+checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
 dependencies = [
  "instant",
 ]
@@ -1033,9 +1045,9 @@ dependencies = [
 
 [[package]]
 name = "flume"
-version = "0.10.9"
+version = "0.10.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24c3fd473b3a903a62609e413ed7538f99e10b665ecb502b5e481a95283f8ab4"
+checksum = "0b279436a715a9de95dcd26b151db590a71961cc06e54918b24fe0dd5b7d3fc4"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1074,11 +1086,17 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
 [[package]]
 name = "futures"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4"
+checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1091,9 +1109,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1101,15 +1119,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
+checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1124,14 +1142,14 @@ checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e"
 dependencies = [
  "futures-core",
  "lock_api",
- "parking_lot",
+ "parking_lot 0.11.2",
 ]
 
 [[package]]
 name = "futures-io"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
+checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
 
 [[package]]
 name = "futures-lite"
@@ -1150,9 +1168,9 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
+checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1161,15 +1179,15 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508"
+checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
 
 [[package]]
 name = "futures-task"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
+checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
 
 [[package]]
 name = "futures-timer"
@@ -1179,9 +1197,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
 
 [[package]]
 name = "futures-util"
-version = "0.3.19"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
+checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1216,9 +1234,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1227,22 +1245,21 @@ dependencies = [
 
 [[package]]
 name = "gloo-timers"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f16c88aa13d2656ef20d1c042086b8767bbe2bdb62526894275a1b062161b2e"
+checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
 dependencies = [
  "futures-channel",
  "futures-core",
  "js-sys",
  "wasm-bindgen",
- "web-sys",
 ]
 
 [[package]]
 name = "h2"
-version = "0.3.9"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd"
+checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
 dependencies = [
  "bytes",
  "fnv",
@@ -1354,9 +1371,9 @@ checksum = "eee9694f83d9b7c09682fdb32213682939507884e5bcf227be9aff5d644b90dc"
 
 [[package]]
 name = "httparse"
-version = "1.5.1"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
 
 [[package]]
 name = "httpdate"
@@ -1364,11 +1381,33 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
 
+[[package]]
+name = "httptest"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f25cfb6def593d43fae1ead24861f217e93bc70768a45cc149a69b5f049df4"
+dependencies = [
+ "bstr",
+ "bytes",
+ "crossbeam-channel",
+ "form_urlencoded",
+ "futures",
+ "http",
+ "hyper",
+ "log",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+]
+
 [[package]]
 name = "hyper"
-version = "0.14.16"
+version = "0.14.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
+checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1379,7 +1418,7 @@ dependencies = [
  "http-body",
  "httparse",
  "httpdate",
- "itoa 0.4.8",
+ "itoa 1.0.1",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -1414,11 +1453,11 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "hashbrown",
 ]
 
@@ -1478,9 +1517,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.55"
+version = "0.3.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1502,9 +1541,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
 
 [[package]]
 name = "lapin"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef0a8c145a248b1536cfa06890d480cda7982add59f77973ac7b03db47f349b5"
+checksum = "36c0eacc7b8880c2e73ab70e47c9f099ad81af6debde9353fdb11045c0ce716e"
 dependencies = [
  "amq-protocol",
  "async-task",
@@ -1512,7 +1551,7 @@ dependencies = [
  "futures-core",
  "log",
  "mio 0.7.14",
- "parking_lot",
+ "parking_lot 0.12.0",
  "pinky-swear",
  "serde",
 ]
@@ -1528,15 +1567,15 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.112"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
 
 [[package]]
 name = "libm"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
+checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
 
 [[package]]
 name = "libsqlite3-sys"
@@ -1569,9 +1608,9 @@ checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb"
 
 [[package]]
 name = "lock_api"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
 dependencies = [
  "scopeguard",
 ]
@@ -1644,7 +1683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
 dependencies = [
  "adler",
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
 ]
 
 [[package]]
@@ -1748,9 +1787,9 @@ dependencies = [
 
 [[package]]
 name = "ntapi"
-version = "0.3.6"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
 dependencies = [
  "winapi",
 ]
@@ -1761,7 +1800,7 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "num-integer",
  "num-traits",
 ]
@@ -1772,14 +1811,14 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480"
 dependencies = [
- "autocfg 0.1.7",
+ "autocfg 0.1.8",
  "byteorder",
  "lazy_static",
  "libm",
  "num-integer",
  "num-iter",
  "num-traits",
- "rand",
+ "rand 0.8.5",
  "smallvec",
  "zeroize",
 ]
@@ -1790,7 +1829,7 @@ version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "num-traits",
 ]
 
@@ -1800,7 +1839,7 @@ version = "0.1.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "num-integer",
  "num-traits",
 ]
@@ -1811,7 +1850,7 @@ version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "libm",
 ]
 
@@ -1825,6 +1864,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "num_threads"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.9.0"
@@ -1859,9 +1907,9 @@ dependencies = [
 
 [[package]]
 name = "openssl-probe"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-src"
@@ -1878,7 +1926,7 @@ version = "0.9.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg 1.1.0",
  "cc",
  "libc",
  "openssl-src",
@@ -1900,7 +1948,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
 dependencies = [
  "instant",
  "lock_api",
- "parking_lot_core",
+ "parking_lot_core 0.8.5",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.1",
 ]
 
 [[package]]
@@ -1917,6 +1975,19 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "parking_lot_core"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
 [[package]]
 name = "paste"
 version = "1.0.6"
@@ -2026,12 +2097,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pinky-swear"
-version = "4.4.0"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bf8cda6f8e1500338634e4e3ce90ac59eb7929a1e088b6946c742be1cc44dc1"
+checksum = "c5ade3d5e4fa85586b4795e097180fd48447d39fb56e351bd889f1c9664291a8"
 dependencies = [
  "doc-comment",
- "parking_lot",
+ "parking_lot 0.12.0",
  "tracing",
 ]
 
@@ -2137,23 +2208,35 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.14"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
+checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "rand"
-version = "0.8.4"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
  "rand_chacha",
- "rand_core",
- "rand_hc",
+ "rand_core 0.6.3",
 ]
 
 [[package]]
@@ -2163,32 +2246,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.6.3",
 ]
 
 [[package]]
 name = "rand_core"
-version = "0.6.3"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
 dependencies = [
- "getrandom",
+ "rand_core 0.4.2",
 ]
 
 [[package]]
-name = "rand_hc"
-version = "0.3.1"
+name = "rand_core"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
 dependencies = [
- "rand_core",
+ "getrandom",
 ]
 
 [[package]]
 name = "rbatis"
-version = "3.0.30"
+version = "3.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8c968bd15606738295d8b71dca35cfef3bb1a124af89dab45a18ed83b19975"
+checksum = "c7d3ebffcbf93beb72bf3854c590b027e8590069d5cb0d9e614ed5adc9e349a5"
 dependencies = [
  "async-trait",
  "chrono",
@@ -2197,7 +2286,7 @@ dependencies = [
  "hex",
  "log",
  "once_cell",
- "rand",
+ "rand 0.8.5",
  "rbatis-core",
  "rbatis-macro-driver",
  "rbatis_sql",
@@ -2208,9 +2297,9 @@ dependencies = [
 
 [[package]]
 name = "rbatis-core"
-version = "3.0.20"
+version = "3.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cd0da1efe628da94506a15dc8e628a6c25b6d8de77fb2d51149ac9c7893153d"
+checksum = "ac21224137c4c885210ab3fd659ecd440806de10ea49ce43461d83acf3d54c1a"
 dependencies = [
  "base64",
  "bigdecimal",
@@ -2284,13 +2373,22 @@ dependencies = [
  "hex",
  "indexmap",
  "lazy_static",
- "rand",
+ "rand 0.8.5",
  "serde",
  "serde_bytes",
  "serde_json",
  "uuid 0.8.2",
 ]
 
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "redis"
 version = "0.19.0"
@@ -2341,6 +2439,12 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.25"
@@ -2358,15 +2462,16 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258"
+checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
 dependencies = [
  "base64",
  "bytes",
  "encoding_rs",
  "futures-core",
  "futures-util",
+ "h2",
  "http",
  "http-body",
  "hyper",
@@ -2432,16 +2537,16 @@ dependencies = [
  "num-traits",
  "pkcs1",
  "pkcs8",
- "rand",
+ "rand 0.8.5",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
 name = "rust_decimal"
-version = "1.19.0"
+version = "1.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c2d4912d369fb95a351c221475657970678d344d70c1a788223f6e74d1e3732"
+checksum = "4214023b1223d02a4aad9f0bb9828317634a56530870a2eaf7200a99c0c10f68"
 dependencies = [
  "arrayvec",
  "num-traits",
@@ -2463,7 +2568,7 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 dependencies = [
- "semver 1.0.4",
+ "semver 1.0.5",
 ]
 
 [[package]]
@@ -2481,11 +2586,11 @@ dependencies = [
 
 [[package]]
 name = "rustus"
-version = "0.4.0"
+version = "0.4.1"
 dependencies = [
  "actix-files",
+ "actix-rt",
  "actix-web",
- "async-process",
  "async-std",
  "async-trait",
  "base64",
@@ -2493,6 +2598,7 @@ dependencies = [
  "derive_more",
  "fern",
  "futures",
+ "httptest",
  "lapin",
  "lazy_static",
  "log",
@@ -2507,6 +2613,7 @@ dependencies = [
  "strfmt",
  "structopt",
  "strum",
+ "tempdir",
  "thiserror",
  "tokio",
  "tokio-amqp",
@@ -2554,9 +2661,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "2.4.2"
+version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
+checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
 dependencies = [
  "bitflags",
  "core-foundation",
@@ -2567,9 +2674,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.4.2"
+version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -2586,9 +2693,9 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.4"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
+checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7"
 
 [[package]]
 name = "semver-parser"
@@ -2598,9 +2705,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
-version = "1.0.133"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
 dependencies = [
  "serde_derive",
 ]
@@ -2616,9 +2723,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.133"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
+checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2627,9 +2734,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.75"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79"
+checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
 dependencies = [
  "indexmap",
  "itoa 1.0.1",
@@ -2639,12 +2746,12 @@ dependencies = [
 
 [[package]]
 name = "serde_urlencoded"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
 dependencies = [
  "form_urlencoded",
- "itoa 0.4.8",
+ "itoa 1.0.1",
  "ryu",
  "serde",
 ]
@@ -2682,20 +2789,29 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.1",
+ "digest 0.10.2",
 ]
 
 [[package]]
 name = "sha1"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
+dependencies = [
+ "sha1_smol",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
 
 [[package]]
 name = "sha2"
-version = "0.9.8"
+version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
 dependencies = [
  "block-buffer 0.9.0",
  "cfg-if",
@@ -2704,16 +2820,6 @@ dependencies = [
  "opaque-debug 0.3.0",
 ]
 
-[[package]]
-name = "signal-hook"
-version = "0.3.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
-
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
@@ -2731,15 +2837,15 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
 
 [[package]]
 name = "smallvec"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
 
 [[package]]
 name = "socket2"
-version = "0.4.2"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
 dependencies = [
  "libc",
  "winapi",
@@ -2822,9 +2928,9 @@ dependencies = [
  "memchr",
  "num-bigint",
  "once_cell",
- "parking_lot",
+ "parking_lot 0.11.2",
  "percent-encoding",
- "rand",
+ "rand 0.8.5",
  "regex",
  "rsa",
  "rust_decimal",
@@ -2941,9 +3047,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 
 [[package]]
 name = "structopt"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
+checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
 dependencies = [
  "clap",
  "lazy_static",
@@ -2993,9 +3099,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
 name = "syn"
-version = "1.0.84"
+version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
+checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3016,9 +3122,9 @@ dependencies = [
 
 [[package]]
 name = "tcp-stream"
-version = "0.20.9"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a77f06a7c6a1ecff1bbab0743a8beca305ca1ebcbe5d1bc32390e94117859af"
+checksum = "c839b9cf24db4225fa445589e014e6ecc4c42ba6ecf5db3e9fe38fbe8ea2377a"
 dependencies = [
  "cfg-if",
  "mio 0.7.14",
@@ -3026,15 +3132,25 @@ dependencies = [
  "pem",
 ]
 
+[[package]]
+name = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+dependencies = [
+ "rand 0.4.6",
+ "remove_dir_all",
+]
+
 [[package]]
 name = "tempfile"
-version = "3.2.0"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
 dependencies = [
  "cfg-if",
+ "fastrand",
  "libc",
- "rand",
  "redox_syscall",
  "remove_dir_all",
  "winapi",
@@ -3098,12 +3214,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.5"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
+checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
 dependencies = [
- "itoa 0.4.8",
+ "itoa 1.0.1",
  "libc",
+ "num_threads",
  "time-macros 0.2.3",
 ]
 
@@ -3153,9 +3270,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.15.0"
+version = "1.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
+checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
 dependencies = [
  "bytes",
  "libc",
@@ -3163,7 +3280,7 @@ dependencies = [
  "mio 0.7.14",
  "num_cpus",
  "once_cell",
- "parking_lot",
+ "parking_lot 0.11.2",
  "pin-project-lite",
  "signal-hook-registry",
  "winapi",
@@ -3171,12 +3288,12 @@ dependencies = [
 
 [[package]]
 name = "tokio-amqp"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2913044c3ce17203305876b2fbd615dd98e13a20b9a24c061bbb7abab88f19cb"
+checksum = "85ac53b83d524e047f3d20ce43dc5851d932dcb187578a5b37f754ca61b909d1"
 dependencies = [
  "lapin",
- "parking_lot",
+ "parking_lot 0.12.0",
  "tokio",
 ]
 
@@ -3234,9 +3351,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
 
 [[package]]
 name = "tracing"
-version = "0.1.29"
+version = "0.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
 dependencies = [
  "cfg-if",
  "pin-project-lite",
@@ -3245,9 +3362,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.21"
+version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
 dependencies = [
  "lazy_static",
 ]
@@ -3296,9 +3413,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.8.0"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
 
 [[package]]
 name = "unicode-width"
@@ -3407,9 +3524,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.78"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -3417,9 +3534,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.78"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -3432,9 +3549,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.28"
+version = "0.4.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
+checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3444,9 +3561,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.78"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3454,9 +3571,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.78"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3467,15 +3584,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.78"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
 
 [[package]]
 name = "web-sys"
-version = "0.3.55"
+version = "0.3.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3541,6 +3658,49 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-sys"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
+
 [[package]]
 name = "winreg"
 version = "0.7.0"
@@ -3567,9 +3727,9 @@ dependencies = [
 
 [[package]]
 name = "zeroize_derive"
-version = "1.2.2"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73"
+checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3579,18 +3739,18 @@ dependencies = [
 
 [[package]]
 name = "zstd"
-version = "0.9.1+zstd.1.5.1"
+version = "0.10.0+zstd.1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "538b8347df9257b7fbce37677ef7535c00a3c7bf1f81023cc328ed7fe4b41de8"
+checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "4.1.2+zstd.1.5.1"
+version = "4.1.4+zstd.1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb4cfe2f6e6d35c5d27ecd9d256c4b6f7933c4895654917460ec56c29336cc1"
+checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee"
 dependencies = [
  "libc",
  "zstd-sys",
@@ -3598,9 +3758,9 @@ dependencies = [
 
 [[package]]
 name = "zstd-sys"
-version = "1.6.2+zstd.1.5.1"
+version = "1.6.3+zstd.1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f"
+checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8"
 dependencies = [
  "cc",
  "libc",
diff --git a/Cargo.toml b/Cargo.toml
index fcbb70db8a7f69011745f059c874044d7254d681..a083d0debca07ad7770de7127d361a8a2172702c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustus"
-version = "0.4.0"
+version = "0.4.1"
 edition = "2021"
 description = "TUS protocol implementation written in Rust."
 
@@ -12,12 +12,15 @@ async-trait = "0.1.52"
 base64 = "^0.13.0"
 lazy_static = "1.4.0"
 log = "^0.4.14"
-serde = "1"
 serde_json = "1"
 strfmt = "^0.1.6"
 thiserror = "^1.0"
 url = "2.2.2"
 
+[dependencies.serde]
+version = "1"
+features = ["derive"]
+
 [dependencies.openssl]
 version = "0.10.38"
 features = ["vendored"]
@@ -71,10 +74,6 @@ version = "^3.0"
 optional = true
 version = "2.0"
 
-[dependencies.async-process]
-version = "1.3.0"
-optional = true
-
 [dependencies.reqwest]
 features = ["json"]
 optional = true
@@ -88,7 +87,7 @@ features = ["derive"]
 version = "0.23"
 
 [dependencies.tokio]
-features = ["time"]
+features = ["time", "process"]
 version = "1.4.0"
 
 [dependencies.tokio-amqp]
@@ -100,13 +99,23 @@ features = ["v4"]
 version = "^1.0.0-alpha.1"
 
 [features]
-all = ["redis_info_storage", "db_info_storage", "http_notifier", "amqp_notifier", "file_notifiers"]
+all = ["redis_info_storage", "db_info_storage", "http_notifier", "amqp_notifier"]
 amqp_notifier = ["lapin", "tokio-amqp", "mobc-lapin"]
 db_info_storage = ["rbatis", "rbson"]
 default = []
 http_notifier = ["reqwest"]
 redis_info_storage = ["mobc-redis"]
-file_notifiers = ["async-process"]
+
+### For testing
+test_redis = []
+test_db = []
+test_rmq = []
+integration_tests = ["test_redis", "test_db", "test_rmq"]
+
+[dev-dependencies]
+tempdir = "0.3.7"
+actix-rt = "2.6.0"
+httptest = "0.15.4"
 
 [profile]
 [profile.release]
diff --git a/README.md b/README.md
index 7cb97ee7f5db3f4e896bf0e0e0535b30325e103c..ea1bb822d14015f290251c6204c4cdf1accadfdf 100644
--- a/README.md
+++ b/README.md
@@ -226,7 +226,8 @@ Example of a single file hook:
 
 # Hook name would be "pre-create", "post-create" and so on.
 HOOK_NAME="$1"
-MEME="$(cat /dev/stdin | jq ".upload .metadata .meme" | xargs)"
+HOOK_INFO="$2"
+MEME="$(echo "$HOOK_INFO" | jq ".upload .metadata .meme" | xargs)"
 
 # Here we check if name in metadata is equal to pepe.
 if [[ $MEME = "pepe" ]]; then
@@ -271,7 +272,10 @@ rustus --hooks-dir "hooks"
 In this case rustus will append a hook name to the directory you pointed at and call it as
 an executable.
 
-Information about hook can be found in stdin.
+Information about hook is passed as a first parameter, as if you call script by running:
+```bash
+./hooks/pre-create '{"id": "someid", ...}'
+```
 
 ### Http Hooks
 
diff --git a/src/config.rs b/src/config.rs
index 2adf14dadb70f49dcb5001534f8445dc1c0e77a0..f772c6fab56dad52945036b7e7831a8752f93430 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,6 @@
-use std::collections::HashMap;
-use std::env;
+use std::ffi::OsString;
 use std::path::PathBuf;
 
-use chrono::{Datelike, Timelike};
-use lazy_static::lazy_static;
-use log::error;
 use structopt::StructOpt;
 
 use crate::info_storages::AvailableInfoStores;
@@ -13,17 +9,6 @@ use crate::protocol::extensions::Extensions;
 
 use crate::storages::AvailableStores;
 
-lazy_static! {
-    /// Freezing ENVS on startup.
-    static ref ENV_MAP: HashMap<String, String> = {
-        let mut m = HashMap::new();
-        for (key, value) in env::vars() {
-            m.insert(format!("env[{}]", key), value);
-        }
-        m
-    };
-}
-
 #[derive(StructOpt, Debug, Clone)]
 pub struct StorageOptions {
     /// Rustus storage type.
@@ -124,11 +109,17 @@ pub struct NotificationsOptions {
     #[structopt(long, env = "RUSTUS_HOOKS_AMQP_EXCHANGE", default_value = "rustus")]
     pub hooks_amqp_exchange: String,
 
-    #[cfg(feature = "file_notifiers")]
+    #[cfg(feature = "amqp_notifier")]
+    #[structopt(
+        long,
+        env = "RUSTUS_HOOKS_AMQP_QUEUES_PREFIX",
+        default_value = "rustus"
+    )]
+    pub hooks_amqp_queues_prefix: String,
+
     #[structopt(long, env = "RUSTUS_HOOKS_DIR")]
     pub hooks_dir: Option<PathBuf>,
 
-    #[cfg(feature = "file_notifiers")]
     #[structopt(long, env = "RUSTUS_HOOKS_FILE")]
     pub hooks_file: Option<String>,
 }
@@ -200,6 +191,7 @@ pub struct RustusConf {
     pub notification_opts: NotificationsOptions,
 }
 
+#[cfg_attr(coverage, no_coverage)]
 impl RustusConf {
     /// Function to parse CLI parametes.
     ///
@@ -209,6 +201,14 @@ impl RustusConf {
         <RustusConf as StructOpt>::from_args()
     }
 
+    pub fn from_iter<I>(iter: I) -> RustusConf
+    where
+        I: IntoIterator,
+        I::Item: Into<OsString> + Clone,
+    {
+        <RustusConf as StructOpt>::from_iter(iter)
+    }
+
     /// Base API url.
     pub fn base_url(&self) -> String {
         format!(
@@ -219,14 +219,16 @@ impl RustusConf {
         )
     }
 
-    /// URL for a particular file.
-    pub fn file_url(&self) -> String {
+    /// Helper for generating URI for test files.
+    #[cfg(test)]
+    pub fn file_url(&self, file_id: &str) -> String {
         let base_url = self.base_url();
         format!(
-            "{}/{{file_id}}",
+            "{}/{}",
             base_url
                 .strip_suffix('/')
-                .unwrap_or_else(|| base_url.as_str())
+                .unwrap_or_else(|| base_url.as_str()),
+            file_id
         )
     }
 
@@ -235,21 +237,6 @@ impl RustusConf {
         self.notification_opts.hooks.contains(&hook)
     }
 
-    /// Generate directory name with user template.
-    pub fn dir_struct(&self) -> String {
-        let now = chrono::Utc::now();
-        let mut vars: HashMap<String, String> = ENV_MAP.clone();
-        vars.insert("day".into(), now.day().to_string());
-        vars.insert("month".into(), now.month().to_string());
-        vars.insert("year".into(), now.year().to_string());
-        vars.insert("hour".into(), now.hour().to_string());
-        vars.insert("minute".into(), now.minute().to_string());
-        strfmt::strfmt(self.storage_opts.dir_structure.as_str(), &vars).unwrap_or_else(|err| {
-            error!("{}", err);
-            "".into()
-        })
-    }
-
     /// List of extensions.
     ///
     /// This function will parse list of extensions from CLI
diff --git a/src/errors.rs b/src/errors.rs
index 19be4062cb8e175be17b2094c0b2c237626bed2c..942fe39fdef95c98a0623950f8ed02a20dc13258 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -61,6 +61,7 @@ pub enum RustusError {
 }
 
 /// This conversion allows us to use `RustusError` in the `main` function.
+#[cfg_attr(coverage, no_coverage)]
 impl From<RustusError> for Error {
     fn from(err: RustusError) -> Self {
         Error::new(ErrorKind::Other, err)
@@ -68,6 +69,7 @@ impl From<RustusError> for Error {
 }
 
 /// Trait to convert errors to http-responses.
+#[cfg_attr(coverage, no_coverage)]
 impl ResponseError for RustusError {
     fn error_response(&self) -> HttpResponse {
         error!("{}", self);
diff --git a/src/info_storages/db_info_storage.rs b/src/info_storages/db_info_storage.rs
index e96f053bc715eac8bf597fbb317c00f60004cb20..164f2bcf82a9bea0a9d2ed645cdd8429c7a8316e 100644
--- a/src/info_storages/db_info_storage.rs
+++ b/src/info_storages/db_info_storage.rs
@@ -9,7 +9,6 @@ use rbatis::rbatis::Rbatis;
 
 use crate::errors::{RustusError, RustusResult};
 use crate::info_storages::{FileInfo, InfoStorage};
-use crate::RustusConf;
 
 #[crud_table]
 struct DbModel {
@@ -33,15 +32,11 @@ pub struct DBInfoStorage {
 }
 
 impl DBInfoStorage {
-    pub async fn new(app_conf: RustusConf) -> RustusResult<Self> {
+    pub async fn new(dsn: &str) -> RustusResult<Self> {
         let db = Rbatis::new();
         let mut opts = DBPoolOptions::new();
         opts.connect_timeout = Duration::new(2, 0);
-        db.link_opt(
-            app_conf.info_storage_opts.info_db_dsn.unwrap().as_str(),
-            opts,
-        )
-        .await?;
+        db.link_opt(dsn, opts).await?;
         Ok(Self { db })
     }
 }
@@ -84,3 +79,68 @@ impl InfoStorage for DBInfoStorage {
         Ok(())
     }
 }
+
+#[cfg(feature = "test_db")]
+#[cfg(test)]
+mod tests {
+    use super::{DBInfoStorage, DbModel};
+    use crate::info_storages::FileInfo;
+    use crate::InfoStorage;
+    use rbatis::crud::CRUD;
+
+    async fn get_info_storage() -> DBInfoStorage {
+        let db_url = std::env::var("TEST_DB_URL").unwrap();
+        let mut storage = DBInfoStorage::new(db_url.as_str()).await.unwrap();
+        storage.prepare().await.unwrap();
+        storage
+    }
+
+    #[actix_rt::test]
+    async fn success() {
+        let info_storage = get_info_storage().await;
+        let file_info = FileInfo::new_test();
+        info_storage.set_info(&file_info, true).await.unwrap();
+        let info = info_storage
+            .db
+            .fetch_by_column::<Option<DbModel>, &str>("id", file_info.id.as_str())
+            .await
+            .unwrap();
+        assert!(info.is_some());
+        let info = info_storage.get_info(file_info.id.as_str()).await.unwrap();
+        assert_eq!(file_info.id, info.id);
+        assert_eq!(file_info.storage, info.storage);
+        assert_eq!(file_info.length, info.length);
+    }
+
+    #[actix_rt::test]
+    async fn success_deletion() {
+        let info_storage = get_info_storage().await;
+        let file_info = FileInfo::new_test();
+        info_storage.set_info(&file_info, true).await.unwrap();
+        info_storage
+            .remove_info(file_info.id.as_str())
+            .await
+            .unwrap();
+        let info = info_storage
+            .db
+            .fetch_by_column::<Option<DbModel>, &str>("id", file_info.id.as_str())
+            .await
+            .unwrap();
+        assert!(info.is_none());
+    }
+
+    #[actix_rt::test]
+    async fn deletion_not_found() {
+        let info_storage = get_info_storage().await;
+        let res = info_storage.remove_info("unknown").await;
+        // We don't care if it doesn't exist.
+        assert!(res.is_ok());
+    }
+
+    #[actix_rt::test]
+    async fn getting_not_found() {
+        let info_storage = get_info_storage().await;
+        let res = info_storage.get_info("unknown").await;
+        assert!(res.is_err());
+    }
+}
diff --git a/src/info_storages/file_info_storage.rs b/src/info_storages/file_info_storage.rs
index 338ac6800406dfe8166fab78f8028eb3fe8105eb..65cd3cac2dab9699b52f982cfaad99fb33de96c4 100644
--- a/src/info_storages/file_info_storage.rs
+++ b/src/info_storages/file_info_storage.rs
@@ -7,31 +7,27 @@ use log::error;
 
 use crate::errors::{RustusError, RustusResult};
 use crate::info_storages::{FileInfo, InfoStorage};
-use crate::RustusConf;
 
 pub struct FileInfoStorage {
-    app_conf: RustusConf,
+    info_dir: PathBuf,
 }
 
 impl FileInfoStorage {
-    pub fn new(app_conf: RustusConf) -> Self {
-        Self { app_conf }
+    pub fn new(info_dir: PathBuf) -> Self {
+        Self { info_dir }
     }
 
     pub fn info_file_path(&self, file_id: &str) -> PathBuf {
-        self.app_conf
-            .info_storage_opts
-            .info_dir
-            .join(format!("{}.info", file_id))
+        self.info_dir.join(format!("{}.info", file_id))
     }
 }
 
 #[async_trait]
 impl InfoStorage for FileInfoStorage {
     async fn prepare(&mut self) -> RustusResult<()> {
-        if !self.app_conf.info_storage_opts.info_dir.exists() {
+        if !self.info_dir.exists() {
             DirBuilder::new()
-                .create(self.app_conf.info_storage_opts.info_dir.as_path())
+                .create(self.info_dir.as_path())
                 .await
                 .map_err(|err| RustusError::UnableToPrepareInfoStorage(err.to_string()))?;
         }
@@ -42,6 +38,7 @@ impl InfoStorage for FileInfoStorage {
         let mut file = OpenOptions::new()
             .write(true)
             .create(create)
+            .truncate(true)
             .open(self.info_file_path(file_info.id.as_str()).as_path())
             .await
             .map_err(|err| {
@@ -86,3 +83,78 @@ impl InfoStorage for FileInfoStorage {
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::FileInfoStorage;
+    use crate::info_storages::FileInfo;
+    use crate::InfoStorage;
+    use std::collections::HashMap;
+    use std::fs::File;
+    use std::io::{Read, Write};
+
+    #[actix_rt::test]
+    async fn preparation() {
+        let dir = tempdir::TempDir::new("file_info").unwrap();
+        let target_path = dir.into_path().join("not_exist");
+        let mut storage = FileInfoStorage::new(target_path.clone());
+        assert!(!target_path.exists());
+        storage.prepare().await.unwrap();
+        assert!(target_path.exists());
+    }
+
+    #[actix_rt::test]
+    async fn setting_info() {
+        let dir = tempdir::TempDir::new("file_info").unwrap();
+        let storage = FileInfoStorage::new(dir.into_path());
+        let file_info = FileInfo::new(
+            uuid::Uuid::new_v4().to_string().as_str(),
+            Some(10),
+            Some("random_path".into()),
+            "random_storage".into(),
+            None,
+        );
+        storage.set_info(&file_info, true).await.unwrap();
+        let info_path = storage.info_file_path(file_info.id.as_str());
+        let mut buffer = String::new();
+        File::open(info_path)
+            .unwrap()
+            .read_to_string(&mut buffer)
+            .unwrap();
+        assert!(buffer.len() > 0);
+    }
+
+    #[actix_rt::test]
+    async fn set_get_info() {
+        let dir = tempdir::TempDir::new("file_info").unwrap();
+        let storage = FileInfoStorage::new(dir.into_path());
+        let file_info = FileInfo::new(
+            uuid::Uuid::new_v4().to_string().as_str(),
+            Some(10),
+            Some("random_path".into()),
+            "random_storage".into(),
+            {
+                let mut a = HashMap::new();
+                a.insert("test".into(), "pest".into());
+                Some(a)
+            },
+        );
+        storage.set_info(&file_info, true).await.unwrap();
+        let read_info = storage.get_info(file_info.id.as_str()).await.unwrap();
+        assert_eq!(read_info.id, read_info.id);
+        assert_eq!(read_info.length, read_info.length);
+        assert_eq!(read_info.path, read_info.path);
+        assert_eq!(read_info.metadata, read_info.metadata);
+    }
+
+    #[actix_rt::test]
+    async fn get_broken_info() {
+        let dir = tempdir::TempDir::new("file_info").unwrap();
+        let storage = FileInfoStorage::new(dir.into_path());
+        let file_id = "random_file";
+        let mut file = File::create(storage.info_file_path(file_id)).unwrap();
+        file.write_all("{not a json}".as_bytes()).unwrap();
+        let read_info = storage.get_info(file_id).await;
+        assert!(read_info.is_err());
+    }
+}
diff --git a/src/info_storages/models/available_info_storages.rs b/src/info_storages/models/available_info_storages.rs
index 66a84cd717781da8b919b6afe8c145c5140cb877..e5e481fb556dc20e606b3745689d36b7a644447c 100644
--- a/src/info_storages/models/available_info_storages.rs
+++ b/src/info_storages/models/available_info_storages.rs
@@ -32,21 +32,38 @@ impl AvailableInfoStores {
     /// # Params
     /// `config` - Rustus configuration.
     ///
+    #[cfg_attr(coverage, no_coverage)]
     pub async fn get(
         &self,
         config: &RustusConf,
     ) -> RustusResult<Box<dyn InfoStorage + Sync + Send>> {
         match self {
             Self::Files => Ok(Box::new(file_info_storage::FileInfoStorage::new(
-                config.clone(),
+                config.info_storage_opts.info_dir.clone(),
             ))),
             #[cfg(feature = "db_info_storage")]
             Self::DB => Ok(Box::new(
-                db_info_storage::DBInfoStorage::new(config.clone()).await?,
+                db_info_storage::DBInfoStorage::new(
+                    config
+                        .info_storage_opts
+                        .info_db_dsn
+                        .clone()
+                        .unwrap()
+                        .as_str(),
+                )
+                .await?,
             )),
             #[cfg(feature = "redis_info_storage")]
             AvailableInfoStores::Redis => Ok(Box::new(
-                redis_info_storage::RedisStorage::new(config.clone()).await?,
+                redis_info_storage::RedisStorage::new(
+                    config
+                        .info_storage_opts
+                        .info_db_dsn
+                        .clone()
+                        .unwrap()
+                        .as_str(),
+                )
+                .await?,
             )),
         }
     }
diff --git a/src/info_storages/models/file_info.rs b/src/info_storages/models/file_info.rs
index 44c55a161202d14598e01374bf5dc02043830cc8..bace48bd72d78095b72a280a05396a0e0d2e5c09 100644
--- a/src/info_storages/models/file_info.rs
+++ b/src/info_storages/models/file_info.rs
@@ -87,4 +87,15 @@ impl FileInfo {
             Some(result.join(","))
         }
     }
+
+    #[cfg(test)]
+    pub fn new_test() -> Self {
+        FileInfo::new(
+            uuid::Uuid::new_v4().to_string().as_str(),
+            Some(10),
+            Some("random_path".into()),
+            "random_storage".into(),
+            None,
+        )
+    }
 }
diff --git a/src/info_storages/redis_info_storage.rs b/src/info_storages/redis_info_storage.rs
index 584407df2900bd1d0a3ffcb8959aff6f17150c9a..b9e0b3b11b4686f9e454b593f38188ea589f8dfd 100644
--- a/src/info_storages/redis_info_storage.rs
+++ b/src/info_storages/redis_info_storage.rs
@@ -6,15 +6,14 @@ use redis::aio::Connection;
 
 use crate::errors::{RustusError, RustusResult};
 use crate::info_storages::{FileInfo, InfoStorage};
-use crate::RustusConf;
 
 pub struct RedisStorage {
     pool: Pool<RedisConnectionManager>,
 }
 
 impl RedisStorage {
-    pub async fn new(app_conf: RustusConf) -> RustusResult<Self> {
-        let client = redis::Client::open(app_conf.info_storage_opts.info_db_dsn.unwrap().as_str())?;
+    pub async fn new(db_dsn: &str) -> RustusResult<Self> {
+        let client = redis::Client::open(db_dsn)?;
         let manager = RedisConnectionManager::new(client);
         let pool = Pool::builder().max_open(100).build(manager);
         Ok(Self { pool })
@@ -62,3 +61,81 @@ impl InfoStorage for RedisStorage {
         }
     }
 }
+
+#[cfg(test)]
+#[cfg(feature = "test_redis")]
+mod tests {
+    use super::RedisStorage;
+    use crate::info_storages::FileInfo;
+    use crate::InfoStorage;
+    use mobc_redis::redis;
+    use mobc_redis::redis::AsyncCommands;
+
+    async fn get_storage() -> RedisStorage {
+        let redis_url = std::env::var("TEST_REDIS_URL").unwrap();
+        RedisStorage::new(redis_url.as_str()).await.unwrap()
+    }
+
+    async fn get_redis() -> redis::aio::Connection {
+        let redis_url = std::env::var("TEST_REDIS_URL").unwrap();
+        let redis = redis::Client::open(redis_url).unwrap();
+        redis.get_async_connection().await.unwrap()
+    }
+
+    #[actix_rt::test]
+    async fn success() {
+        let info_storage = get_storage().await;
+        let file_info = FileInfo::new_test();
+        info_storage.set_info(&file_info, true).await.unwrap();
+        let mut redis = get_redis().await;
+        let value: Option<String> = redis.get(file_info.id.as_str()).await.unwrap();
+        assert!(value.is_some());
+
+        let file_info_from_storage = info_storage.get_info(file_info.id.as_str()).await.unwrap();
+
+        assert_eq!(file_info.id, file_info_from_storage.id);
+        assert_eq!(file_info.path, file_info_from_storage.path);
+        assert_eq!(file_info.storage, file_info_from_storage.storage);
+    }
+
+    #[actix_rt::test]
+    async fn no_connection() {
+        let info_storage = RedisStorage::new("redis://unknonwn_url/0").await.unwrap();
+        let file_info = FileInfo::new_test();
+        let res = info_storage.set_info(&file_info, true).await;
+        assert!(res.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn unknown_id() {
+        let info_storage = get_storage().await;
+        let res = info_storage
+            .get_info(uuid::Uuid::new_v4().to_string().as_str())
+            .await;
+        assert!(res.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn deletion_success() {
+        let info_storage = get_storage().await;
+        let mut redis = get_redis().await;
+        let res = info_storage.remove_info("unknown").await;
+        assert!(res.is_err());
+        let file_info = FileInfo::new_test();
+        info_storage.set_info(&file_info, true).await.unwrap();
+        assert!(redis
+            .get::<&str, Option<String>>(file_info.id.as_str())
+            .await
+            .unwrap()
+            .is_some());
+        info_storage
+            .remove_info(file_info.id.as_str())
+            .await
+            .unwrap();
+        assert!(redis
+            .get::<&str, Option<String>>(file_info.id.as_str())
+            .await
+            .unwrap()
+            .is_none());
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index b05a51bf48a5110c010348f297fb62b0cc0e5214..93aacc19b89975f8e51d66314f381374d2c21764 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+#![cfg_attr(coverage, feature(no_coverage))]
 use std::str::FromStr;
 use std::sync::Arc;
 
@@ -10,12 +11,13 @@ use fern::colors::{Color, ColoredLevelConfig};
 use fern::Dispatch;
 use log::LevelFilter;
 
+use config::RustusConf;
+
 use crate::errors::RustusResult;
 use crate::info_storages::InfoStorage;
 use crate::notifiers::models::notification_manager::NotificationManager;
+use crate::server::rustus_service;
 use crate::state::State;
-use config::RustusConf;
-
 use crate::storages::Storage;
 
 mod config;
@@ -24,10 +26,12 @@ mod info_storages;
 mod notifiers;
 mod protocol;
 mod routes;
+mod server;
 mod state;
 mod storages;
 mod utils;
 
+#[cfg_attr(coverage, no_coverage)]
 fn greeting(app_conf: &RustusConf) {
     let extensions = app_conf
         .extensions_vec()
@@ -66,24 +70,15 @@ fn greeting(app_conf: &RustusConf) {
 /// This function may throw an error
 /// if the server can't be bound to the
 /// given address.
+#[cfg_attr(coverage, no_coverage)]
 pub fn create_server(state: State) -> Result<Server, std::io::Error> {
     let host = state.config.host.clone();
     let port = state.config.port;
-    let config = state.config.clone();
     let workers = state.config.workers;
     let state_data: web::Data<State> = web::Data::from(Arc::new(state));
     let mut server = HttpServer::new(move || {
         App::new()
-            .app_data(state_data.clone())
-            // Adds all routes.
-            .configure(protocol::setup(config.clone()))
-            // Main middleware that appends TUS headers.
-            .wrap(
-                middleware::DefaultHeaders::new()
-                    .add(("Tus-Resumable", "1.0.0"))
-                    .add(("Tus-Max-Size", config.max_body_size.to_string()))
-                    .add(("Tus-Version", "1.0.0")),
-            )
+            .configure(rustus_service(state_data.clone()))
             .wrap(middleware::Logger::new("\"%r\" \"-\" \"%s\" \"%a\" \"%D\""))
             // Middleware that overrides method of a request if
             // "X-HTTP-Method-Override" header is provided.
@@ -111,6 +106,7 @@ pub fn create_server(state: State) -> Result<Server, std::io::Error> {
     Ok(server.run())
 }
 
+#[cfg_attr(coverage, no_coverage)]
 fn setup_logging(app_config: &RustusConf) -> RustusResult<()> {
     let colors = ColoredLevelConfig::new()
         // use builder methods
@@ -137,6 +133,7 @@ fn setup_logging(app_config: &RustusConf) -> RustusResult<()> {
 }
 
 /// Main program entrypoint.
+#[cfg_attr(coverage, no_coverage)]
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let app_conf = RustusConf::from_args();
diff --git a/src/notifiers/amqp_notifier.rs b/src/notifiers/amqp_notifier.rs
index 4177c61be55400c20d2c925f9fe798e76be7095e..a7088f96161c65163754ff60cbc62949ca80b7fa 100644
--- a/src/notifiers/amqp_notifier.rs
+++ b/src/notifiers/amqp_notifier.rs
@@ -1,5 +1,5 @@
 use crate::notifiers::{Hook, Notifier};
-use crate::{RustusConf, RustusResult};
+use crate::RustusResult;
 use actix_web::http::header::HeaderMap;
 use async_trait::async_trait;
 use lapin::options::{
@@ -15,23 +15,25 @@ use tokio_amqp::LapinTokioExt;
 pub struct AMQPNotifier {
     exchange_name: String,
     pool: Pool<RMQConnectionManager>,
+    queues_prefix: String,
 }
 
 impl AMQPNotifier {
-    pub fn new(app_conf: RustusConf) -> Self {
+    pub fn new(amqp_url: &str, exchange: &str, queues_prefix: &str) -> Self {
         let manager = RMQConnectionManager::new(
-            app_conf.notification_opts.hooks_amqp_url.unwrap(),
+            amqp_url.into(),
             ConnectionProperties::default().with_tokio(),
         );
         let pool = Pool::<RMQConnectionManager>::builder().build(manager);
         Self {
             pool,
-            exchange_name: app_conf.notification_opts.hooks_amqp_exchange,
+            exchange_name: exchange.into(),
+            queues_prefix: queues_prefix.into(),
         }
     }
 
-    pub fn get_queue_name(hook: Hook) -> String {
-        format!("rustus.{}", hook)
+    pub fn get_queue_name(&self, hook: Hook) -> String {
+        format!("{}.{}", self.queues_prefix.as_str(), hook)
     }
 }
 
@@ -47,7 +49,7 @@ impl Notifier for AMQPNotifier {
         )
         .await?;
         for hook in Hook::iter() {
-            let queue_name = Self::get_queue_name(hook);
+            let queue_name = self.get_queue_name(hook);
             chan.queue_declare(
                 queue_name.as_str(),
                 QueueDeclareOptions::default(),
@@ -73,7 +75,7 @@ impl Notifier for AMQPNotifier {
         _header_map: &HeaderMap,
     ) -> RustusResult<()> {
         let chan = self.pool.get().await?.create_channel().await?;
-        let queue = Self::get_queue_name(hook);
+        let queue = self.get_queue_name(hook);
         chan.basic_publish(
             self.exchange_name.as_str(),
             queue.as_str(),
@@ -85,3 +87,68 @@ impl Notifier for AMQPNotifier {
         Ok(())
     }
 }
+
+#[cfg(feature = "test_rmq")]
+#[cfg(test)]
+mod tests {
+    use super::AMQPNotifier;
+    use crate::notifiers::{Hook, Notifier};
+    use actix_web::http::header::HeaderMap;
+    use lapin::options::{BasicAckOptions, BasicGetOptions};
+
+    async fn get_notifier() -> AMQPNotifier {
+        let amqp_url = std::env::var("TEST_AMQP_URL").unwrap();
+        let mut notifier = AMQPNotifier::new(
+            amqp_url.as_str(),
+            uuid::Uuid::new_v4().to_string().as_str(),
+            uuid::Uuid::new_v4().to_string().as_str(),
+        );
+        notifier.prepare().await.unwrap();
+        notifier
+    }
+
+    #[actix_rt::test]
+    async fn success() {
+        let notifier = get_notifier().await;
+        let hook = Hook::PostCreate;
+        let test_msg = String::from("Test Message");
+        notifier
+            .send_message(test_msg.clone(), hook.clone(), &HeaderMap::new())
+            .await
+            .unwrap();
+        let chan = notifier
+            .pool
+            .get()
+            .await
+            .unwrap()
+            .create_channel()
+            .await
+            .unwrap();
+        let message = chan
+            .basic_get(
+                format!("{}.{}", notifier.queues_prefix.as_str(), hook).as_str(),
+                BasicGetOptions::default(),
+            )
+            .await
+            .unwrap();
+        assert!(message.is_some());
+        assert_eq!(
+            String::from_utf8(message.clone().unwrap().data.clone()).unwrap(),
+            test_msg
+        );
+        message
+            .unwrap()
+            .ack(BasicAckOptions::default())
+            .await
+            .unwrap();
+    }
+
+    #[actix_rt::test]
+    async fn unknown_url() {
+        let notifier = AMQPNotifier::new("http://unknown", "test", "test");
+        let res = notifier
+            .send_message("Test Message".into(), Hook::PostCreate, &HeaderMap::new())
+            .await;
+        assert!(res.is_err());
+    }
+}
diff --git a/src/notifiers/dir_notifier.rs b/src/notifiers/dir_notifier.rs
index 670d1e1bd04aeb9839ce3691932d89b7f0474d50..008d79ca07ca42244619e1aef9d4eddc65a5e32b 100644
--- a/src/notifiers/dir_notifier.rs
+++ b/src/notifiers/dir_notifier.rs
@@ -2,11 +2,10 @@ use crate::errors::RustusError;
 use crate::notifiers::{Hook, Notifier};
 use crate::RustusResult;
 use actix_web::http::header::HeaderMap;
-use async_process::{Command, Stdio};
 use async_trait::async_trait;
-use futures::AsyncWriteExt;
 use log::debug;
 use std::path::PathBuf;
+use tokio::process::Command;
 
 pub struct DirNotifier {
     pub dir: PathBuf,
@@ -31,18 +30,71 @@ impl Notifier for DirNotifier {
         _headers_map: &HeaderMap,
     ) -> RustusResult<()> {
         let hook_path = self.dir.join(hook.to_string());
+        if !hook_path.exists() {
+            debug!("Hook {} not found.", hook.to_string());
+            return Err(RustusError::HookError(format!(
+                "Hook file {} not found.",
+                hook
+            )));
+        }
         debug!("Running hook: {}", hook_path.as_path().display());
-        let mut command = Command::new(hook_path).stdin(Stdio::piped()).spawn()?;
-        command
-            .stdin
-            .as_mut()
-            .unwrap()
-            .write_all(message.as_bytes())
-            .await?;
-        let stat = command.status().await?;
+        let mut command = Command::new(hook_path).arg(message).spawn()?;
+        let stat = command.wait().await?;
         if !stat.success() {
             return Err(RustusError::HookError("Returned wrong status code".into()));
         }
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::DirNotifier;
+    use crate::notifiers::{Hook, Notifier};
+    use actix_web::http::header::HeaderMap;
+    use std::fs::File;
+    use std::io::{Read, Write};
+    #[cfg(unix)]
+    use std::os::unix::fs::PermissionsExt;
+    use tempdir::TempDir;
+
+    #[actix_rt::test]
+    async fn no_such_hook_file() {
+        let hook_dir = TempDir::new("dir_notifier").unwrap().into_path();
+        let notifier = DirNotifier::new(hook_dir);
+        let res = notifier
+            .send_message("test".into(), Hook::PostCreate, &HeaderMap::new())
+            .await;
+        assert!(res.is_err());
+    }
+
+    #[cfg(unix)]
+    #[actix_rt::test]
+    async fn success() {
+        let hook = Hook::PostCreate;
+        let dir = tempdir::TempDir::new("dir_notifier").unwrap().into_path();
+        let hook_path = dir.join(hook.to_string());
+        {
+            let mut file = File::create(hook_path.clone()).unwrap();
+            let mut permissions = file.metadata().unwrap().permissions();
+            permissions.set_mode(0o755);
+            file.set_permissions(permissions).unwrap();
+            let script = r#"#!/bin/sh
+            echo "$1" > "$(dirname $0)/output""#;
+            file.write_all(script.as_bytes()).unwrap();
+            file.sync_all().unwrap();
+        }
+        let notifier = DirNotifier::new(dir.to_path_buf());
+        let test_message = uuid::Uuid::new_v4().to_string();
+        notifier
+            .send_message(test_message.clone(), hook.clone(), &HeaderMap::new())
+            .await
+            .unwrap();
+        let output_path = dir.join("output");
+        assert!(output_path.exists());
+        let mut buffer = String::new();
+        let mut out_file = File::open(output_path).unwrap();
+        out_file.read_to_string(&mut buffer).unwrap();
+        assert_eq!(buffer, format!("{}\n", test_message));
+    }
+}
diff --git a/src/notifiers/file_notifier.rs b/src/notifiers/file_notifier.rs
index a502592c24fdc68fc3ef2db125598e3f1b1f2f75..5ac7e31484df5bf543483947075878968ce474fc 100644
--- a/src/notifiers/file_notifier.rs
+++ b/src/notifiers/file_notifier.rs
@@ -2,10 +2,9 @@ use crate::errors::RustusError;
 use crate::notifiers::{Hook, Notifier};
 use crate::RustusResult;
 use actix_web::http::header::HeaderMap;
-use async_process::{Command, Stdio};
 use async_trait::async_trait;
-use futures::AsyncWriteExt;
 use log::debug;
+use tokio::process::Command;
 
 pub struct FileNotifier {
     pub command: String,
@@ -19,6 +18,7 @@ impl FileNotifier {
 
 #[async_trait]
 impl Notifier for FileNotifier {
+    #[cfg_attr(coverage, no_coverage)]
     async fn prepare(&mut self) -> RustusResult<()> {
         Ok(())
     }
@@ -32,18 +32,87 @@ impl Notifier for FileNotifier {
         debug!("Running command: {}", self.command.as_str());
         let mut command = Command::new(self.command.as_str())
             .arg(hook.to_string())
-            .stdin(Stdio::piped())
+            .arg(message)
             .spawn()?;
-        command
-            .stdin
-            .as_mut()
-            .unwrap()
-            .write_all(message.as_bytes())
-            .await?;
-        let stat = command.status().await?;
+        let stat = command.wait().await?;
         if !stat.success() {
             return Err(RustusError::HookError("Returned wrong status code".into()));
         }
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::FileNotifier;
+    use crate::notifiers::{Hook, Notifier};
+    use actix_web::http::header::HeaderMap;
+    use std::fs::File;
+    use std::io::{Read, Write};
+    #[cfg(unix)]
+    use std::os::unix::fs::PermissionsExt;
+
+    #[cfg(unix)]
+    #[actix_rt::test]
+    async fn success() {
+        let dir = tempdir::TempDir::new("file_notifier").unwrap().into_path();
+        let hook_path = dir.join("executable.sh");
+        {
+            let mut file = File::create(hook_path.clone()).unwrap();
+            let mut permissions = file.metadata().unwrap().permissions();
+            permissions.set_mode(0o755);
+            file.set_permissions(permissions).unwrap();
+            let script = r#"#!/bin/sh
+            HOOK_NAME="$1";
+            MESSAGE="$2";
+            echo "$HOOK_NAME $MESSAGE" > "$(dirname $0)/output""#;
+            file.write_all(script.as_bytes()).unwrap();
+            file.sync_all().unwrap();
+        }
+        let notifier = FileNotifier::new(hook_path.display().to_string());
+        let hook = Hook::PostCreate;
+        let test_message = uuid::Uuid::new_v4().to_string();
+        notifier
+            .send_message(test_message.clone(), hook.clone(), &HeaderMap::new())
+            .await
+            .unwrap();
+        let output_path = dir.join("output");
+        assert!(output_path.exists());
+        let mut buffer = String::new();
+        let mut out_file = File::open(output_path).unwrap();
+        out_file.read_to_string(&mut buffer).unwrap();
+        assert_eq!(buffer, format!("{} {}\n", hook.to_string(), test_message));
+    }
+
+    #[cfg(unix)]
+    #[actix_rt::test]
+    async fn error_status() {
+        let dir = tempdir::TempDir::new("file_notifier").unwrap().into_path();
+        let hook_path = dir.join("error_executable.sh");
+        {
+            let mut file = File::create(hook_path.clone()).unwrap();
+            let mut permissions = file.metadata().unwrap().permissions();
+            permissions.set_mode(0o755);
+            file.set_permissions(permissions).unwrap();
+            let script = r#"#!/bin/sh
+            read -t 0.1 MESSAGE
+            exit 1"#;
+            file.write_all(script.as_bytes()).unwrap();
+            file.sync_all().unwrap();
+        }
+        let notifier = FileNotifier::new(hook_path.display().to_string());
+        let res = notifier
+            .send_message("test".into(), Hook::PostCreate, &HeaderMap::new())
+            .await;
+        assert!(res.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn no_such_file() {
+        let notifier = FileNotifier::new(format!("/{}.sh", uuid::Uuid::new_v4()));
+        let res = notifier
+            .send_message("test".into(), Hook::PreCreate, &HeaderMap::new())
+            .await;
+        assert!(res.is_err());
+    }
+}
diff --git a/src/notifiers/http_notifier.rs b/src/notifiers/http_notifier.rs
index d8ea9ac300881b0924622c02100434331e254612..2e710047d16bf3a3099c6ab20008dd5ed36c75dd 100644
--- a/src/notifiers/http_notifier.rs
+++ b/src/notifiers/http_notifier.rs
@@ -28,6 +28,7 @@ impl HttpNotifier {
 
 #[async_trait]
 impl Notifier for HttpNotifier {
+    #[cfg_attr(coverage, no_coverage)]
     async fn prepare(&mut self) -> RustusResult<()> {
         Ok(())
     }
@@ -62,3 +63,94 @@ impl Notifier for HttpNotifier {
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::HttpNotifier;
+    use crate::notifiers::{Hook, Notifier};
+    use actix_web::http::header::{HeaderMap, HeaderName, HeaderValue};
+    use httptest::matchers::contains;
+    use httptest::responders::status_code;
+    use std::str::FromStr;
+    use std::time::Duration;
+
+    #[actix_rt::test]
+    async fn success_request() {
+        let server = httptest::Server::run();
+        server.expect(
+            httptest::Expectation::matching(httptest::matchers::request::method_path(
+                "POST", "/hook",
+            ))
+            .respond_with(httptest::responders::status_code(200)),
+        );
+        let hook_url = server.url_str("/hook");
+
+        let notifier = HttpNotifier::new(vec![hook_url], vec![]);
+        notifier
+            .send_message("test_message".into(), Hook::PostCreate, &HeaderMap::new())
+            .await
+            .unwrap();
+    }
+
+    #[actix_rt::test]
+    async fn timeout_request() {
+        let server = httptest::Server::run();
+        server.expect(
+            httptest::Expectation::matching(httptest::matchers::request::method_path(
+                "POST", "/hook",
+            ))
+            .respond_with(httptest::responders::delay_and_then(
+                Duration::from_secs(3),
+                status_code(200),
+            )),
+        );
+        let hook_url = server.url_str("/hook");
+
+        let notifier = HttpNotifier::new(vec![hook_url], vec![]);
+        let result = notifier
+            .send_message("test_message".into(), Hook::PostCreate, &HeaderMap::new())
+            .await;
+        assert!(result.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn unknown_url() {
+        let server = httptest::Server::run();
+        server.expect(
+            httptest::Expectation::matching(httptest::matchers::request::method_path(
+                "POST", "/hook",
+            ))
+            .respond_with(httptest::responders::status_code(404)),
+        );
+        let hook_url = server.url_str("/hook");
+
+        let notifier = HttpNotifier::new(vec![hook_url], vec![]);
+        let result = notifier
+            .send_message("test_message".into(), Hook::PostCreate, &HeaderMap::new())
+            .await;
+        assert!(result.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn forwarded_header() {
+        let server = httptest::Server::run();
+        server.expect(
+            httptest::Expectation::matching(httptest::matchers::all_of![
+                httptest::matchers::request::method_path("POST", "/hook",),
+                httptest::matchers::request::headers(contains(("x-test-header", "meme-value")))
+            ])
+            .respond_with(httptest::responders::status_code(200)),
+        );
+        let hook_url = server.url_str("/hook");
+        let notifier = HttpNotifier::new(vec![hook_url], vec!["X-TEST-HEADER".into()]);
+        let mut header_map = HeaderMap::new();
+        header_map.insert(
+            HeaderName::from_str("X-TEST-HEADER").unwrap(),
+            HeaderValue::from_str("meme-value").unwrap(),
+        );
+        notifier
+            .send_message("test_message".into(), Hook::PostCreate, &header_map)
+            .await
+            .unwrap();
+    }
+}
diff --git a/src/notifiers/mod.rs b/src/notifiers/mod.rs
index 86ac9c692379c510f4fb15ee8cc7012f21f84131..03595103b447fc29583de41caaf80e1ac392c031 100644
--- a/src/notifiers/mod.rs
+++ b/src/notifiers/mod.rs
@@ -1,8 +1,6 @@
 #[cfg(feature = "amqp_notifier")]
 pub mod amqp_notifier;
-#[cfg(feature = "file_notifiers")]
 pub mod dir_notifier;
-#[cfg(feature = "file_notifiers")]
 mod file_notifier;
 #[cfg(feature = "http_notifier")]
 pub mod http_notifier;
diff --git a/src/notifiers/models/notification_manager.rs b/src/notifiers/models/notification_manager.rs
index 84509da22954ee0e5ae5241896001713bb86b39e..68c2ae9dfa8e7278ac7736bd736472ecd8a217fb 100644
--- a/src/notifiers/models/notification_manager.rs
+++ b/src/notifiers/models/notification_manager.rs
@@ -1,9 +1,7 @@
 use crate::errors::RustusResult;
 #[cfg(feature = "amqp_notifier")]
 use crate::notifiers::amqp_notifier;
-#[cfg(feature = "file_notifiers")]
 use crate::notifiers::dir_notifier::DirNotifier;
-#[cfg(feature = "file_notifiers")]
 use crate::notifiers::file_notifier::FileNotifier;
 #[cfg(feature = "http_notifier")]
 use crate::notifiers::http_notifier;
@@ -17,45 +15,52 @@ pub struct NotificationManager {
 }
 
 impl NotificationManager {
-    pub async fn new(tus_config: &RustusConf) -> RustusResult<Self> {
+    pub async fn new(rustus_config: &RustusConf) -> RustusResult<Self> {
         let mut manager = Self {
             notifiers: Vec::new(),
         };
         debug!("Initializing notification manager.");
-        #[cfg(feature = "file_notifiers")]
-        if tus_config.notification_opts.hooks_file.is_some() {
+        if rustus_config.notification_opts.hooks_file.is_some() {
             debug!("Found hooks file");
             manager.notifiers.push(Box::new(FileNotifier::new(
-                tus_config.notification_opts.hooks_file.clone().unwrap(),
+                rustus_config.notification_opts.hooks_file.clone().unwrap(),
             )));
         }
-        #[cfg(feature = "file_notifiers")]
-        if tus_config.notification_opts.hooks_dir.is_some() {
+        if rustus_config.notification_opts.hooks_dir.is_some() {
             debug!("Found hooks directory");
             manager.notifiers.push(Box::new(DirNotifier::new(
-                tus_config.notification_opts.hooks_dir.clone().unwrap(),
+                rustus_config.notification_opts.hooks_dir.clone().unwrap(),
             )));
         }
         #[cfg(feature = "http_notifier")]
-        if !tus_config.notification_opts.hooks_http_urls.is_empty() {
+        if !rustus_config.notification_opts.hooks_http_urls.is_empty() {
             debug!("Found http hook urls.");
             manager
                 .notifiers
                 .push(Box::new(http_notifier::HttpNotifier::new(
-                    tus_config.notification_opts.hooks_http_urls.clone(),
-                    tus_config
+                    rustus_config.notification_opts.hooks_http_urls.clone(),
+                    rustus_config
                         .notification_opts
                         .hooks_http_proxy_headers
                         .clone(),
                 )));
         }
         #[cfg(feature = "amqp_notifier")]
-        if tus_config.notification_opts.hooks_amqp_url.is_some() {
+        if rustus_config.notification_opts.hooks_amqp_url.is_some() {
             debug!("Found AMQP notifier.");
             manager
                 .notifiers
                 .push(Box::new(amqp_notifier::AMQPNotifier::new(
-                    tus_config.clone(),
+                    rustus_config
+                        .notification_opts
+                        .hooks_amqp_url
+                        .as_ref()
+                        .unwrap(),
+                    rustus_config.notification_opts.hooks_amqp_exchange.as_str(),
+                    rustus_config
+                        .notification_opts
+                        .hooks_amqp_queues_prefix
+                        .as_str(),
                 )));
         }
         for notifier in &mut manager.notifiers.iter_mut() {
diff --git a/src/protocol/core/get_info.rs b/src/protocol/core/get_info.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bc8cc8ad64572a5e2358a3467f9f15ca8c02095f
--- /dev/null
+++ b/src/protocol/core/get_info.rs
@@ -0,0 +1,263 @@
+use actix_web::{web, HttpRequest, HttpResponse};
+
+use crate::errors::RustusError;
+
+use crate::{RustusResult, State};
+
+pub async fn get_file_info(
+    state: web::Data<State>,
+    request: HttpRequest,
+) -> RustusResult<HttpResponse> {
+    // Getting file id from URL.
+    if request.match_info().get("file_id").is_none() {
+        return Err(RustusError::FileNotFound);
+    }
+    let file_id = request.match_info().get("file_id").unwrap();
+
+    // Getting file info from info_storage.
+    let file_info = state.info_storage.get_info(file_id).await?;
+    if file_info.storage != state.data_storage.to_string() {
+        return Err(RustusError::FileNotFound);
+    }
+    let mut builder = HttpResponse::Ok();
+    if file_info.is_partial {
+        builder.insert_header(("Upload-Concat", "partial"));
+    }
+    if file_info.is_final && file_info.parts.is_some() {
+        #[allow(clippy::or_fun_call)]
+        let parts = file_info
+            .parts
+            .clone()
+            .unwrap()
+            .iter()
+            .map(|file| {
+                format!(
+                    "{}/{}",
+                    state
+                        .config
+                        .base_url()
+                        .strip_suffix('/')
+                        .unwrap_or(state.config.base_url().as_str()),
+                    file.as_str()
+                )
+            })
+            .collect::<Vec<String>>()
+            .join(" ");
+        builder.insert_header(("Upload-Concat", format!("final; {}", parts)));
+    }
+    builder
+        .no_chunking(file_info.offset as u64)
+        .insert_header(("Upload-Offset", file_info.offset.to_string()))
+        .insert_header(("Content-Length", file_info.offset.to_string()));
+    // Upload length is known.
+    if let Some(upload_len) = file_info.length {
+        builder.insert_header(("Upload-Length", upload_len.to_string()));
+    } else {
+        builder.insert_header(("Upload-Defer-Length", "1"));
+    }
+    if let Some(meta) = file_info.get_metadata_string() {
+        builder.insert_header(("Upload-Metadata", meta));
+    }
+    Ok(builder.finish())
+}
+
+#[cfg(test)]
+mod tests {
+    use actix_web::http::{Method, StatusCode};
+
+    use crate::{rustus_service, State};
+    use actix_web::test::{call_service, init_service, TestRequest};
+    use actix_web::{web, App};
+
+    #[actix_rt::test]
+    async fn success() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.offset = 100;
+        file_info.length = Some(100);
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        let offset = response
+            .headers()
+            .get("Upload-Offset")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .parse::<usize>()
+            .unwrap();
+        assert_eq!(file_info.offset, offset)
+    }
+
+    #[actix_rt::test]
+    async fn success_metadata() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.offset = 100;
+        file_info.length = Some(100);
+        file_info.metadata.insert("test".into(), "value".into());
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        let metadata = response
+            .headers()
+            .get("Upload-Metadata")
+            .unwrap()
+            .to_str()
+            .unwrap();
+        assert_eq!(
+            String::from(metadata),
+            format!("{} {}", "test", base64::encode("value"))
+        )
+    }
+
+    #[actix_rt::test]
+    async fn success_defer_len() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.deferred_size = true;
+        file_info.length = None;
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(
+            response
+                .headers()
+                .get("Upload-Defer-Length")
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            "1"
+        );
+    }
+
+    #[actix_rt::test]
+    async fn test_get_file_info_partial() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.is_partial = true;
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(
+            response
+                .headers()
+                .get("Upload-Concat")
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            "partial"
+        );
+    }
+
+    #[actix_rt::test]
+    async fn success_final() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.is_partial = false;
+        file_info.is_final = true;
+        file_info.parts = Some(vec!["test1".into(), "test2".into()]);
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(
+            response
+                .headers()
+                .get("Upload-Concat")
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            format!(
+                "final; {} {}",
+                state.config.file_url("test1"),
+                state.config.file_url("test2")
+            )
+            .as_str()
+        );
+    }
+
+    #[actix_rt::test]
+    async fn no_file() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::with_uri(state.config.file_url("unknknown").as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(response.status(), StatusCode::NOT_FOUND);
+    }
+
+    #[actix_rt::test]
+    async fn test_get_file_info_wrong_storage() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.storage = String::from("unknown");
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::with_uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .method(Method::HEAD)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(response.status(), StatusCode::NOT_FOUND);
+    }
+}
diff --git a/src/protocol/core/mod.rs b/src/protocol/core/mod.rs
index df00d5cab53b70e640e582c47d2cc7abcbbded15..dc29f98b35c65ee0b12ebee5a6e0c2b40c1c9622 100644
--- a/src/protocol/core/mod.rs
+++ b/src/protocol/core/mod.rs
@@ -1,9 +1,8 @@
-use actix_web::web::PayloadConfig;
 use actix_web::{guard, middleware, web};
 
-use crate::RustusConf;
-
-mod routes;
+mod get_info;
+mod server_info;
+mod write_bytes;
 
 /// Add core TUS protocol endpoints.
 ///
@@ -13,34 +12,33 @@ mod routes;
 /// OPTIONS /api    - to get info about the app.
 /// HEAD /api/file  - to get info about the file.
 /// PATCH /api/file - to add bytes to file.
-pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &RustusConf) {
+#[cfg_attr(coverage, no_coverage)]
+pub fn add_extension(web_app: &mut web::ServiceConfig) {
     web_app
         .service(
             // PATCH /base/{file_id}
             // Main URL for uploading files.
-            web::resource(app_conf.base_url().as_str())
+            web::resource("")
                 .name("core:server_info")
                 .guard(guard::Options())
-                .to(routes::server_info),
+                .to(server_info::server_info),
         )
         .service(
             // PATCH /base/{file_id}
             // Main URL for uploading files.
-            web::resource(app_conf.file_url().as_str())
-                // 10 MB chunks
-                .app_data(PayloadConfig::new(app_conf.max_body_size))
+            web::resource("{file_id}")
                 .name("core:write_bytes")
                 .guard(guard::Patch())
-                .to(routes::write_bytes),
+                .to(write_bytes::write_bytes),
         )
         .service(
             // HEAD /base/{file_id}
             // Main URL for getting info about files.
-            web::resource(app_conf.file_url().as_str())
+            web::resource("{file_id}")
                 .name("core:file_info")
                 .guard(guard::Head())
                 // Header to prevent the client and/or proxies from caching the response.
                 .wrap(middleware::DefaultHeaders::new().add(("Cache-Control", "no-store")))
-                .to(routes::get_file_info),
+                .to(get_info::get_file_info),
         );
 }
diff --git a/src/protocol/core/routes.rs b/src/protocol/core/routes.rs
deleted file mode 100644
index d910f67c2be7bb6a7b18fa562cd9a266ab1f3fb6..0000000000000000000000000000000000000000
--- a/src/protocol/core/routes.rs
+++ /dev/null
@@ -1,187 +0,0 @@
-use actix_web::{web, web::Bytes, HttpRequest, HttpResponse};
-
-use crate::errors::RustusError;
-use crate::notifiers::Hook;
-use crate::protocol::extensions::Extensions;
-use crate::utils::headers::{check_header, parse_header};
-use crate::{RustusConf, State};
-
-#[allow(clippy::needless_pass_by_value)]
-pub fn server_info(app_conf: web::Data<RustusConf>) -> HttpResponse {
-    let ext_str = app_conf
-        .extensions_vec()
-        .into_iter()
-        .map(|x| x.to_string())
-        .collect::<Vec<String>>()
-        .join(",");
-    HttpResponse::Ok()
-        .insert_header(("Tus-Extension", ext_str.as_str()))
-        .finish()
-}
-
-pub async fn get_file_info(
-    state: web::Data<State>,
-    request: HttpRequest,
-) -> actix_web::Result<HttpResponse> {
-    // Getting file id from URL.
-    if request.match_info().get("file_id").is_none() {
-        return Ok(HttpResponse::NotFound().body("No file id provided."));
-    }
-    let file_id = request.match_info().get("file_id").unwrap();
-
-    // Getting file info from info_storage.
-    let file_info = state.info_storage.get_info(file_id).await?;
-    if file_info.storage != state.data_storage.to_string() {
-        return Ok(HttpResponse::NotFound().body("File not found."));
-    }
-    let mut builder = HttpResponse::Ok();
-    if file_info.is_partial {
-        builder.insert_header(("Upload-Concat", "partial"));
-    }
-    if file_info.is_final && file_info.parts.is_some() {
-        #[allow(clippy::or_fun_call)]
-        let parts = file_info
-            .parts
-            .clone()
-            .unwrap()
-            .iter()
-            .map(|file| {
-                format!(
-                    "{}/{}",
-                    state
-                        .config
-                        .base_url()
-                        .strip_suffix('/')
-                        .unwrap_or(state.config.base_url().as_str()),
-                    file.as_str()
-                )
-            })
-            .collect::<Vec<String>>()
-            .join(" ");
-        builder.insert_header(("Upload-Concat", format!("final; {}", parts)));
-    }
-    builder
-        .no_chunking(file_info.offset as u64)
-        .insert_header(("Upload-Offset", file_info.offset.to_string()))
-        .insert_header(("Content-Length", file_info.offset.to_string()));
-    // Upload length is known.
-    if let Some(upload_len) = file_info.length {
-        builder.insert_header(("Upload-Length", upload_len.to_string()));
-    } else {
-        builder.insert_header(("Upload-Defer-Length", "1"));
-    }
-    if let Some(meta) = file_info.get_metadata_string() {
-        builder.insert_header(("Upload-Metadata", meta));
-    }
-    Ok(builder.finish())
-}
-
-pub async fn write_bytes(
-    request: HttpRequest,
-    bytes: Bytes,
-    state: web::Data<State>,
-) -> actix_web::Result<HttpResponse> {
-    // Checking if request has required headers.
-    let check_content_type = |val: &str| val == "application/offset+octet-stream";
-    if !check_header(&request, "Content-Type", check_content_type) {
-        return Ok(HttpResponse::UnsupportedMediaType().body("Unknown content-type."));
-    }
-    // Getting current offset.
-    let offset: Option<usize> = parse_header(&request, "Upload-Offset");
-
-    if offset.is_none() {
-        return Ok(HttpResponse::UnsupportedMediaType().body("No offset provided."));
-    }
-
-    if request.match_info().get("file_id").is_none() {
-        return Ok(HttpResponse::NotFound().body("No file id provided."));
-    }
-
-    // New upload length.
-    // Parses header `Upload-Length` only if the creation-defer-length extension is enabled.
-    let updated_len = if state
-        .config
-        .extensions_vec()
-        .contains(&Extensions::CreationDeferLength)
-    {
-        parse_header(&request, "Upload-Length")
-    } else {
-        None
-    };
-
-    let file_id = request.match_info().get("file_id").unwrap();
-    // Getting file info.
-    let mut file_info = state.info_storage.get_info(file_id).await?;
-
-    // According to TUS protocol you can't update final uploads.
-    if file_info.is_final {
-        return Ok(HttpResponse::Forbidden().finish());
-    }
-
-    // Checking if file was stored in the same storage.
-    if file_info.storage != state.data_storage.to_string() {
-        return Ok(HttpResponse::NotFound().finish());
-    }
-    // Checking if offset from request is the same as the real offset.
-    if offset.unwrap() != file_info.offset {
-        return Ok(HttpResponse::Conflict().finish());
-    }
-
-    // If someone want to update file length.
-    // This required by Upload-Defer-Length extension.
-    if let Some(new_len) = updated_len {
-        // Whoop, someone gave us total file length
-        // less that he had already uploaded.
-        if new_len < file_info.offset {
-            return Err(RustusError::WrongOffset.into());
-        }
-        // We already know the exact size of a file.
-        // Someone want to update it.
-        // Anyway, it's not allowed, heh.
-        if file_info.length.is_some() {
-            return Err(RustusError::SizeAlreadyKnown.into());
-        }
-
-        // All checks are ok. Now our file will have exact size.
-        file_info.deferred_size = false;
-        file_info.length = Some(new_len);
-    }
-
-    // Checking if the size of the upload is already equals
-    // to calculated offset. It means that all bytes were already written.
-    if Some(file_info.offset) == file_info.length {
-        return Err(RustusError::FrozenFile.into());
-    }
-
-    // Appending bytes to file.
-    state
-        .data_storage
-        .add_bytes(&file_info, bytes.as_ref())
-        .await?;
-    // Updating offset.
-    file_info.offset += bytes.len();
-    // Saving info to info storage.
-    state.info_storage.set_info(&file_info, false).await?;
-
-    let mut hook = Hook::PostReceive;
-    if file_info.length == Some(file_info.offset) {
-        hook = Hook::PostFinish;
-    }
-    if state.config.hook_is_active(hook) {
-        let message = state
-            .config
-            .notification_opts
-            .hooks_format
-            .format(&request, &file_info)?;
-        let headers = request.headers().clone();
-        tokio::spawn(async move {
-            state
-                .notification_manager
-                .send_message(message, hook, &headers)
-                .await
-        });
-    }
-    Ok(HttpResponse::NoContent()
-        .insert_header(("Upload-Offset", file_info.offset.to_string()))
-        .finish())
-}
diff --git a/src/protocol/core/server_info.rs b/src/protocol/core/server_info.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f5217cd49d43f88ff6ea25fde34e15a3a12c468b
--- /dev/null
+++ b/src/protocol/core/server_info.rs
@@ -0,0 +1,56 @@
+use actix_web::{web, HttpResponse};
+
+use crate::State;
+
+#[allow(clippy::needless_pass_by_value)]
+#[allow(clippy::unused_async)]
+pub async fn server_info(state: web::Data<State>) -> HttpResponse {
+    let ext_str = state
+        .config
+        .extensions_vec()
+        .into_iter()
+        .map(|x| x.to_string())
+        .collect::<Vec<String>>()
+        .join(",");
+    HttpResponse::Ok()
+        .insert_header(("Tus-Extension", ext_str.as_str()))
+        .finish()
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::protocol::extensions::Extensions;
+    use crate::{rustus_service, State};
+    use actix_web::test::{call_service, init_service, TestRequest};
+
+    use actix_web::http::Method;
+    use actix_web::{web, App};
+
+    #[actix_rt::test]
+    async fn test_server_info() {
+        let mut state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        state.config.tus_extensions = vec![
+            Extensions::Creation,
+            Extensions::Concatenation,
+            Extensions::Termination,
+        ];
+        let request = TestRequest::with_uri(state.config.base_url().as_str())
+            .method(Method::OPTIONS)
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        let extensions = response
+            .headers()
+            .get("Tus-Extension")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .clone();
+        assert!(extensions.contains(Extensions::Creation.to_string().as_str()));
+        assert!(extensions.contains(Extensions::Concatenation.to_string().as_str()));
+        assert!(extensions.contains(Extensions::Termination.to_string().as_str()));
+    }
+}
diff --git a/src/protocol/core/write_bytes.rs b/src/protocol/core/write_bytes.rs
new file mode 100644
index 0000000000000000000000000000000000000000..322b2191a6864e7e0ccf27fc2b6628c5fad2540a
--- /dev/null
+++ b/src/protocol/core/write_bytes.rs
@@ -0,0 +1,411 @@
+use actix_web::{web, web::Bytes, HttpRequest, HttpResponse};
+
+use crate::errors::RustusError;
+use crate::notifiers::Hook;
+use crate::protocol::extensions::Extensions;
+use crate::utils::headers::{check_header, parse_header};
+use crate::{RustusResult, State};
+
+pub async fn write_bytes(
+    request: HttpRequest,
+    bytes: Bytes,
+    state: web::Data<State>,
+) -> RustusResult<HttpResponse> {
+    // Checking if request has required headers.
+    let check_content_type = |val: &str| val == "application/offset+octet-stream";
+    if !check_header(&request, "Content-Type", check_content_type) {
+        return Ok(HttpResponse::UnsupportedMediaType().body("Unknown content-type."));
+    }
+    // Getting current offset.
+    let offset: Option<usize> = parse_header(&request, "Upload-Offset");
+
+    if offset.is_none() {
+        return Ok(HttpResponse::UnsupportedMediaType().body("No offset provided."));
+    }
+
+    if request.match_info().get("file_id").is_none() {
+        return Err(RustusError::FileNotFound);
+    }
+
+    // New upload length.
+    // Parses header `Upload-Length` only if the creation-defer-length extension is enabled.
+    let updated_len = if state
+        .config
+        .extensions_vec()
+        .contains(&Extensions::CreationDeferLength)
+    {
+        parse_header(&request, "Upload-Length")
+    } else {
+        None
+    };
+
+    let file_id = request.match_info().get("file_id").unwrap();
+    // Getting file info.
+    let mut file_info = state.info_storage.get_info(file_id).await?;
+
+    // According to TUS protocol you can't update final uploads.
+    if file_info.is_final {
+        return Ok(HttpResponse::Forbidden().finish());
+    }
+
+    // Checking if file was stored in the same storage.
+    if file_info.storage != state.data_storage.to_string() {
+        return Err(RustusError::FileNotFound);
+    }
+    // Checking if offset from request is the same as the real offset.
+    if offset.unwrap() != file_info.offset {
+        return Ok(HttpResponse::Conflict().finish());
+    }
+
+    // If someone want to update file length.
+    // This required by Upload-Defer-Length extension.
+    if let Some(new_len) = updated_len {
+        // Whoop, someone gave us total file length
+        // less that he had already uploaded.
+        if new_len < file_info.offset {
+            return Err(RustusError::WrongOffset);
+        }
+        // We already know the exact size of a file.
+        // Someone want to update it.
+        // Anyway, it's not allowed, heh.
+        if file_info.length.is_some() {
+            return Err(RustusError::SizeAlreadyKnown);
+        }
+
+        // All checks are ok. Now our file will have exact size.
+        file_info.deferred_size = false;
+        file_info.length = Some(new_len);
+    }
+
+    // Checking if the size of the upload is already equals
+    // to calculated offset. It means that all bytes were already written.
+    if Some(file_info.offset) == file_info.length {
+        return Err(RustusError::FrozenFile);
+    }
+
+    // Appending bytes to file.
+    state
+        .data_storage
+        .add_bytes(&file_info, bytes.as_ref())
+        .await?;
+    // Updating offset.
+    file_info.offset += bytes.len();
+    // Saving info to info storage.
+    state.info_storage.set_info(&file_info, false).await?;
+
+    let mut hook = Hook::PostReceive;
+    if file_info.length == Some(file_info.offset) {
+        hook = Hook::PostFinish;
+    }
+    if state.config.hook_is_active(hook) {
+        let message = state
+            .config
+            .notification_opts
+            .hooks_format
+            .format(&request, &file_info)?;
+        let headers = request.headers().clone();
+        tokio::spawn(async move {
+            state
+                .notification_manager
+                .send_message(message, hook, &headers)
+                .await
+        });
+    }
+    Ok(HttpResponse::NoContent()
+        .insert_header(("Upload-Offset", file_info.offset.to_string()))
+        .finish())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{rustus_service, State};
+    use actix_web::http::StatusCode;
+    use actix_web::test::{call_service, init_service, TestRequest};
+    use actix_web::{web, App};
+
+    #[actix_rt::test]
+    /// Success test for writing bytes.
+    ///
+    /// This test creates file and writes bytes to it.
+    async fn success() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = Some(100);
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let test_data = "memes";
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .insert_header(("Upload-Offset", file.offset))
+            .set_payload(test_data)
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NO_CONTENT);
+        assert_eq!(
+            resp.headers()
+                .get("Upload-Offset")
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            test_data.len().to_string().as_str()
+        );
+        let new_info = state
+            .info_storage
+            .get_info(file.id.clone().as_str())
+            .await
+            .unwrap();
+        assert_eq!(new_info.offset, test_data.len());
+    }
+
+    #[actix_rt::test]
+    /// Testing defer-length extension.
+    ///
+    /// During this test we'll try to update
+    /// file's length while writing bytes to it.
+    async fn success_update_file_length() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = None;
+        file.deferred_size = true;
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let test_data = "memes";
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .param("file_id", file.id.clone())
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Upload-Length", "20"))
+            .set_payload(test_data)
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NO_CONTENT);
+        assert_eq!(
+            resp.headers()
+                .get("Upload-Offset")
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            test_data.len().to_string().as_str()
+        );
+        let new_info = state
+            .info_storage
+            .get_info(file.id.clone().as_str())
+            .await
+            .unwrap();
+        assert_eq!(new_info.offset, test_data.len());
+        assert_eq!(new_info.deferred_size, false);
+        assert_eq!(new_info.length, Some(20));
+    }
+
+    #[actix_rt::test]
+    /// Tests that if new file length
+    /// is less than current offset, error is thrown.
+    async fn new_file_length_lt_offset() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = None;
+        file.deferred_size = true;
+        file.offset = 30;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let test_data = "memes";
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Upload-Length", "20"))
+            .set_payload(test_data)
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CONFLICT);
+    }
+
+    #[actix_rt::test]
+    /// Tests if user tries to update
+    /// file length with known length,
+    /// error is thrown.
+    async fn new_file_length_size_already_known() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = Some(100);
+        file.deferred_size = false;
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let test_data = "memes";
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Upload-Length", "120"))
+            .set_payload(test_data)
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+    }
+
+    #[actix_rt::test]
+    /// Checks that if Content-Type header missing,
+    /// wrong status code is returned.
+    async fn no_content_header() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = Some(100);
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Upload-Offset", "0"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method will return error if no offset header specified.
+    async fn no_offset_header() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = Some(100);
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method will return error if wrong offset is passed.
+    async fn wrong_offset_header() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.length = Some(100);
+        file.offset = 0;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Upload-Offset", "1"))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CONFLICT);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method would return error if file was already uploaded.
+    async fn final_upload() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.is_final = true;
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method would return 404 if file was saved in other storage.
+    async fn wrong_storage() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.storage = "unknown".into();
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method won't allow you to update
+    /// file if it's offset already equal to length.
+    async fn frozen_file() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file = state.create_test_file().await;
+        file.offset = 10;
+        file.length = Some(10);
+        state.info_storage.set_info(&file, false).await.unwrap();
+        let request = TestRequest::patch()
+            .uri(state.config.file_url(file.id.as_str()).as_str())
+            .insert_header(("Upload-Offset", file.offset))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+    }
+
+    #[actix_rt::test]
+    /// Tests that method will return 404 if
+    /// unknown file_id is passed.
+    async fn unknown_file_id() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::patch()
+            .uri(state.config.file_url("unknown").as_str())
+            .insert_header(("Upload-Offset", "0"))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload("memes")
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+    }
+}
diff --git a/src/protocol/creation/mod.rs b/src/protocol/creation/mod.rs
index 2ecaf127e9de83ce0806fd89374eb44ffd704614..eede35da9d24fcdbea6fba7bd08fb482669496a3 100644
--- a/src/protocol/creation/mod.rs
+++ b/src/protocol/creation/mod.rs
@@ -1,18 +1,16 @@
 use actix_web::{guard, web};
-
-use crate::RustusConf;
-
 mod routes;
 
 /// Add creation extensions.
 ///
 /// This extension allows you
 /// to create file before sending data.
-pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &RustusConf) {
+#[cfg_attr(coverage, no_coverage)]
+pub fn add_extension(web_app: &mut web::ServiceConfig) {
     web_app.service(
         // Post /base
         // URL for creating files.
-        web::resource(app_conf.base_url().as_str())
+        web::resource("")
             .name("creation:create_file")
             .guard(guard::Post())
             .to(routes::create_file),
diff --git a/src/protocol/creation/routes.rs b/src/protocol/creation/routes.rs
index 21c710d8e45f515339ff33185218ffca42e5d8b4..20df718620cc9b1a40aa2ddba47c3896cd462dff 100644
--- a/src/protocol/creation/routes.rs
+++ b/src/protocol/creation/routes.rs
@@ -30,7 +30,7 @@ fn get_metadata(request: &HttpRequest) -> Option<HashMap<String, String>> {
         .map(|header_string| {
             let mut meta_map = HashMap::new();
             for meta_pair in header_string.split(',') {
-                let mut split = meta_pair.split(' ');
+                let mut split = meta_pair.trim().split(' ');
                 let key = split.next();
                 let b64val = split.next();
                 if key.is_none() || b64val.is_none() {
@@ -55,7 +55,7 @@ fn get_upload_parts(request: &HttpRequest) -> Vec<String> {
     let urls = header_str.strip_prefix("final;").unwrap();
 
     urls.split(' ')
-        .filter_map(|val: &str| val.split('/').last().map(String::from))
+        .filter_map(|val: &str| val.trim().split('/').last().map(String::from))
         .filter(|val| val.trim() != "")
         .collect()
 }
@@ -171,9 +171,6 @@ pub async fn create_file(
         }
     }
 
-    // Create upload URL for this file.
-    let upload_url = request.url_for("core:write_bytes", &[file_info.id.clone()])?;
-
     // Checking if creation-with-upload extension is enabled.
     let with_upload = state
         .config
@@ -181,15 +178,14 @@ pub async fn create_file(
         .contains(&Extensions::CreationWithUpload);
     if with_upload && !bytes.is_empty() && !(concat_ext && is_final) {
         let octet_stream = |val: &str| val == "application/offset+octet-stream";
-        if !check_header(&request, "Content-Type", octet_stream) {
-            return Ok(HttpResponse::BadRequest().finish());
+        if check_header(&request, "Content-Type", octet_stream) {
+            // Writing first bytes.
+            state
+                .data_storage
+                .add_bytes(&file_info, bytes.as_ref())
+                .await?;
+            file_info.offset += bytes.len();
         }
-        // Writing first bytes.
-        state
-            .data_storage
-            .add_bytes(&file_info, bytes.as_ref())
-            .await?;
-        file_info.offset += bytes.len();
     }
 
     state.info_storage.set_info(&file_info, true).await?;
@@ -211,8 +207,299 @@ pub async fn create_file(
         });
     }
 
+    // Create upload URL for this file.
+    let upload_url = request.url_for("core:write_bytes", &[file_info.id.clone()])?;
+
     Ok(HttpResponse::Created()
         .insert_header(("Location", upload_url.as_str()))
         .insert_header(("Upload-Offset", file_info.offset.to_string()))
         .finish())
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::server::rustus_service;
+    use crate::State;
+    use actix_web::http::StatusCode;
+    use actix_web::test::{call_service, init_service, TestRequest};
+    use actix_web::{web, App};
+
+    #[actix_rt::test]
+    async fn success() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert_eq!(file_info.offset, 0);
+    }
+
+    #[actix_rt::test]
+    async fn success_with_bytes() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let test_data = "memes";
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header(("Content-Type", "application/offset+octet-stream"))
+            .set_payload(web::Bytes::from(test_data))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert_eq!(file_info.offset, test_data.len());
+    }
+
+    #[actix_rt::test]
+    async fn with_bytes_wrong_content_type() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let test_data = "memes";
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header(("Content-Type", "random"))
+            .set_payload(web::Bytes::from(test_data))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert_eq!(file_info.offset, 0);
+    }
+
+    #[actix_rt::test]
+    async fn success_defer_size() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Defer-Length", "1"))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, None);
+        assert!(file_info.deferred_size);
+    }
+
+    #[actix_rt::test]
+    async fn success_partial_upload() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header(("Upload-Concat", "partial"))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert!(file_info.is_partial);
+        assert_eq!(file_info.is_final, false);
+    }
+
+    #[actix_rt::test]
+    async fn success_final_upload() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut part1 = state.create_test_file().await;
+        let mut part2 = state.create_test_file().await;
+        part1.is_partial = true;
+        part1.length = Some(100);
+        part1.offset = 100;
+
+        part2.is_partial = true;
+        part2.length = Some(100);
+        part2.offset = 100;
+
+        state.info_storage.set_info(&part1, false).await.unwrap();
+        state.info_storage.set_info(&part2, false).await.unwrap();
+
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header((
+                "Upload-Concat",
+                format!("final;/files/{} /files/{}", part1.id, part2.id),
+            ))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(200));
+        assert!(file_info.is_final);
+    }
+
+    #[actix_rt::test]
+    async fn success_with_metadata() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header((
+                "Upload-Metadata",
+                format!(
+                    "test {}, pest {}",
+                    base64::encode("data1"),
+                    base64::encode("data2")
+                ),
+            ))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert_eq!(file_info.metadata.get("test").unwrap(), "data1");
+        assert_eq!(file_info.metadata.get("pest").unwrap(), "data2");
+        assert_eq!(file_info.offset, 0);
+    }
+
+    #[actix_rt::test]
+    async fn success_with_metadata_wrong_encoding() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .insert_header(("Upload-Length", 100))
+            .insert_header((
+                "Upload-Metadata",
+                format!("test data1, pest {}", base64::encode("data")),
+            ))
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::CREATED);
+        // Getting file from location header.
+        let item_id = resp
+            .headers()
+            .get("Location")
+            .unwrap()
+            .to_str()
+            .unwrap()
+            .split('/')
+            .last()
+            .unwrap();
+        let file_info = state.info_storage.get_info(item_id).await.unwrap();
+        assert_eq!(file_info.length, Some(100));
+        assert!(file_info.metadata.get("test").is_none());
+        assert_eq!(file_info.metadata.get("pest").unwrap(), "data");
+        assert_eq!(file_info.offset, 0);
+    }
+
+    #[actix_rt::test]
+    async fn no_length_header() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::post()
+            .uri(state.config.base_url().as_str())
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+    }
+}
diff --git a/src/protocol/getting/mod.rs b/src/protocol/getting/mod.rs
index e80e4485069e0aff5dddb0669711877ed9c77644..7b9184768e9612264c47cfff7efe252b364f9e3b 100644
--- a/src/protocol/getting/mod.rs
+++ b/src/protocol/getting/mod.rs
@@ -1,7 +1,5 @@
 use actix_web::{guard, web};
 
-use crate::RustusConf;
-
 mod routes;
 
 /// Add getting extension.
@@ -10,10 +8,11 @@ mod routes;
 /// to get uploaded file.
 ///
 /// This is unofficial extension.
-pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &RustusConf) {
+#[cfg_attr(coverage, no_coverage)]
+pub fn add_extension(web_app: &mut web::ServiceConfig) {
     web_app.service(
         // GET /base/file
-        web::resource(app_conf.file_url().as_str())
+        web::resource("{file_id}")
             .name("getting:get")
             .guard(guard::Get())
             .to(routes::get_file),
diff --git a/src/protocol/getting/routes.rs b/src/protocol/getting/routes.rs
index 97af31a183dba4a927d2b064c5abb9098d75aad8..e3e7c0a59973d727c828186fe153830593f59491 100644
--- a/src/protocol/getting/routes.rs
+++ b/src/protocol/getting/routes.rs
@@ -1,12 +1,13 @@
-use actix_web::{web, HttpRequest, Responder};
+use actix_files::NamedFile;
+use actix_web::{web, HttpRequest};
 
 use crate::errors::RustusError;
-use crate::State;
+use crate::{RustusResult, State};
 
 /// Retrieve actual file.
 ///
 /// This method allows you to download files directly from storage.
-pub async fn get_file(request: HttpRequest, state: web::Data<State>) -> impl Responder {
+pub async fn get_file(request: HttpRequest, state: web::Data<State>) -> RustusResult<NamedFile> {
     let file_id_opt = request.match_info().get("file_id").map(String::from);
     if let Some(file_id) = file_id_opt {
         let file_info = state.info_storage.get_info(file_id.as_str()).await?;
@@ -18,3 +19,67 @@ pub async fn get_file(request: HttpRequest, state: web::Data<State>) -> impl Res
         Err(RustusError::FileNotFound)
     }
 }
+
+#[cfg(test)]
+#[cfg_attr(coverage, no_coverage)]
+mod test {
+    use crate::{rustus_service, State};
+    use actix_web::http::StatusCode;
+    use actix_web::test::{call_service, init_service, TestRequest};
+    use actix_web::{web, App};
+
+    #[actix_rt::test]
+    async fn success() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let file_info = state.create_test_file().await;
+        state
+            .data_storage
+            .add_bytes(&file_info, "data".as_bytes())
+            .await
+            .unwrap();
+        let request = TestRequest::get()
+            .uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert!(resp.status().is_success());
+    }
+
+    #[actix_rt::test]
+    async fn unknown_file_id() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::get()
+            .uri(state.config.file_url("random_str").as_str())
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+    }
+
+    #[actix_rt::test]
+    async fn unknown_storage() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.storage = "unknown_storage".into();
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::get()
+            .uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .to_request();
+        let resp = call_service(&mut rustus, request).await;
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+    }
+}
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
index ecd6dba3bf8798e97d9337c86210cc8fe72c848c..6ac9a7b2739dbe2708a550f0ea09f3ebd008b814 100644
--- a/src/protocol/mod.rs
+++ b/src/protocol/mod.rs
@@ -12,20 +12,21 @@ mod termination;
 ///
 /// This function resolves all protocol extensions
 /// provided by CLI into services and adds it to the application.
+#[cfg_attr(coverage, no_coverage)]
 pub fn setup(app_conf: RustusConf) -> Box<dyn Fn(&mut web::ServiceConfig)> {
     Box::new(move |web_app| {
         for extension in app_conf.extensions_vec() {
             match extension {
-                extensions::Extensions::Creation => creation::add_extension(web_app, &app_conf),
+                extensions::Extensions::Creation => creation::add_extension(web_app),
                 extensions::Extensions::Termination => {
-                    termination::add_extension(web_app, &app_conf);
+                    termination::add_extension(web_app);
                 }
                 extensions::Extensions::Getting => {
-                    getting::add_extension(web_app, &app_conf);
+                    getting::add_extension(web_app);
                 }
                 _ => {}
             }
         }
-        core::add_extension(web_app, &app_conf);
+        core::add_extension(web_app);
     })
 }
diff --git a/src/protocol/termination/mod.rs b/src/protocol/termination/mod.rs
index 3774fff828fd9835234041dfa8767e15bbc1a85f..bc3e23b2744b5351849126d371fcf1728dccad0a 100644
--- a/src/protocol/termination/mod.rs
+++ b/src/protocol/termination/mod.rs
@@ -1,17 +1,16 @@
 use actix_web::{guard, web};
 
-use crate::RustusConf;
-
 mod routes;
 
 /// Add termination extension.
 ///
 /// This extension allows you
 /// to terminate file upload.
-pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &RustusConf) {
+#[cfg_attr(coverage, no_coverage)]
+pub fn add_extension(web_app: &mut web::ServiceConfig) {
     web_app.service(
         // DELETE /base/file
-        web::resource(app_conf.file_url().as_str())
+        web::resource("{file_id}")
             .name("termination:terminate")
             .guard(guard::Delete())
             .to(routes::terminate),
diff --git a/src/protocol/termination/routes.rs b/src/protocol/termination/routes.rs
index 8e70f9d7539d46aadf33beac11697518412a683d..82ae88e6c3ca51aede0518ed8e31c91f7f71b2fc 100644
--- a/src/protocol/termination/routes.rs
+++ b/src/protocol/termination/routes.rs
@@ -1,6 +1,6 @@
 use actix_web::{web, HttpRequest, HttpResponse};
 
-use crate::errors::RustusResult;
+use crate::errors::{RustusError, RustusResult};
 use crate::notifiers::Hook;
 use crate::State;
 
@@ -16,7 +16,7 @@ pub async fn terminate(
     if let Some(file_id) = file_id_opt {
         let file_info = state.info_storage.get_info(file_id.as_str()).await?;
         if file_info.storage != state.data_storage.to_string() {
-            return Ok(HttpResponse::NotFound().finish());
+            return Err(RustusError::FileNotFound);
         }
         state.info_storage.remove_info(file_id.as_str()).await?;
         state.data_storage.remove_file(&file_info).await?;
@@ -37,3 +37,68 @@ pub async fn terminate(
     }
     Ok(HttpResponse::NoContent().finish())
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::{rustus_service, State};
+    use actix_web::http::StatusCode;
+    use actix_web::test::{call_service, init_service, TestRequest};
+    use actix_web::{web, App};
+    use std::path::PathBuf;
+
+    #[actix_rt::test]
+    async fn success() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let file_info = state.create_test_file().await;
+        let request = TestRequest::delete()
+            .uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(response.status(), StatusCode::NO_CONTENT);
+        assert!(state
+            .info_storage
+            .get_info(file_info.id.as_str())
+            .await
+            .is_err());
+        assert!(!PathBuf::from(file_info.path.unwrap()).exists());
+    }
+
+    #[actix_rt::test]
+    async fn unknown_file_id() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let request = TestRequest::delete()
+            .param("file_id", "not_exists")
+            .to_request();
+        let result = call_service(&mut rustus, request).await;
+        assert_eq!(result.status(), StatusCode::NOT_FOUND);
+    }
+
+    #[actix_rt::test]
+    async fn wrong_storage() {
+        let state = State::test_new().await;
+        let mut rustus = init_service(
+            App::new().configure(rustus_service(web::Data::new(state.test_clone().await))),
+        )
+        .await;
+        let mut file_info = state.create_test_file().await;
+        file_info.storage = "unknown_storage".into();
+        state
+            .info_storage
+            .set_info(&file_info, false)
+            .await
+            .unwrap();
+        let request = TestRequest::delete()
+            .uri(state.config.file_url(file_info.id.as_str()).as_str())
+            .to_request();
+        let response = call_service(&mut rustus, request).await;
+        assert_eq!(response.status(), StatusCode::NOT_FOUND);
+    }
+}
diff --git a/src/routes.rs b/src/routes.rs
index d7310952a278edaff27b0d6e626dfa5332cdab7e..9dca6f6662765707b129ff720e2589c130d1a711 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -6,6 +6,7 @@ use crate::errors::{RustusError, RustusResult};
 /// All protocol urls can be found
 /// at `crate::protocol::*`.
 #[allow(clippy::unused_async)]
+#[cfg_attr(coverage, no_coverage)]
 pub async fn not_found() -> RustusResult<HttpResponse> {
     Err(RustusError::FileNotFound)
 }
diff --git a/src/server.rs b/src/server.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b688e88e56efa212bab91fa12605aa40f01b13a6
--- /dev/null
+++ b/src/server.rs
@@ -0,0 +1,20 @@
+use crate::{protocol, State};
+use actix_web::web::PayloadConfig;
+use actix_web::{middleware, web};
+
+pub fn rustus_service(state: web::Data<State>) -> Box<dyn Fn(&mut web::ServiceConfig)> {
+    Box::new(move |web_app| {
+        web_app.service(
+            web::scope(state.config.base_url().as_str())
+                .app_data(state.clone())
+                .app_data(PayloadConfig::new(state.config.max_body_size))
+                // Main middleware that appends TUS headers.
+                .wrap(
+                    middleware::DefaultHeaders::new()
+                        .add(("Tus-Resumable", "1.0.0"))
+                        .add(("Tus-Version", "1.0.0")),
+                )
+                .configure(protocol::setup(state.config.clone())),
+        );
+    })
+}
diff --git a/src/state.rs b/src/state.rs
index 16bc4f24ab2bab10450226a85b8e6c2d8c7faef8..07de07387dbf81c457e8f9ecb202ae7a45981c3d 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -1,3 +1,5 @@
+#[cfg(test)]
+use crate::info_storages::FileInfo;
 use crate::{InfoStorage, NotificationManager, RustusConf, Storage};
 
 pub struct State {
@@ -21,4 +23,58 @@ impl State {
             notification_manager,
         }
     }
+
+    #[cfg(test)]
+    pub async fn from_config(config: RustusConf) -> Self {
+        Self {
+            config: config.clone(),
+            data_storage: Box::new(crate::storages::file_storage::FileStorage::new(
+                config.storage_opts.data_dir.clone(),
+                config.storage_opts.dir_structure.clone(),
+            )),
+            info_storage: Box::new(
+                crate::info_storages::file_info_storage::FileInfoStorage::new(
+                    config.info_storage_opts.info_dir.clone(),
+                ),
+            ),
+            notification_manager: NotificationManager::new(&config).await.unwrap(),
+        }
+    }
+
+    #[cfg(test)]
+    pub async fn test_new() -> Self {
+        let data_dir = tempdir::TempDir::new("data_dir").unwrap();
+        let info_dir = tempdir::TempDir::new("info_dir").unwrap();
+        let config = RustusConf::from_iter(
+            vec![
+                "rustus",
+                "--data-dir",
+                data_dir.into_path().to_str().unwrap(),
+                "--info-dir",
+                info_dir.into_path().to_str().unwrap(),
+            ]
+            .into_iter(),
+        );
+        Self::from_config(config).await
+    }
+
+    #[cfg(test)]
+    pub async fn test_clone(&self) -> Self {
+        let config = self.config.clone();
+        Self::from_config(config).await
+    }
+
+    #[cfg(test)]
+    pub async fn create_test_file(&self) -> FileInfo {
+        let mut new_file = FileInfo::new(
+            uuid::Uuid::new_v4().to_string().as_str(),
+            Some(10),
+            None,
+            self.data_storage.to_string(),
+            None,
+        );
+        new_file.path = Some(self.data_storage.create_file(&new_file).await.unwrap());
+        self.info_storage.set_info(&new_file, true).await.unwrap();
+        new_file
+    }
 }
diff --git a/src/storages/file_storage.rs b/src/storages/file_storage.rs
index 12d8b15ec0c5f4ce95c2c618bac85c97fc8ea863..ccc699c0c70ab37c155b340a412352224972f053 100644
--- a/src/storages/file_storage.rs
+++ b/src/storages/file_storage.rs
@@ -10,24 +10,26 @@ use log::error;
 use crate::errors::{RustusError, RustusResult};
 use crate::info_storages::FileInfo;
 use crate::storages::Storage;
-use crate::RustusConf;
+use crate::utils::dir_struct::dir_struct;
 use derive_more::Display;
 
 #[derive(Display)]
 #[display(fmt = "file_storage")]
 pub struct FileStorage {
-    app_conf: RustusConf,
+    data_dir: PathBuf,
+    dir_struct: String,
 }
 
 impl FileStorage {
-    pub fn new(app_conf: RustusConf) -> FileStorage {
-        FileStorage { app_conf }
+    pub fn new(data_dir: PathBuf, dir_struct: String) -> FileStorage {
+        FileStorage {
+            data_dir,
+            dir_struct,
+        }
     }
 
     pub async fn data_file_path(&self, file_id: &str) -> RustusResult<PathBuf> {
         let dir = self
-            .app_conf
-            .storage_opts
             .data_dir
             // We're working wit absolute paths, because tus.io says so.
             .canonicalize()
@@ -35,7 +37,7 @@ impl FileStorage {
                 error!("{}", err);
                 RustusError::UnableToWrite(err.to_string())
             })?
-            .join(self.app_conf.dir_struct().as_str());
+            .join(dir_struct(self.dir_struct.as_str()));
         DirBuilder::new()
             .recursive(true)
             .create(dir.as_path())
@@ -44,7 +46,7 @@ impl FileStorage {
                 error!("{}", err);
                 RustusError::UnableToWrite(err.to_string())
             })?;
-        Ok(dir.join(file_id.to_string()))
+        Ok(dir.join(file_id))
     }
 }
 
@@ -53,10 +55,10 @@ impl Storage for FileStorage {
     async fn prepare(&mut self) -> RustusResult<()> {
         // We're creating directory for new files
         // if it doesn't already exist.
-        if !self.app_conf.storage_opts.data_dir.exists() {
+        if !self.data_dir.exists() {
             DirBuilder::new()
                 .recursive(true)
-                .create(self.app_conf.storage_opts.data_dir.as_path())
+                .create(self.data_dir.as_path())
                 .await
                 .map_err(|err| RustusError::UnableToPrepareStorage(err.to_string()))?;
         }
@@ -137,8 +139,7 @@ impl Storage for FileStorage {
         let mut file = OpenOptions::new()
             .write(true)
             .append(true)
-            .create(false)
-            .create_new(false)
+            .create(true)
             .open(file_info.path.as_ref().unwrap().clone())
             .await
             .map_err(|err| {
@@ -169,3 +170,151 @@ impl Storage for FileStorage {
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::FileStorage;
+    use crate::info_storages::FileInfo;
+    use crate::Storage;
+    use std::fs::File;
+    use std::io::{Read, Write};
+    use std::path::PathBuf;
+
+    #[actix_rt::test]
+    async fn preparation() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let target_path = dir.into_path().join("not_exist");
+        let mut storage = FileStorage::new(target_path.clone(), "".into());
+        assert_eq!(target_path.exists(), false);
+        storage.prepare().await.unwrap();
+        assert_eq!(target_path.exists(), true);
+    }
+
+    #[actix_rt::test]
+    async fn create_file() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+        let file_info = FileInfo::new("test_id", Some(5), None, storage.to_string(), None);
+        let new_path = storage.create_file(&file_info).await.unwrap();
+        assert!(PathBuf::from(new_path).exists());
+    }
+
+    #[actix_rt::test]
+    async fn create_file_but_it_exists() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let base_path = dir.into_path().clone();
+        let storage = FileStorage::new(base_path.clone(), "".into());
+        let file_info = FileInfo::new("test_id", Some(5), None, storage.to_string(), None);
+        File::create(base_path.join("test_id")).unwrap();
+        let result = storage.create_file(&file_info).await;
+        assert!(result.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn adding_bytes() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+        let mut file_info = FileInfo::new("test_id", Some(5), None, storage.to_string(), None);
+        let new_path = storage.create_file(&file_info).await.unwrap();
+        let test_data = "MyTestData";
+        file_info.path = Some(new_path.clone());
+        storage
+            .add_bytes(&file_info, test_data.as_bytes())
+            .await
+            .unwrap();
+        let mut file = File::open(new_path).unwrap();
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).unwrap();
+        assert_eq!(contents, String::from(test_data))
+    }
+
+    #[actix_rt::test]
+    async fn adding_bytes_to_unknown_file() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+        let file_info = FileInfo::new(
+            "test_id",
+            Some(5),
+            Some(String::from("some_file")),
+            storage.to_string(),
+            None,
+        );
+        let test_data = "MyTestData";
+        let result = storage.add_bytes(&file_info, test_data.as_bytes()).await;
+        assert!(result.is_err())
+    }
+
+    #[actix_rt::test]
+    async fn get_contents_of_unknown_file() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+        let file_info = FileInfo::new(
+            "test_id",
+            Some(5),
+            Some(storage.data_dir.join("unknown").display().to_string()),
+            storage.to_string(),
+            None,
+        );
+        let file_info = storage.get_contents(&file_info).await;
+        assert!(file_info.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn remove_unknown_file() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+        let file_info = FileInfo::new(
+            "test_id",
+            Some(5),
+            Some(storage.data_dir.join("unknown").display().to_string()),
+            storage.to_string(),
+            None,
+        );
+        let file_info = storage.remove_file(&file_info).await;
+        assert!(file_info.is_err());
+    }
+
+    #[actix_rt::test]
+    async fn success_concatenation() {
+        let dir = tempdir::TempDir::new("file_storage").unwrap();
+        let storage = FileStorage::new(dir.into_path().clone(), "".into());
+
+        let mut parts = Vec::new();
+        let part1_path = storage.data_dir.as_path().join("part1");
+        let mut part1 = File::create(part1_path.clone()).unwrap();
+        let size1 = part1.write("hello ".as_bytes()).unwrap();
+
+        parts.push(FileInfo::new(
+            "part_id1",
+            Some(size1),
+            Some(part1_path.display().to_string()),
+            storage.to_string(),
+            None,
+        ));
+
+        let part2_path = storage.data_dir.as_path().join("part2");
+        let mut part2 = File::create(part2_path.clone()).unwrap();
+        let size2 = part2.write("world".as_bytes()).unwrap();
+        parts.push(FileInfo::new(
+            "part_id2",
+            Some(size2),
+            Some(part2_path.display().to_string()),
+            storage.to_string(),
+            None,
+        ));
+
+        let final_info = FileInfo::new(
+            "final_id",
+            None,
+            Some(storage.data_dir.join("final_info").display().to_string()),
+            storage.to_string(),
+            None,
+        );
+        storage.concat_files(&final_info, parts).await.unwrap();
+        let mut final_file = File::open(final_info.path.unwrap()).unwrap();
+        let mut buffer = String::new();
+        final_file.read_to_string(&mut buffer).unwrap();
+
+        assert_eq!(buffer.as_str(), "hello world");
+    }
+}
diff --git a/src/storages/models/available_stores.rs b/src/storages/models/available_stores.rs
index c4f8d1b788a84fecb0bf29cd00bd89944a2c5761..7bde0987c00c335302068b56c9194631606b82ca 100644
--- a/src/storages/models/available_stores.rs
+++ b/src/storages/models/available_stores.rs
@@ -19,10 +19,14 @@ impl AvailableStores {
     /// `config` - Rustus configuration.
     /// `info_storage` - Storage for information about files.
     ///
+    #[cfg_attr(coverage, no_coverage)]
     pub fn get(&self, config: &RustusConf) -> Box<dyn Storage + Send + Sync> {
         #[allow(clippy::single_match)]
         match self {
-            Self::FileStorage => Box::new(file_storage::FileStorage::new(config.clone())),
+            Self::FileStorage => Box::new(file_storage::FileStorage::new(
+                config.storage_opts.data_dir.clone(),
+                config.storage_opts.dir_structure.clone(),
+            )),
         }
     }
 }
diff --git a/src/utils/dir_struct.rs b/src/utils/dir_struct.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9c1875729d0e960a24f4dc4de6f23405c83bc578
--- /dev/null
+++ b/src/utils/dir_struct.rs
@@ -0,0 +1,50 @@
+use chrono::{Datelike, Timelike};
+use lazy_static::lazy_static;
+use log::error;
+use std::collections::HashMap;
+use std::env;
+
+lazy_static! {
+    /// Freezing ENVS on startup.
+    static ref ENV_MAP: HashMap<String, String> = {
+        let mut m = HashMap::new();
+        for (key, value) in env::vars() {
+            m.insert(format!("env[{}]", key), value);
+        }
+        m
+    };
+}
+
+/// Generate directory name with user template.
+pub fn dir_struct(dir_structure: &str) -> String {
+    let now = chrono::Utc::now();
+    let mut vars: HashMap<String, String> = ENV_MAP.clone();
+    vars.insert("day".into(), now.day().to_string());
+    vars.insert("month".into(), now.month().to_string());
+    vars.insert("year".into(), now.year().to_string());
+    vars.insert("hour".into(), now.hour().to_string());
+    vars.insert("minute".into(), now.minute().to_string());
+    strfmt::strfmt(dir_structure, &vars).unwrap_or_else(|err| {
+        error!("{}", err);
+        "".into()
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::dir_struct;
+    use chrono::Datelike;
+
+    #[test]
+    pub fn test_time() {
+        let now = chrono::Utc::now();
+        let dir = dir_struct("{day}/{month}");
+        assert_eq!(dir, format!("{}/{}", now.day(), now.month()));
+    }
+
+    #[test]
+    pub fn test_unknown_var() {
+        let dir = dir_struct("test/{quake}");
+        assert_eq!(dir, String::from(""));
+    }
+}
diff --git a/src/utils/enums.rs b/src/utils/enums.rs
index 5109aeef353f609c345e4b70dde0a42ea0477ad3..4d90861881afddf73fe6c605627da289d9e1f3f1 100644
--- a/src/utils/enums.rs
+++ b/src/utils/enums.rs
@@ -27,3 +27,32 @@ macro_rules! from_str {
         }
     };
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::from_str;
+    use derive_more::{Display, From};
+    use strum::EnumIter;
+
+    #[derive(PartialEq, Debug, Display, EnumIter, From, Clone, Eq)]
+    pub enum TestEnum {
+        #[display(fmt = "test-val-1")]
+        TestVal1,
+        #[display(fmt = "test-val-2")]
+        TestVal2,
+    }
+
+    from_str!(TestEnum, "test-vals");
+
+    #[test]
+    fn test_from_str_unknown_val() {
+        let result = TestEnum::from_str("unknown");
+        assert!(result.is_err())
+    }
+
+    #[test]
+    fn test_from_str() {
+        let result = TestEnum::from_str("test-val-1");
+        assert_eq!(result.unwrap(), TestEnum::TestVal1)
+    }
+}
diff --git a/src/utils/headers.rs b/src/utils/headers.rs
index f2b0c42aaa214275c9b845b6bf1dde4b9b118f02..493b09ab79571000e5661ef8ad9db44346c3c4ba 100644
--- a/src/utils/headers.rs
+++ b/src/utils/headers.rs
@@ -41,3 +41,52 @@ pub fn check_header(request: &HttpRequest, header_name: &str, expr: fn(&str) ->
         })
         .unwrap_or(false)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::{check_header, parse_header};
+    use actix_web::test::TestRequest;
+
+    #[actix_rt::test]
+    async fn test_parse_header_unknown_header() {
+        let request = TestRequest::get().to_http_request();
+        let header = parse_header::<String>(&request, "unknown");
+        assert!(header.is_none());
+    }
+
+    #[actix_rt::test]
+    async fn test_parse_header_wrong_type() {
+        let request = TestRequest::get()
+            .insert_header(("test_header", String::from("test").as_bytes()))
+            .to_http_request();
+        let header = parse_header::<i32>(&request, "test_header");
+        assert!(header.is_none());
+    }
+
+    #[actix_rt::test]
+    async fn test_parse_header() {
+        let request = TestRequest::get()
+            .insert_header(("test_header", String::from("123").as_bytes()))
+            .to_http_request();
+        let header = parse_header::<usize>(&request, "test_header");
+        assert_eq!(header.unwrap(), 123);
+    }
+
+    #[actix_rt::test]
+    async fn test_check_header_unknown_header() {
+        let request = TestRequest::get().to_http_request();
+        let check = check_header(&request, "unknown", |value| value == "1");
+        assert_eq!(check, false);
+    }
+
+    #[actix_rt::test]
+    async fn test_check_header() {
+        let request = TestRequest::get()
+            .insert_header(("test_header", "1"))
+            .to_http_request();
+        let check = check_header(&request, "test_header", |value| value == "1");
+        assert!(check);
+        let check = check_header(&request, "test_header", |value| value == "2");
+        assert!(!check);
+    }
+}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 0f6b3005ddba8a142e59aa63b83f8ba61de6af87..0bef5e255fa47a6f53bdba0c498862fee1325665 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,2 +1,3 @@
+pub mod dir_struct;
 pub mod enums;
 pub mod headers;