diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8efadc91cda45cf7e7714b59a042f9a82e9c1028..cd06272d937f09989f48cacde2eb67761be9c214 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,25 +16,36 @@ workflow:
 # Pipeline start
 # =============================================================================
 
+# Run the unit tests
+testing:
+  image: node:22-alpine
+  stage: validate
+  rules:
+    - changes:
+        - src/**/*
+  script:
+    - npm ci
+    - npm test
+
 # Validate the fasta files if they have changed
 validate-fasta-files:
+  image: node:22-alpine
   stage: validate
   rules:
     - changes:
         - data/*.fasta
-  image: node:22-alpine
   script:
     - npm run validate
 
 # Build the archive of the fasta files and compute the stats.
 # Commit the files in the data folder, triggering another CI.
 build-archive-and-stats:
+  image: node:22-alpine
   stage: archive
   rules:
     - if: $CI_COMMIT_BRANCH == "master"
       changes:
         - data/*.fasta
-  image: node:22-alpine
   script:
     - export CURRENT_DATE=$(date +'%Y-%m-%d')
     - export ARCHIVE_PATH=data/${CURRENT_DATE}.tar.gz
@@ -54,10 +65,10 @@ build-archive-and-stats:
 
 # Build the docker image and store it the gitlab registery.
 build-docker-image:
+  image: docker:24
   stage: build
   rules:
     - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev"
-  image: docker:24
   interruptible: true
   script:
     - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
@@ -68,10 +79,10 @@ build-docker-image:
 # Fetch the image of MongoDB from the DockerHub and store it if there is any changes.
 # This avoid issues with Docker in case of overpulling.
 fetch-mongo-image:
+  image: docker:24
   stage: build
   rules:
     - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev"
-  image: docker:24
   interruptible: true
   script:
     - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
@@ -81,8 +92,8 @@ fetch-mongo-image:
 
 # Base stage for deploy-dev and deploy-prod
 .deploy:
-  stage: deploy
   image: registry-gitlab.pasteur.fr/dsi-tools/docker-images:docker_kubernetes_image
+  stage: deploy
   interruptible: true
   script:
     - kubectl delete secret registry-gitlab -n ${NAMESPACE} --ignore-not-found=true
diff --git a/package-lock.json b/package-lock.json
index d34f7fd74896711b37ea3b54987946af47961782..7b789b2a249c7ec5d60c55fd54a3c6c43281fa32 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
         "fastify": "^5.2.1",
         "modern-normalize": "^3.0.1",
         "mongodb": "^6.13.0",
-        "pinia": "^2.3.1",
+        "pinia": "^3.0.1",
         "plotly.js-dist-min": "^3.0.0",
         "qs": "^6.14.0",
         "vue": "^3.5.13",
@@ -24,7 +24,7 @@
       },
       "devDependencies": {
         "@biomejs/biome": "^1.9.4",
-        "@types/node": "^22.13.0",
+        "@types/node": "^22.13.4",
         "@types/plotly.js-dist-min": "^2.3.4",
         "@types/qs": "^6.9.18",
         "@vitejs/plugin-vue": "^5.2.1",
@@ -36,7 +36,8 @@
         "rollup-plugin-brotli": "^3.1.0",
         "rollup-plugin-gzip": "^4.0.1",
         "typescript": "^5.7.3",
-        "vite": "^6.0.11",
+        "vite": "^6.1.0",
+        "vitest": "^3.0.5",
         "vue-tsc": "^2.2.0"
       }
     },
@@ -910,9 +911,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
-      "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz",
+      "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==",
       "cpu": [
         "arm"
       ],
@@ -924,9 +925,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
-      "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz",
+      "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==",
       "cpu": [
         "arm64"
       ],
@@ -938,9 +939,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
-      "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz",
+      "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==",
       "cpu": [
         "arm64"
       ],
@@ -952,9 +953,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
-      "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz",
+      "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==",
       "cpu": [
         "x64"
       ],
@@ -966,9 +967,9 @@
       ]
     },
     "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
-      "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz",
+      "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==",
       "cpu": [
         "arm64"
       ],
@@ -980,9 +981,9 @@
       ]
     },
     "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
-      "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz",
+      "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==",
       "cpu": [
         "x64"
       ],
@@ -994,9 +995,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
-      "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz",
+      "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==",
       "cpu": [
         "arm"
       ],
@@ -1008,9 +1009,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
-      "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz",
+      "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==",
       "cpu": [
         "arm"
       ],
@@ -1022,9 +1023,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
-      "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz",
+      "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==",
       "cpu": [
         "arm64"
       ],
@@ -1036,9 +1037,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
-      "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz",
+      "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==",
       "cpu": [
         "arm64"
       ],
@@ -1050,9 +1051,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
-      "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz",
+      "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==",
       "cpu": [
         "loong64"
       ],
@@ -1064,9 +1065,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
-      "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz",
+      "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==",
       "cpu": [
         "ppc64"
       ],
@@ -1078,9 +1079,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
-      "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz",
+      "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==",
       "cpu": [
         "riscv64"
       ],
@@ -1092,9 +1093,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
-      "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz",
+      "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==",
       "cpu": [
         "s390x"
       ],
@@ -1106,9 +1107,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
-      "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz",
+      "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==",
       "cpu": [
         "x64"
       ],
@@ -1120,9 +1121,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
-      "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz",
+      "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==",
       "cpu": [
         "x64"
       ],
@@ -1134,9 +1135,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
-      "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz",
+      "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==",
       "cpu": [
         "arm64"
       ],
@@ -1148,9 +1149,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
-      "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz",
+      "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==",
       "cpu": [
         "ia32"
       ],
@@ -1162,9 +1163,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
-      "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz",
+      "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==",
       "cpu": [
         "x64"
       ],
@@ -1192,9 +1193,9 @@
       "license": "MIT"
     },
     "node_modules/@types/node": {
-      "version": "22.13.0",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz",
-      "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==",
+      "version": "22.13.4",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
+      "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1252,6 +1253,129 @@
         "vue": "^3.2.25"
       }
     },
+    "node_modules/@vitest/expect": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
+      "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/spy": "3.0.5",
+        "@vitest/utils": "3.0.5",
+        "chai": "^5.1.2",
+        "tinyrainbow": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/mocker": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
+      "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/spy": "3.0.5",
+        "estree-walker": "^3.0.3",
+        "magic-string": "^0.30.17"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "msw": "^2.4.9",
+        "vite": "^5.0.0 || ^6.0.0"
+      },
+      "peerDependenciesMeta": {
+        "msw": {
+          "optional": true
+        },
+        "vite": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vitest/mocker/node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
+    "node_modules/@vitest/pretty-format": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
+      "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tinyrainbow": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/runner": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
+      "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/utils": "3.0.5",
+        "pathe": "^2.0.2"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/snapshot": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
+      "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/pretty-format": "3.0.5",
+        "magic-string": "^0.30.17",
+        "pathe": "^2.0.2"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/spy": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
+      "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tinyspy": "^3.0.2"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/utils": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
+      "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/pretty-format": "3.0.5",
+        "loupe": "^3.1.2",
+        "tinyrainbow": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
     "node_modules/@volar/language-core": {
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz",
@@ -1347,6 +1471,30 @@
       "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
       "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
     },
+    "node_modules/@vue/devtools-kit": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz",
+      "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-shared": "^7.7.2",
+        "birpc": "^0.2.19",
+        "hookable": "^5.5.3",
+        "mitt": "^3.0.1",
+        "perfect-debounce": "^1.0.0",
+        "speakingurl": "^14.0.1",
+        "superjson": "^2.2.1"
+      }
+    },
+    "node_modules/@vue/devtools-shared": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz",
+      "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==",
+      "license": "MIT",
+      "dependencies": {
+        "rfdc": "^1.4.1"
+      }
+    },
     "node_modules/@vue/language-core": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz",
@@ -1544,6 +1692,16 @@
         "node": ">= 8"
       }
     },
+    "node_modules/assertion-error": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+      "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1648,6 +1806,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/birpc": {
+      "version": "0.2.19",
+      "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
+      "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1740,6 +1907,16 @@
         "ieee754": "^1.2.1"
       }
     },
+    "node_modules/cac": {
+      "version": "6.7.14",
+      "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+      "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/call-bind-apply-helpers": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
@@ -1802,6 +1979,23 @@
       ],
       "license": "CC-BY-4.0"
     },
+    "node_modules/chai": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+      "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "assertion-error": "^2.0.1",
+        "check-error": "^2.1.1",
+        "deep-eql": "^5.0.1",
+        "loupe": "^3.1.0",
+        "pathval": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1830,6 +2024,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/check-error": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+      "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 16"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -1983,6 +2187,21 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/copy-anything": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+      "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+      "license": "MIT",
+      "dependencies": {
+        "is-what": "^4.1.8"
+      },
+      "engines": {
+        "node": ">=12.13"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mesqueeb"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2193,12 +2412,13 @@
       "license": "MIT"
     },
     "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "ms": "2.1.2"
+        "ms": "^2.1.3"
       },
       "engines": {
         "node": ">=6.0"
@@ -2209,6 +2429,16 @@
         }
       }
     },
+    "node_modules/deep-eql": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+      "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -2390,6 +2620,13 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/es-module-lexer": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
+      "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/es-object-atoms": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -2480,6 +2717,16 @@
         "node": ">=0.8.x"
       }
     },
+    "node_modules/expect-type": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
+      "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/fast-copy": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
@@ -2873,6 +3120,12 @@
       "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
       "dev": true
     },
+    "node_modules/hookable": {
+      "version": "5.5.3",
+      "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+      "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+      "license": "MIT"
+    },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -2979,6 +3232,18 @@
         "node": ">=0.12.0"
       }
     },
+    "node_modules/is-what": {
+      "version": "4.1.16",
+      "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+      "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.13"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mesqueeb"
+      }
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3063,6 +3328,13 @@
       "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
       "dev": true
     },
+    "node_modules/loupe": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
+      "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/lru-cache": {
       "version": "11.0.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
@@ -3073,9 +3345,9 @@
       }
     },
     "node_modules/magic-string": {
-      "version": "0.30.14",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
-      "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
+      "version": "0.30.17",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
       "license": "MIT",
       "dependencies": {
         "@jridgewell/sourcemap-codec": "^1.5.0"
@@ -3163,6 +3435,12 @@
         "node": ">=16 || 14 >=14.17"
       }
     },
+    "node_modules/mitt": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+      "license": "MIT"
+    },
     "node_modules/modern-normalize": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-3.0.1.tgz",
@@ -3230,10 +3508,11 @@
       }
     },
     "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/muggle-string": {
       "version": "0.4.1",
@@ -3243,15 +3522,16 @@
       "license": "MIT"
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
       "funding": [
         {
           "type": "github",
           "url": "https://github.com/sponsors/ai"
         }
       ],
+      "license": "MIT",
       "bin": {
         "nanoid": "bin/nanoid.cjs"
       },
@@ -3426,6 +3706,29 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/pathe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/pathval": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+      "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14.16"
+      }
+    },
+    "node_modules/perfect-debounce": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+      "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+      "license": "MIT"
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3445,13 +3748,12 @@
       }
     },
     "node_modules/pinia": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
-      "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.1.tgz",
+      "integrity": "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==",
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-api": "^6.6.3",
-        "vue-demi": "^0.14.10"
+        "@vue/devtools-api": "^7.7.2"
       },
       "funding": {
         "url": "https://github.com/sponsors/posva"
@@ -3466,29 +3768,13 @@
         }
       }
     },
-    "node_modules/pinia/node_modules/vue-demi": {
-      "version": "0.14.10",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
-      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
-      "hasInstallScript": true,
-      "bin": {
-        "vue-demi-fix": "bin/vue-demi-fix.js",
-        "vue-demi-switch": "bin/vue-demi-switch.js"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/antfu"
-      },
-      "peerDependencies": {
-        "@vue/composition-api": "^1.0.0-rc.1",
-        "vue": "^3.0.0-0 || ^2.6.0"
-      },
-      "peerDependenciesMeta": {
-        "@vue/composition-api": {
-          "optional": true
-        }
+    "node_modules/pinia/node_modules/@vue/devtools-api": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz",
+      "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-kit": "^7.7.2"
       }
     },
     "node_modules/pino": {
@@ -3573,9 +3859,9 @@
       "license": "MIT"
     },
     "node_modules/postcss": {
-      "version": "8.4.49",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
-      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+      "version": "8.5.2",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
+      "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
       "funding": [
         {
           "type": "opencollective",
@@ -3592,7 +3878,7 @@
       ],
       "license": "MIT",
       "dependencies": {
-        "nanoid": "^3.3.7",
+        "nanoid": "^3.3.8",
         "picocolors": "^1.1.1",
         "source-map-js": "^1.2.1"
       },
@@ -4170,9 +4456,9 @@
       "license": "MIT"
     },
     "node_modules/rollup": {
-      "version": "4.28.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
-      "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
+      "version": "4.34.6",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz",
+      "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -4186,25 +4472,25 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.28.1",
-        "@rollup/rollup-android-arm64": "4.28.1",
-        "@rollup/rollup-darwin-arm64": "4.28.1",
-        "@rollup/rollup-darwin-x64": "4.28.1",
-        "@rollup/rollup-freebsd-arm64": "4.28.1",
-        "@rollup/rollup-freebsd-x64": "4.28.1",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
-        "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
-        "@rollup/rollup-linux-arm64-gnu": "4.28.1",
-        "@rollup/rollup-linux-arm64-musl": "4.28.1",
-        "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
-        "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
-        "@rollup/rollup-linux-s390x-gnu": "4.28.1",
-        "@rollup/rollup-linux-x64-gnu": "4.28.1",
-        "@rollup/rollup-linux-x64-musl": "4.28.1",
-        "@rollup/rollup-win32-arm64-msvc": "4.28.1",
-        "@rollup/rollup-win32-ia32-msvc": "4.28.1",
-        "@rollup/rollup-win32-x64-msvc": "4.28.1",
+        "@rollup/rollup-android-arm-eabi": "4.34.6",
+        "@rollup/rollup-android-arm64": "4.34.6",
+        "@rollup/rollup-darwin-arm64": "4.34.6",
+        "@rollup/rollup-darwin-x64": "4.34.6",
+        "@rollup/rollup-freebsd-arm64": "4.34.6",
+        "@rollup/rollup-freebsd-x64": "4.34.6",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.34.6",
+        "@rollup/rollup-linux-arm-musleabihf": "4.34.6",
+        "@rollup/rollup-linux-arm64-gnu": "4.34.6",
+        "@rollup/rollup-linux-arm64-musl": "4.34.6",
+        "@rollup/rollup-linux-loongarch64-gnu": "4.34.6",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6",
+        "@rollup/rollup-linux-riscv64-gnu": "4.34.6",
+        "@rollup/rollup-linux-s390x-gnu": "4.34.6",
+        "@rollup/rollup-linux-x64-gnu": "4.34.6",
+        "@rollup/rollup-linux-x64-musl": "4.34.6",
+        "@rollup/rollup-win32-arm64-msvc": "4.34.6",
+        "@rollup/rollup-win32-ia32-msvc": "4.34.6",
+        "@rollup/rollup-win32-x64-msvc": "4.34.6",
         "fsevents": "~2.3.2"
       }
     },
@@ -4403,6 +4689,13 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/siginfo": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+      "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+      "dev": true,
+      "license": "ISC"
+    },
     "node_modules/signal-exit": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -4453,6 +4746,15 @@
         "memory-pager": "^1.0.2"
       }
     },
+    "node_modules/speakingurl": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+      "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/split2": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -4461,6 +4763,13 @@
         "node": ">= 10.x"
       }
     },
+    "node_modules/stackback": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+      "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/statuses": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -4470,6 +4779,13 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/std-env": {
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
+      "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/string_decoder": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -4558,6 +4874,18 @@
         "postcss": "^8.4.31"
       }
     },
+    "node_modules/superjson": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+      "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+      "license": "MIT",
+      "dependencies": {
+        "copy-anything": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
     "node_modules/supports-color": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -4606,6 +4934,50 @@
         "real-require": "^0.2.0"
       }
     },
+    "node_modules/tinybench": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+      "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/tinyexec": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+      "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/tinypool": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
+      "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      }
+    },
+    "node_modules/tinyrainbow": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+      "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/tinyspy": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+      "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4746,15 +5118,15 @@
       "dev": true
     },
     "node_modules/vite": {
-      "version": "6.0.11",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
-      "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz",
+      "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "esbuild": "^0.24.2",
-        "postcss": "^8.4.49",
-        "rollup": "^4.23.0"
+        "postcss": "^8.5.1",
+        "rollup": "^4.30.1"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -4817,6 +5189,99 @@
         }
       }
     },
+    "node_modules/vite-node": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
+      "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cac": "^6.7.14",
+        "debug": "^4.4.0",
+        "es-module-lexer": "^1.6.0",
+        "pathe": "^2.0.2",
+        "vite": "^5.0.0 || ^6.0.0"
+      },
+      "bin": {
+        "vite-node": "vite-node.mjs"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/vitest": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
+      "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/expect": "3.0.5",
+        "@vitest/mocker": "3.0.5",
+        "@vitest/pretty-format": "^3.0.5",
+        "@vitest/runner": "3.0.5",
+        "@vitest/snapshot": "3.0.5",
+        "@vitest/spy": "3.0.5",
+        "@vitest/utils": "3.0.5",
+        "chai": "^5.1.2",
+        "debug": "^4.4.0",
+        "expect-type": "^1.1.0",
+        "magic-string": "^0.30.17",
+        "pathe": "^2.0.2",
+        "std-env": "^3.8.0",
+        "tinybench": "^2.9.0",
+        "tinyexec": "^0.3.2",
+        "tinypool": "^1.0.2",
+        "tinyrainbow": "^2.0.0",
+        "vite": "^5.0.0 || ^6.0.0",
+        "vite-node": "3.0.5",
+        "why-is-node-running": "^2.3.0"
+      },
+      "bin": {
+        "vitest": "vitest.mjs"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "@edge-runtime/vm": "*",
+        "@types/debug": "^4.1.12",
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "@vitest/browser": "3.0.5",
+        "@vitest/ui": "3.0.5",
+        "happy-dom": "*",
+        "jsdom": "*"
+      },
+      "peerDependenciesMeta": {
+        "@edge-runtime/vm": {
+          "optional": true
+        },
+        "@types/debug": {
+          "optional": true
+        },
+        "@types/node": {
+          "optional": true
+        },
+        "@vitest/browser": {
+          "optional": true
+        },
+        "@vitest/ui": {
+          "optional": true
+        },
+        "happy-dom": {
+          "optional": true
+        },
+        "jsdom": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/vscode-uri": {
       "version": "3.0.8",
       "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
@@ -4911,6 +5376,23 @@
         "node": ">= 8"
       }
     },
+    "node_modules/why-is-node-running": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+      "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "siginfo": "^2.0.0",
+        "stackback": "0.0.2"
+      },
+      "bin": {
+        "why-is-node-running": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index 6dc5c9da13740623bd990a08c9de7be5e13c8363..d2046610b82c220d69bdcdffdea1db0c4eb12498 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
     "dev": "concurrently -n Client,Server -c '#325D79,#45ADA8' 'npm:dev:client' 'npm:dev:server'",
     "dev:client": "vite",
     "dev:server": "nodemon --use-strict src/server/app.js | pino-pretty -t 'yyyy-mm-dd HH:MM:ss'",
+    "test": "NODE_ENV='production' vitest",
     "build": "vue-tsc && vite build",
     "build:db": "node src/scripts/buildDatabase.js",
     "stats": "node src/scripts/displayStatistics",
@@ -35,7 +36,7 @@
     "fastify": "^5.2.1",
     "modern-normalize": "^3.0.1",
     "mongodb": "^6.13.0",
-    "pinia": "^2.3.1",
+    "pinia": "^3.0.1",
     "plotly.js-dist-min": "^3.0.0",
     "qs": "^6.14.0",
     "vue": "^3.5.13",
@@ -43,7 +44,7 @@
   },
   "devDependencies": {
     "@biomejs/biome": "^1.9.4",
-    "@types/node": "^22.13.0",
+    "@types/node": "^22.13.4",
     "@types/plotly.js-dist-min": "^2.3.4",
     "@types/qs": "^6.9.18",
     "@vitejs/plugin-vue": "^5.2.1",
@@ -55,7 +56,8 @@
     "rollup-plugin-brotli": "^3.1.0",
     "rollup-plugin-gzip": "^4.0.1",
     "typescript": "^5.7.3",
-    "vite": "^6.0.11",
+    "vite": "^6.1.0",
+    "vitest": "^3.0.5",
     "vue-tsc": "^2.2.0"
   }
 }
diff --git a/src/client/store.ts b/src/client/store.ts
index 149d0f742285bbeaa9c091dbc906e416b299eb3b..5d6111d9be0e625db8c7470906e790f82ddeeff4 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -102,8 +102,8 @@ export const useStore = defineStore('store', {
     countAntibodies(countRequest: APICountParams): void {
       // Avoid to send an empty request to get an information
       // we already have in the stats.
-      if (Object.keys(countRequest).length === 0) {
-        this.antibodiesCount = this.statistics?.antibodies || 0
+      if (Object.keys(countRequest).length === 0 && this.statistics?.antibodies) {
+        this.antibodiesCount = this.statistics.antibodies
         return
       }
 
diff --git a/src/server/app.js b/src/server/app.js
index a635b5ab67400fc56d785b0c4643c49d5cbc0056..1e0c7913eda6032f357512d87d4d2d9af9f803d2 100644
--- a/src/server/app.js
+++ b/src/server/app.js
@@ -27,7 +27,7 @@ import antibodiesCountRoute from './routes/antibodiesCountRoute.js'
 import antibodiesDownloadArchiveFileRoute from './routes/antibodiesDownloadArchiveFileRoute.js'
 import antibodiesDownloadArchiveRoute from './routes/antibodiesDownloadArchiveRoute.js'
 import antibodiesDownloadRoute from './routes/antibodiesDownloadRoute.js'
-import antibodiesFindOneRoute from './routes/antibodiesFindOneRoute.js'
+import antibodiesFindByIdRoute from './routes/antibodiesFindByIdRoute.js'
 import antibodiesFindRoute from './routes/antibodiesFindRoute.js'
 import antibodiesSourcesRoute from './routes/antibodiesSourcesRoute.js'
 import antibodiesSpeciesRoute from './routes/antibodiesSpeciesRoute.js'
@@ -107,7 +107,7 @@ fastify.route(antibodiesCountRoute)
 fastify.route(antibodiesDownloadArchiveFileRoute)
 fastify.route(antibodiesDownloadArchiveRoute)
 fastify.route(antibodiesDownloadRoute)
-fastify.route(antibodiesFindOneRoute)
+fastify.route(antibodiesFindByIdRoute)
 fastify.route(antibodiesFindRoute)
 fastify.route(antibodiesSourcesRoute)
 fastify.route(antibodiesSpeciesRoute)
diff --git a/src/server/config/env.test.js b/src/server/config/env.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..f026aa22e75fd81a8d0c3614ae0471a1e80adde3
--- /dev/null
+++ b/src/server/config/env.test.js
@@ -0,0 +1,69 @@
+// ABSD
+// Copyright (C) 2023 Institut Pasteur
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import { describe, expect, test } from 'vitest'
+import serverConfig from './env'
+
+describe('Server env config', () => {
+  test('Config should exists', () => {
+    expect(serverConfig).toBeDefined()
+  })
+
+  test('Config should have all and only the defined properties', () => {
+    const envVariables = Object.keys(serverConfig).sort()
+    expect(envVariables).toStrictEqual([
+      "ABSD_DB_HOST",
+      "ABSD_DB_NAME",
+      "ABSD_DB_PORT",
+      "ABSD_SERVER_HOST",
+      "ABSD_SERVER_PORT",
+      "ABSD_SERVER_PROXY",
+      "NODE_ENV",
+    ])
+  })
+
+  test('ABSD_DB_HOST should be a string', () => {
+    expect(serverConfig.ABSD_DB_HOST).toBeTypeOf('string')
+  })
+
+  test('ABSD_DB_NAME should be a string', () => {
+    expect(serverConfig.ABSD_DB_NAME).toBeTypeOf('string')
+  })
+
+  test('ABSD_DB_PORT should be a positive integer', () => {
+    expect(serverConfig.ABSD_DB_PORT).toBeTypeOf('number')
+    expect(serverConfig.ABSD_DB_PORT).toBeGreaterThan(0)
+    expect(serverConfig.ABSD_DB_PORT % 1).toBe(0)
+  })
+
+  test('ABSD_SERVER_HOST should be a string', () => {
+    expect(serverConfig.ABSD_SERVER_HOST).toBeTypeOf('string')
+  })
+
+  test('ABSD_SERVER_PORT should be a positive integer', () => {
+    expect(serverConfig.ABSD_SERVER_PORT).toBeTypeOf('number')
+    expect(serverConfig.ABSD_SERVER_PORT).toBeGreaterThan(0)
+    expect(serverConfig.ABSD_SERVER_PORT % 1).toBe(0)
+  })
+
+  test('ABSD_SERVER_PROXY should be a boolean', () => {
+    expect(serverConfig.ABSD_SERVER_PROXY).toBeTypeOf('boolean')
+  })
+
+  test('NODE_ENV should be either development or production', () => {
+    expect(serverConfig.NODE_ENV).toBeOneOf(['development', 'production'])
+  })
+})
diff --git a/src/server/models/Antibody.test.js b/src/server/models/Antibody.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..7dd24c344dbdc7f03a9a42db11e63f6cf29c4119
--- /dev/null
+++ b/src/server/models/Antibody.test.js
@@ -0,0 +1,140 @@
+// ABSD
+// Copyright (C) 2023 Institut Pasteur
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import { describe, expect, test } from "vitest"
+import { Antibody } from "./Antibody"
+
+describe('Antibody class', () => {
+  test('should be a class', () => {
+    const antibody = new Antibody()
+    expect(antibody).toBeInstanceOf(Antibody)
+  })
+
+  test('should define the static method "listSources"', () => {
+    expect(Antibody.listSources).toBeInstanceOf(Function)
+  })
+
+  test('should define the static method "listSegments"', () => {
+    expect(Antibody.listSegments).toBeInstanceOf(Function)
+  })
+
+  test('should define the static method "generateHashId"', () => {
+    expect(Antibody.generateHashId).toBeInstanceOf(Function)
+  })
+
+  test('should define the static method "toFasta"', () => {
+    expect(Antibody.toFasta).toBeInstanceOf(Function)
+  })
+
+  test('should define the static method "toString"', () => {
+    expect(Antibody.toString).toBeInstanceOf(Function)
+  })
+})
+
+describe('Antibody class methods', () => {
+  const antibody = {
+    "id": "368.22.A.0062",
+    "species": "Homo sapiens",
+    "hashId": "53775feae7c2d86939d15177e0b963e20504141de8601e28ab701f9f4fe9d3a2",
+    "lightChain": {
+      "headers": [
+        {
+          "id": "368.22.A.0062",
+          "vGeneSegment": "IGLV4",
+          "source": "CoV-AbDab",
+          "header": "368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_light_"
+        },
+        {
+          "id": "SRR17729722",
+          "vGeneSegment": "IGLV4",
+          "source": "PairedNGS",
+          "header": "PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light"
+        },
+        {
+          "id": "SRR17778139",
+          "vGeneSegment": "IGLV4",
+          "source": "PairedNGS",
+          "header": "PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light"
+        }
+      ],
+      "sequence": "QLVLTQSPSASASLGASVKLTCTLSSGHSSYAIAWHQQQPEKGPRYLMKLNSDGSHSEGDGIPDRFSGSSSGAERYLTISSLQSEDEADYYCQTWGTGIHVFGTGTKVTVL",
+      "rawFasta": "368.22.A.0062|||368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_light_;368.22.A.0062;IGLV4;CoV-AbDab|||PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light;SRR17729722;IGLV4;PairedNGS|||PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light;SRR17778139;IGLV4;PairedNGS"
+    },
+    "heavyChain": {
+      "headers": [
+        {
+          "id": "368.22.A.0062",
+          "vGeneSegment": "IGHV3",
+          "source": "CoV-AbDab",
+          "header": "368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_heavy_"
+        },
+        {
+          "id": "SRR17729722",
+          "vGeneSegment": "IGHV3",
+          "source": "PairedNGS",
+          "header": "PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy"
+        },
+        {
+          "id": "SRR17778139",
+          "vGeneSegment": "IGHV3",
+          "source": "PairedNGS",
+          "header": "PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy"
+        }
+      ],
+      "sequence": "QVQLVESGGGVVQPGRSLRLSCAASGFTFSGYAMHWVRQAPGKGLEWVAVISYDGSNRYYADSVKGRFSISRDNSKKTLYLQMNSLRDEDTAVYYCAKVEGGNYVGAFDVWGQGTMVTVSS",
+      "rawFasta": "368.22.A.0062|||368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_heavy_;368.22.A.0062;IGHV3;CoV-AbDab|||PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy;SRR17729722;IGHV3;PairedNGS|||PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy;SRR17778139;IGHV3;PairedNGS"
+    }
+  }
+
+  test('"listSources" should return the list of sources as a Set', () => {
+    const sources = Antibody.listSources(antibody)
+    const expected = new Set()
+      .add('CoV-AbDab')
+      .add('PairedNGS')
+    expect(sources).toStrictEqual(expected)
+  })
+
+  test('"listSegments" should return the list of gene segments as an object containg two Sets', () => {
+    const segments = Antibody.listSegments(antibody)
+    const expected = {
+      heavyChain: new Set().add('IGHV3'),
+      lightChain: new Set().add('IGLV4')
+    }
+    expect(segments).toStrictEqual(expected)
+  })
+
+  test('"generateHashId" should return the SHA256 of the antibody', () => {
+    const hashId = Antibody.generateHashId(antibody)
+    expect(hashId).toBe(antibody.hashId)
+  })
+
+  test('"toFasta" should return the FASTA version of the antibody', () => {
+    const fasta = Antibody.toFasta(antibody)
+    const expected = ">368.22.A.0062|||368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_light_;368.22.A.0062;IGLV4;CoV-AbDab|||PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light;SRR17729722;IGLV4;PairedNGS|||PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_light;SRR17778139;IGLV4;PairedNGS"
+      .concat("\nQLVLTQSPSASASLGASVKLTCTLSSGHSSYAIAWHQQQPEKGPRYLMKLNSDGSHSEGDGIPDRFSGSSSGAERYLTISSLQSEDEADYYCQTWGTGIHVFGTGTKVTVL")
+      .concat("\n>368.22.A.0062|||368.22.A.0062_B-cells SARS-CoV2 Human Patient_human_heavy_;368.22.A.0062;IGHV3;CoV-AbDab|||PRJNA777934|SRR17729722|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02,IGHG1|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy;SRR17729722;IGHV3;PairedNGS|||PRJNA777934|SRR17778139|human|GCTGCAGGTACCTACA|IGHV3_30*04,IGHD4_23*01,IGHJ3*02|IGLV4_69*01,IGLJ1*01,IGLC1|AKVEGGNYVGAFDV|QTWGTGIHV_heavy;SRR17778139;IGHV3;PairedNGS")
+      .concat("\nQVQLVESGGGVVQPGRSLRLSCAASGFTFSGYAMHWVRQAPGKGLEWVAVISYDGSNRYYADSVKGRFSISRDNSKKTLYLQMNSLRDEDTAVYYCAKVEGGNYVGAFDVWGQGTMVTVSS")
+
+    expect(fasta).toBe(expected)
+  })
+
+  test('"toString" should return the JSON version of the antibody', () => {
+    const json = Antibody.toString(antibody)
+    const expected = JSON.stringify(antibody)
+
+    expect(json).toBe(expected)
+  })
+})
diff --git a/src/server/routes/antibodiesFindOneRoute.js b/src/server/routes/antibodiesFindByIdRoute.js
similarity index 92%
rename from src/server/routes/antibodiesFindOneRoute.js
rename to src/server/routes/antibodiesFindByIdRoute.js
index d2a81221736adfad3b013b62c5c2d4288d0634c6..975b81a785b7dff2e00349c5c45f5cab5ed3982f 100644
--- a/src/server/routes/antibodiesFindOneRoute.js
+++ b/src/server/routes/antibodiesFindByIdRoute.js
@@ -23,7 +23,11 @@ export default {
   handler: function (request, reply) {
     this.mongo.db
       .collection('antibodies')
-      .findOne({ hashId: request.params.hashId })
+      .findOne({
+        hashId: request.params.hashId
+      }, {
+        projection: { _id: false }
+      })
       .then(antibody => {
         return reply
           .code(200)
diff --git a/src/server/utils/escapeString.js b/src/server/utils/escapeString.js
index 2606aec6d02669af6ed19a6ce3ec5465be10050c..2d06104b1da8fbdfed68b48c3927849737ada7dc 100644
--- a/src/server/utils/escapeString.js
+++ b/src/server/utils/escapeString.js
@@ -15,7 +15,7 @@
 // along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Escape all special charcaters in a string to make it safe
+ * Escape all special characters in a string to make it safe
  * to use in a regex.
  * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
  * @param {string} string - The string containing characters to escape
diff --git a/src/server/utils/escapeString.test.js b/src/server/utils/escapeString.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..f9eb19af3e025dbc6ea198d3ac5f5b893fef6aa0
--- /dev/null
+++ b/src/server/utils/escapeString.test.js
@@ -0,0 +1,25 @@
+// ABSD
+// Copyright (C) 2023 Institut Pasteur
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import { expect, test } from "vitest"
+import escapeString from "./escapeString"
+
+test('Should escape all dangerous characters in a regex', () => {
+  const dangerousString = "abc.de*fgh+ijk?lmn^op$qr{st}u(vw)xyzABCDE|FG\HIJK[LM]NOPQRSTUVWXYZ123456789-_%&#@"
+  const safeString = "abc\\.de\\*fgh\\+ijk\\?lmn\\^op\\$qr\\{st\\}u\\(vw\\)xyzABCDE\\|FGHIJK\\[LM\\]NOPQRSTUVWXYZ123456789-_%&#@"
+
+  expect(escapeString(dangerousString)).toBe(safeString)
+})
diff --git a/vite.config.ts b/vite.config.ts
index 3bdd5fe50f1706e4186a71709382434ba240f19c..f0a8bc86dfa55eeded30af4d2845122fe633e4b2 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,3 +1,4 @@
+/// <reference types='vitest' />
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import brotliPlugin from 'rollup-plugin-brotli'
@@ -29,5 +30,11 @@ export default defineConfig({
     __VUE_OPTIONS_API__: false,
     __VUE_PROD_DEVTOOLS__: false,
     __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
+  },
+  test: {
+    // https://vitest.dev/config/
+    name: 'Server tests',
+    root: 'src/server',
+    include: ['**/*.test.js']
   }
 })