From b3275303adaf7fa5ac7160172a1515898257d7ba Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Sun, 16 Feb 2025 00:52:28 +0100
Subject: [PATCH] Update the tests

Signed-off-by: Martin Weise <martin.weise@tuwien.ac.at>
---
 .gitlab-ci.yml                                |   5 +-
 dbrepo-search-service/.gitignore              |   3 +
 dbrepo-search-service/Dockerfile              |   2 +-
 dbrepo-search-service/app.py                  |   2 +-
 dbrepo-search-service/init/.coveragerc        |   5 +
 dbrepo-search-service/init/Pipfile            |   4 +-
 dbrepo-search-service/init/Pipfile.lock       | 389 +++++++++++++++---
 .../init/clients/keycloak_client.py           |  12 +-
 .../{ => init}/friendly_names_overrides.json  |   0
 dbrepo-search-service/init/test/rsa/rs256.key |   3 +
 .../init/test/rsa/rsa/rs256.key               |   3 +
 .../init/test/rsa/rsa/rsa256.pkey             |   3 +
 .../init/test/rsa/rsa256.pkey                 |   3 +
 .../init/test/test_keycloak_client.py         |  53 +++
 .../init/test/test_opensearch_client.py       | 206 ++++++++++
 dbrepo-search-service/test.sh                 |   7 -
 .../test/test_keycloak_client.py              |  57 ---
 .../test/test_opensearch_client.py            | 215 ----------
 18 files changed, 621 insertions(+), 351 deletions(-)
 create mode 100644 dbrepo-search-service/init/.coveragerc
 rename dbrepo-search-service/{ => init}/friendly_names_overrides.json (100%)
 create mode 100644 dbrepo-search-service/init/test/rsa/rs256.key
 create mode 100644 dbrepo-search-service/init/test/rsa/rsa/rs256.key
 create mode 100644 dbrepo-search-service/init/test/rsa/rsa/rsa256.pkey
 create mode 100644 dbrepo-search-service/init/test/rsa/rsa256.pkey
 create mode 100644 dbrepo-search-service/init/test/test_keycloak_client.py
 create mode 100644 dbrepo-search-service/init/test/test_opensearch_client.py
 delete mode 100644 dbrepo-search-service/test.sh
 delete mode 100644 dbrepo-search-service/test/test_keycloak_client.py
 delete mode 100644 dbrepo-search-service/test/test_opensearch_client.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 74cfb6df9a..43558cb94b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -348,6 +348,9 @@ test-search-service:
   before_script:
     - "cp -r ./dbrepo-search-service/init/clients ./dbrepo-search-service/clients"
     - "cp -r ./dbrepo-search-service/init/omlib ./dbrepo-search-service/omlib"
+    - "cp ./dbrepo-search-service/init/test/test_keycloak_client.py ./dbrepo-search-service/test"
+    - "cp ./dbrepo-search-service/init/test/test_opensearch_client.py ./dbrepo-search-service/test"
+    - "cp ./dbrepo-search-service/init/friendly_names_overrides.json ./dbrepo-search-service/friendly_names_overrides.json"
   script:
     - "pip install pipenv"
     - "pipenv install gunicorn && pipenv install --dev --system --deploy"
@@ -375,7 +378,7 @@ test-search-service-init:
   script:
     - "pip install pipenv"
     - "pipenv install gunicorn && pipenv install --dev --system --deploy"
-    - cd ./dbrepo-search-service/init/ && coverage run -m pytest test/test_app.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
+    - cd ./dbrepo-search-service/init/ && coverage run -m pytest test/test_app.py test/test_keycloak_client.py test/test_opensearch_client.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
     - "cat ./coverage.txt | grep -o 'TOTAL[^%]*%'"
   artifacts:
     when: always
diff --git a/dbrepo-search-service/.gitignore b/dbrepo-search-service/.gitignore
index e4a1bfbd16..f2eb411003 100644
--- a/dbrepo-search-service/.gitignore
+++ b/dbrepo-search-service/.gitignore
@@ -38,6 +38,9 @@ MANIFEST
 *.manifest
 *.spec
 
+# generate
+/friendly_names_overrides.json
+
 # Installer logs
 pip-log.txt
 pip-delete-this-directory.txt
diff --git a/dbrepo-search-service/Dockerfile b/dbrepo-search-service/Dockerfile
index 0efe76bc88..3e46d8fc90 100644
--- a/dbrepo-search-service/Dockerfile
+++ b/dbrepo-search-service/Dockerfile
@@ -19,9 +19,9 @@ WORKDIR /app
 
 COPY --chown=1001 ./init/clients ./clients
 COPY --chown=1001 ./init/omlib ./omlib
+COPY --chown=1001 ./init/friendly_names_overrides.json ./friendly_names_overrides.json
 COPY --chown=1001 ./os-yml ./os-yml
 COPY --chown=1001 ./app.py ./app.py
-COPY --chown=1001 ./friendly_names_overrides.json ./friendly_names_overrides.json
 
 # non-root port
 EXPOSE 8080
diff --git a/dbrepo-search-service/app.py b/dbrepo-search-service/app.py
index f9e2dbcc77..13a9be8704 100644
--- a/dbrepo-search-service/app.py
+++ b/dbrepo-search-service/app.py
@@ -186,7 +186,7 @@ app.config["METADATA_SERVICE_ENDPOINT"] = os.getenv("METADATA_SERVICE_ENDPOINT",
 app.config["JWT_ALGORITHM"] = "HS256"
 app.config["JWT_PUBKEY"] = '-----BEGIN PUBLIC KEY-----\n' + os.getenv("JWT_PUBKEY",
                                                                       "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB") + '\n-----END PUBLIC KEY-----'
-app.config["AUTH_SERVICE_ENDPOINT"] = os.getenv("AUTH_SERVICE_ENDPOINT", "http://auth-service:8080/api/auth")
+app.config["AUTH_SERVICE_ENDPOINT"] = os.getenv("AUTH_SERVICE_ENDPOINT", "http://auth-service:8080")
 app.config["AUTH_SERVICE_CLIENT"] = os.getenv("AUTH_SERVICE_CLIENT", "dbrepo-client")
 app.config["AUTH_SERVICE_CLIENT_SECRET"] = os.getenv("AUTH_SERVICE_CLIENT_SECRET", "MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG")
 app.config["OPENSEARCH_HOST"] = os.getenv('OPENSEARCH_HOST', 'search-db')
diff --git a/dbrepo-search-service/init/.coveragerc b/dbrepo-search-service/init/.coveragerc
new file mode 100644
index 0000000000..b5a0e606be
--- /dev/null
+++ b/dbrepo-search-service/init/.coveragerc
@@ -0,0 +1,5 @@
+[report]
+
+omit =
+    # omit tests
+    ./test/*
\ No newline at end of file
diff --git a/dbrepo-search-service/init/Pipfile b/dbrepo-search-service/init/Pipfile
index b74ed7bc40..171460cd23 100644
--- a/dbrepo-search-service/init/Pipfile
+++ b/dbrepo-search-service/init/Pipfile
@@ -9,12 +9,14 @@ opensearch-py = "~=2.2"
 python-dotenv = "~=1.0"
 testcontainers-opensearch = "*"
 pytest = "*"
-dbrepo = {path = "./lib/dbrepo-1.6.3.tar.gz"}
+dbrepo = {path = "./lib/dbrepo-1.6.4.tar.gz"}
 rdflib = "*"
 
 [dev-packages]
 coverage = "*"
 pytest = "*"
+requests-mock = "*"
+jwt = "~=1.3"
 
 [requires]
 python_version = "3.11"
diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock
index 77017b6d15..c565195173 100644
--- a/dbrepo-search-service/init/Pipfile.lock
+++ b/dbrepo-search-service/init/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "dac534d1eb6a0942c0e296c8a58491847c65d3ca23315039a3725591c86f694f"
+            "sha256": "e9b86cb78a55fb9906d294b2ce675933832658ad12ad1ed4f7f3d5893bc0a301"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -259,11 +259,9 @@
         },
         "dbrepo": {
             "hashes": [
-                "sha256:199a5e4d4ede04d871681880f3797a4bdbb09317ee3796df14c8c501756216d6",
-                "sha256:7d62d00d51c1f0a178c795a8cc09b3b4c93ee0a18aed6514e2cb60465cd877f7"
+                "sha256:a518aee79540d9e302b161e7e10072f50730489da19368f00a1e68204009ce44"
             ],
-            "markers": "python_version >= '3.11'",
-            "path": "./lib/dbrepo-1.6.3.tar.gz"
+            "path": "./lib/dbrepo-1.6.4.tar.gz"
         },
         "docker": {
             "hashes": [
@@ -585,64 +583,64 @@
         },
         "numpy": {
             "hashes": [
-                "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f",
-                "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0",
-                "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd",
-                "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2",
-                "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4",
-                "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648",
-                "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be",
-                "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb",
-                "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160",
-                "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd",
-                "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a",
-                "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84",
-                "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e",
-                "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748",
-                "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825",
-                "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60",
-                "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957",
-                "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715",
-                "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317",
-                "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e",
-                "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283",
-                "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278",
-                "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9",
-                "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de",
-                "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369",
-                "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb",
-                "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189",
-                "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014",
-                "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323",
-                "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e",
-                "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49",
-                "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50",
-                "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d",
-                "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37",
-                "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39",
-                "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576",
-                "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a",
-                "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba",
-                "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7",
-                "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826",
-                "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467",
-                "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495",
-                "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc",
-                "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391",
-                "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0",
-                "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97",
-                "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c",
-                "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac",
-                "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369",
-                "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8",
-                "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2",
-                "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff",
-                "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a",
-                "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df",
-                "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"
+                "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52",
+                "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d",
+                "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693",
+                "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d",
+                "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8",
+                "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027",
+                "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304",
+                "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5",
+                "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5",
+                "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50",
+                "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a",
+                "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94",
+                "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021",
+                "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e",
+                "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe",
+                "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d",
+                "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890",
+                "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8",
+                "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe",
+                "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1",
+                "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e",
+                "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b",
+                "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb",
+                "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b",
+                "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094",
+                "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea",
+                "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c",
+                "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636",
+                "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4",
+                "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba",
+                "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a",
+                "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d",
+                "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95",
+                "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2",
+                "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b",
+                "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f",
+                "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1",
+                "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532",
+                "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082",
+                "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2",
+                "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0",
+                "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71",
+                "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787",
+                "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef",
+                "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d",
+                "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3",
+                "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b",
+                "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf",
+                "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020",
+                "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76",
+                "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716",
+                "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9",
+                "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb",
+                "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610",
+                "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b"
             ],
-            "markers": "python_version == '3.11'",
-            "version": "==2.2.2"
+            "markers": "python_version >= '3.10'",
+            "version": "==2.2.3"
         },
         "opensearch-py": {
             "hashes": [
@@ -1045,7 +1043,7 @@
                 "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
                 "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
             ],
-            "markers": "python_version >= '3.10'",
+            "markers": "python_version >= '3.9'",
             "version": "==2.3.0"
         },
         "werkzeug": {
@@ -1231,6 +1229,185 @@
         }
     },
     "develop": {
+        "certifi": {
+            "hashes": [
+                "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651",
+                "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==2025.1.31"
+        },
+        "cffi": {
+            "hashes": [
+                "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8",
+                "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2",
+                "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1",
+                "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15",
+                "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36",
+                "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824",
+                "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8",
+                "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36",
+                "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17",
+                "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf",
+                "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc",
+                "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3",
+                "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed",
+                "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702",
+                "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1",
+                "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8",
+                "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903",
+                "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6",
+                "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d",
+                "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b",
+                "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e",
+                "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be",
+                "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c",
+                "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683",
+                "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9",
+                "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c",
+                "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8",
+                "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1",
+                "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4",
+                "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655",
+                "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67",
+                "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595",
+                "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0",
+                "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65",
+                "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41",
+                "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6",
+                "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401",
+                "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6",
+                "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3",
+                "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16",
+                "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93",
+                "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e",
+                "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4",
+                "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964",
+                "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c",
+                "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576",
+                "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0",
+                "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3",
+                "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662",
+                "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3",
+                "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff",
+                "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5",
+                "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd",
+                "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f",
+                "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5",
+                "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14",
+                "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d",
+                "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9",
+                "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7",
+                "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382",
+                "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a",
+                "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e",
+                "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a",
+                "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4",
+                "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99",
+                "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87",
+                "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==1.17.1"
+        },
+        "charset-normalizer": {
+            "hashes": [
+                "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537",
+                "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa",
+                "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a",
+                "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294",
+                "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b",
+                "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd",
+                "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601",
+                "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd",
+                "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4",
+                "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d",
+                "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2",
+                "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313",
+                "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd",
+                "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa",
+                "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8",
+                "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1",
+                "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2",
+                "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496",
+                "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d",
+                "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b",
+                "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e",
+                "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a",
+                "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4",
+                "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca",
+                "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78",
+                "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408",
+                "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5",
+                "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3",
+                "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f",
+                "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a",
+                "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765",
+                "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6",
+                "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146",
+                "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6",
+                "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9",
+                "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd",
+                "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c",
+                "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f",
+                "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545",
+                "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176",
+                "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770",
+                "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824",
+                "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f",
+                "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf",
+                "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487",
+                "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d",
+                "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd",
+                "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b",
+                "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534",
+                "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f",
+                "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b",
+                "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9",
+                "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd",
+                "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125",
+                "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9",
+                "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de",
+                "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11",
+                "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d",
+                "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35",
+                "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f",
+                "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda",
+                "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7",
+                "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a",
+                "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971",
+                "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8",
+                "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41",
+                "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d",
+                "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f",
+                "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757",
+                "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a",
+                "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886",
+                "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77",
+                "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76",
+                "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247",
+                "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85",
+                "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb",
+                "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7",
+                "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e",
+                "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6",
+                "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037",
+                "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1",
+                "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e",
+                "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807",
+                "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407",
+                "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c",
+                "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12",
+                "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3",
+                "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089",
+                "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd",
+                "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e",
+                "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00",
+                "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==3.4.1"
+        },
         "coverage": {
             "hashes": [
                 "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95",
@@ -1301,6 +1478,51 @@
             "markers": "python_version >= '3.9'",
             "version": "==7.6.12"
         },
+        "cryptography": {
+            "hashes": [
+                "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7",
+                "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3",
+                "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183",
+                "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69",
+                "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a",
+                "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62",
+                "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911",
+                "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7",
+                "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a",
+                "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41",
+                "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83",
+                "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12",
+                "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864",
+                "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf",
+                "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c",
+                "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2",
+                "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b",
+                "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0",
+                "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4",
+                "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9",
+                "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008",
+                "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862",
+                "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009",
+                "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7",
+                "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f",
+                "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026",
+                "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f",
+                "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd",
+                "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420",
+                "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14",
+                "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"
+            ],
+            "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
+            "version": "==44.0.1"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
+                "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==3.10"
+        },
         "iniconfig": {
             "hashes": [
                 "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
@@ -1309,6 +1531,14 @@
             "markers": "python_version >= '3.7'",
             "version": "==2.0.0"
         },
+        "jwt": {
+            "hashes": [
+                "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.6'",
+            "version": "==1.3.1"
+        },
         "packaging": {
             "hashes": [
                 "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
@@ -1325,6 +1555,14 @@
             "markers": "python_version >= '3.8'",
             "version": "==1.5.0"
         },
+        "pycparser": {
+            "hashes": [
+                "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
+                "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==2.22"
+        },
         "pytest": {
             "hashes": [
                 "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6",
@@ -1333,6 +1571,31 @@
             "index": "pypi",
             "markers": "python_version >= '3.8'",
             "version": "==8.3.4"
+        },
+        "requests": {
+            "hashes": [
+                "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
+                "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==2.32.3"
+        },
+        "requests-mock": {
+            "hashes": [
+                "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563",
+                "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.5'",
+            "version": "==1.12.1"
+        },
+        "urllib3": {
+            "hashes": [
+                "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
+                "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==2.3.0"
         }
     }
 }
diff --git a/dbrepo-search-service/init/clients/keycloak_client.py b/dbrepo-search-service/init/clients/keycloak_client.py
index 2e15d00a9b..e8c277601b 100644
--- a/dbrepo-search-service/init/clients/keycloak_client.py
+++ b/dbrepo-search-service/init/clients/keycloak_client.py
@@ -1,10 +1,10 @@
 import logging
+import os
 from dataclasses import dataclass
 from typing import List
 
 import requests
 from dbrepo.api.dto import ApiError
-from flask import current_app
 from jwt import jwk_from_pem, JWT
 from jwt.exceptions import JWTDecodeError
 
@@ -20,13 +20,13 @@ class KeycloakClient:
 
     def obtain_user_token(self, username: str, password: str) -> str:
         response = requests.post(
-            f"{current_app.config['AUTH_SERVICE_ENDPOINT']}/realms/dbrepo/protocol/openid-connect/token",
+            f"{os.getenv('AUTH_SERVICE_ENDPOINT', 'http://auth-service:8080')}/realms/dbrepo/protocol/openid-connect/token",
             data={
                 "username": username,
                 "password": password,
                 "grant_type": "password",
-                "client_id": current_app.config["AUTH_SERVICE_CLIENT"],
-                "client_secret": current_app.config["AUTH_SERVICE_CLIENT_SECRET"]
+                "client_id": os.getenv("AUTH_SERVICE_CLIENT", "dbrepo-client"),
+                "client_secret": os.getenv("AUTH_SERVICE_CLIENT_SECRET", "MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG")
             })
         body = response.json()
         if "access_token" not in body:
@@ -34,7 +34,9 @@ class KeycloakClient:
         return response.json()["access_token"]
 
     def verify_jwt(self, access_token: str) -> ApiError | User:
-        public_key = jwk_from_pem(str(current_app.config["JWT_PUBKEY"]).encode('utf-8'))
+        public_key = jwk_from_pem(str('-----BEGIN PUBLIC KEY-----\n' + os.getenv("JWT_PUBKEY",
+                                                                                 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB") + '\n-----END PUBLIC KEY-----').encode(
+            'utf-8'))
         payload = JWT().decode(message=access_token, key=public_key, do_time_check=True)
         return User(id=payload.get('uid'), username=payload.get('client_id'),
                     roles=payload.get('realm_access')["roles"])
diff --git a/dbrepo-search-service/friendly_names_overrides.json b/dbrepo-search-service/init/friendly_names_overrides.json
similarity index 100%
rename from dbrepo-search-service/friendly_names_overrides.json
rename to dbrepo-search-service/init/friendly_names_overrides.json
diff --git a/dbrepo-search-service/init/test/rsa/rs256.key b/dbrepo-search-service/init/test/rsa/rs256.key
new file mode 100644
index 0000000000..86b3eaf5c6
--- /dev/null
+++ b/dbrepo-search-service/init/test/rsa/rs256.key
@@ -0,0 +1,3 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQABAoIBADNcMt6hAHub4JTAYS6Mra0EPRBO2XhWmACBrv3+8ETClXd5475KPLDewgRVtlmtbwU8G8awUXESQgPS9lfiqvQhPreA3cHlm6oP2WMKOEtakr2s8I+frsTBLCo0Ini9RaSzjoVVgS0zofyhASKi+T970MafSj5P3XNb8YBFdXgoYDiA7FXLH6a/+m7LScL+wGcFMAAeYESxZbMQLfH3v8L+4EcTraiwjLG17ZdlF3dpybMyUSse6ZQ/PdlyvBuzzLXhN6Ce2gd9ATfS+YWTzo7Yf+GU+ex5bIpVOfHqtuM/hyq7YGKENClsXwNZIAoFnvGCbvECAfgyapVrD30IfykCgYEA0rgsSZ82pxT40NxwgBD1g9lbNVBKXphRB/3S078qusUzJjT7AldEj4imGPhAbI7bI8gAeWJsp1XJWkjM8ktaVrh+NQl7p8e9OPh0pQF/5Bdg8ajbjXESpjnaU66pVYRQy/d+jNli/YRAHX5RUfsBl+6W4+WSVMGmKBiqJsur+ecCgYEAz1YVXClcmUnyZem5B+2E9noIzjF6ROE+jIb6rawM85P3Xd0lXtECQavtxw+Qk7I32qOwrxl1UpK2foVel3pazi+4OpMfmqtYGenRP1Zk1cZwrDo0cIemTDGjj3kJ8tYn12CGolFQpJZgK6OHzvG0tOxI5VZgjIViWNPe1PGWXtUCgYEAxXGNDe8BZs1f11S2lUlOw5yGug3hoYFXbAWJ5p7Ziuf8ZXB/QlJDC7se54a11wKEk6Jzz0lKRgE8CjzszJuOqnN0zn10QGIIC7nCklo1W6QMUmPGVWH994N976tZP6gbjQL6sT+AYcvpx7j0ubxYYeRNvnz+ACzzY964kGGHY0ECgYEAumlwPPNnMN7+VEjGNm2D7UMdJZ3wi3tkjF5ThdA5uMohTsAk+FG80KSu3RmOaGyEsUwY7+VYyYvlDm4E9PZqLBVVczyR3rMNPAcwPd0EPfvzk7WlLkOX7ct3fehaXH3VRlyfz9KCSeh1wOZ/lT1VtpD2nVOC7PSDzs92+kfXZZ0CgYAnrD1y4skgXkdwolZ3unn3EFyGm2d+X5aMTHwQPdWxqoNIAl/9wdghlzihwnPhhsxq1WzlxuC3V2IMrNPtRx70Mi+FbSmR5m4Xx5RptgMtMlwno+L40PzNJgMjHGjt0wcx3Vel8wuohDtnqMyS7P5nG1/TQx0Cyzwn7QOXlNpgbQ==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/dbrepo-search-service/init/test/rsa/rsa/rs256.key b/dbrepo-search-service/init/test/rsa/rsa/rs256.key
new file mode 100644
index 0000000000..86b3eaf5c6
--- /dev/null
+++ b/dbrepo-search-service/init/test/rsa/rsa/rs256.key
@@ -0,0 +1,3 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQABAoIBADNcMt6hAHub4JTAYS6Mra0EPRBO2XhWmACBrv3+8ETClXd5475KPLDewgRVtlmtbwU8G8awUXESQgPS9lfiqvQhPreA3cHlm6oP2WMKOEtakr2s8I+frsTBLCo0Ini9RaSzjoVVgS0zofyhASKi+T970MafSj5P3XNb8YBFdXgoYDiA7FXLH6a/+m7LScL+wGcFMAAeYESxZbMQLfH3v8L+4EcTraiwjLG17ZdlF3dpybMyUSse6ZQ/PdlyvBuzzLXhN6Ce2gd9ATfS+YWTzo7Yf+GU+ex5bIpVOfHqtuM/hyq7YGKENClsXwNZIAoFnvGCbvECAfgyapVrD30IfykCgYEA0rgsSZ82pxT40NxwgBD1g9lbNVBKXphRB/3S078qusUzJjT7AldEj4imGPhAbI7bI8gAeWJsp1XJWkjM8ktaVrh+NQl7p8e9OPh0pQF/5Bdg8ajbjXESpjnaU66pVYRQy/d+jNli/YRAHX5RUfsBl+6W4+WSVMGmKBiqJsur+ecCgYEAz1YVXClcmUnyZem5B+2E9noIzjF6ROE+jIb6rawM85P3Xd0lXtECQavtxw+Qk7I32qOwrxl1UpK2foVel3pazi+4OpMfmqtYGenRP1Zk1cZwrDo0cIemTDGjj3kJ8tYn12CGolFQpJZgK6OHzvG0tOxI5VZgjIViWNPe1PGWXtUCgYEAxXGNDe8BZs1f11S2lUlOw5yGug3hoYFXbAWJ5p7Ziuf8ZXB/QlJDC7se54a11wKEk6Jzz0lKRgE8CjzszJuOqnN0zn10QGIIC7nCklo1W6QMUmPGVWH994N976tZP6gbjQL6sT+AYcvpx7j0ubxYYeRNvnz+ACzzY964kGGHY0ECgYEAumlwPPNnMN7+VEjGNm2D7UMdJZ3wi3tkjF5ThdA5uMohTsAk+FG80KSu3RmOaGyEsUwY7+VYyYvlDm4E9PZqLBVVczyR3rMNPAcwPd0EPfvzk7WlLkOX7ct3fehaXH3VRlyfz9KCSeh1wOZ/lT1VtpD2nVOC7PSDzs92+kfXZZ0CgYAnrD1y4skgXkdwolZ3unn3EFyGm2d+X5aMTHwQPdWxqoNIAl/9wdghlzihwnPhhsxq1WzlxuC3V2IMrNPtRx70Mi+FbSmR5m4Xx5RptgMtMlwno+L40PzNJgMjHGjt0wcx3Vel8wuohDtnqMyS7P5nG1/TQx0Cyzwn7QOXlNpgbQ==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/dbrepo-search-service/init/test/rsa/rsa/rsa256.pkey b/dbrepo-search-service/init/test/rsa/rsa/rsa256.pkey
new file mode 100644
index 0000000000..857dfb22be
--- /dev/null
+++ b/dbrepo-search-service/init/test/rsa/rsa/rsa256.pkey
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/dbrepo-search-service/init/test/rsa/rsa256.pkey b/dbrepo-search-service/init/test/rsa/rsa256.pkey
new file mode 100644
index 0000000000..857dfb22be
--- /dev/null
+++ b/dbrepo-search-service/init/test/rsa/rsa256.pkey
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/dbrepo-search-service/init/test/test_keycloak_client.py b/dbrepo-search-service/init/test/test_keycloak_client.py
new file mode 100644
index 0000000000..0c43da2301
--- /dev/null
+++ b/dbrepo-search-service/init/test/test_keycloak_client.py
@@ -0,0 +1,53 @@
+import time
+import unittest
+
+import jwt
+import requests_mock
+
+from clients.keycloak_client import KeycloakClient
+
+
+class JwtTest(unittest.TestCase):
+
+    def response(self, username) -> dict:
+        return dict({
+            "client_id": username,
+            "access_token": "eyEY1234"
+        })
+
+    def token(self, username: str, roles: [str], iat: int = int(time.time())) -> str:
+        claims = {
+            'iat': iat,
+            'client_id': username,
+            'realm_access': {
+                'roles': roles
+            }
+        }
+        with open('test/rsa/rs256.key', 'rb') as fh:
+            return jwt.JWT().encode(claims, jwt.jwk_from_pem(fh.read()), alg='RS256')
+
+    def test_obtain_user_token_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('http://auth-service:8080/realms/dbrepo/protocol/openid-connect/token',
+                      json=self.response("username"))
+            # test
+            token = KeycloakClient().obtain_user_token("username", "password")
+            self.assertEqual("eyEY1234", token)
+
+    def test_obtain_user_token_malformed_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('http://auth-service:8080/realms/dbrepo/protocol/openid-connect/token',
+                      json={"client_id": "username"})
+            # test
+            try:
+                KeycloakClient().obtain_user_token("username", "password")
+                self.fail()
+            except AssertionError:
+                pass
+
+    def test_verify_jwt_succeeds(self):
+        # test
+        user = KeycloakClient().verify_jwt(self.token("username", []))
+        self.assertEqual("username", user.username)
diff --git a/dbrepo-search-service/init/test/test_opensearch_client.py b/dbrepo-search-service/init/test/test_opensearch_client.py
new file mode 100644
index 0000000000..70328df638
--- /dev/null
+++ b/dbrepo-search-service/init/test/test_opensearch_client.py
@@ -0,0 +1,206 @@
+import unittest
+
+import opensearchpy
+import os
+from dbrepo.api.dto import Database, Table, Column, ColumnType, Constraints, PrimaryKey, \
+    ConceptBrief, UnitBrief, UserBrief, ContainerBrief, ImageBrief, TableBrief, ColumnBrief
+from opensearchpy import NotFoundError
+
+from clients.opensearch_client import OpenSearchClient
+
+req = Database(id=1,
+               name="Test",
+               internal_name="test_tuw1",
+               owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
+               contact=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
+               exchange_name="dbrepo",
+               is_public=True,
+               is_schema_public=True,
+               container=ContainerBrief(id=1,
+                                        name="MariaDB",
+                                        internal_name="mariadb",
+                                        host="data-db",
+                                        image=ImageBrief(id=1,
+                                                         name="mariadb",
+                                                         version="11.1.3",
+                                                         jdbc_method="mariadb")),
+               tables=[Table(id=1,
+                             database_id=1,
+                             name="Data",
+                             internal_name="data",
+                             owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
+                             constraints=Constraints(uniques=[], foreign_keys=[], checks=[], primary_key=[]),
+                             is_versioned=False,
+                             queue_name="dbrepo",
+                             routing_key="dbrepo.1.1",
+                             is_public=True,
+                             is_schema_public=True,
+                             columns=[Column(id=1,
+                                             database_id=1,
+                                             table_id=1,
+                                             name="ID",
+                                             ord=0,
+                                             internal_name="id",
+                                             type=ColumnType.BIGINT,
+                                             is_null_allowed=False,
+                                             size=20,
+                                             d=0,
+                                             concept=ConceptBrief(id=1, uri="http://www.wikidata.org/entity/Q2221906"),
+                                             unit=UnitBrief(id=1,
+                                                            uri="http://www.ontology-of-units-of-measure.org/resource/om-2/degreeCelsius"),
+                                             val_min=0,
+                                             val_max=10)]
+                             )])
+
+
+class OpenSearchClientTest(unittest.TestCase):
+
+    def test_update_database_succeeds(self):
+        req.tables = [Table(id=1,
+                            name="Test Table",
+                            internal_name="test_table",
+                            queue_name="dbrepo",
+                            routing_key="dbrepo.test_tuw1.test_table",
+                            is_public=True,
+                            is_schema_public=True,
+                            database_id=req.id,
+                            constraints=Constraints(uniques=[], foreign_keys=[], checks=[],
+                                                    primary_key=[PrimaryKey(id=1,
+                                                                            table=TableBrief(id=1,
+                                                                                             database_id=req.id,
+                                                                                             name="Test Table",
+                                                                                             internal_name="test_table",
+                                                                                             is_public=True,
+                                                                                             is_schema_public=True,
+                                                                                             is_versioned=True,
+                                                                                             owned_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502"),
+                                                                            column=ColumnBrief(id=1,
+                                                                                               name="ID",
+                                                                                               database_id=req.id,
+                                                                                               table_id=1,
+                                                                                               internal_name="id",
+                                                                                               type=ColumnType.BIGINT))]),
+                            is_versioned=True,
+                            owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
+                            columns=[Column(id=1,
+                                            database_id=req.id,
+                                            table_id=1,
+                                            ord=0,
+                                            name="ID",
+                                            internal_name="id",
+                                            type=ColumnType.BIGINT,
+                                            is_null_allowed=False)])]
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        database = OpenSearchClient().update_database(database_id=req.id, data=req)
+        self.assertEqual(1, database.id)
+        self.assertEqual("Test", database.name)
+        self.assertEqual("test_tuw1", database.internal_name)
+        self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.owner.id)
+        self.assertEqual("foo", database.owner.username)
+        self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.contact.id)
+        self.assertEqual("foo", database.contact.username)
+        self.assertEqual("dbrepo", database.exchange_name)
+        self.assertEqual(True, database.is_public)
+        self.assertEqual(1, database.container.id)
+        # ...
+        self.assertEqual(1, database.container.image.id)
+        # ...
+        self.assertEqual(1, len(database.tables))
+        self.assertEqual(1, database.tables[0].id)
+        self.assertEqual("Test Table", database.tables[0].name)
+        self.assertEqual("test_table", database.tables[0].internal_name)
+        self.assertEqual("dbrepo", database.tables[0].queue_name)
+        self.assertEqual("dbrepo.test_tuw1.test_table", database.tables[0].routing_key)
+        self.assertEqual(True, database.tables[0].is_public)
+        self.assertEqual(1, database.tables[0].database_id)
+        self.assertEqual(True, database.tables[0].is_versioned)
+        self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.tables[0].owner.id)
+        self.assertEqual("foo", database.tables[0].owner.username)
+        self.assertEqual(1, len(database.tables[0].columns))
+        self.assertEqual(1, database.tables[0].columns[0].id)
+        self.assertEqual("ID", database.tables[0].columns[0].name)
+        self.assertEqual("id", database.tables[0].columns[0].internal_name)
+        self.assertEqual(ColumnType.BIGINT, database.tables[0].columns[0].type)
+        self.assertEqual(1, database.tables[0].columns[0].database_id)
+        self.assertEqual(1, database.tables[0].columns[0].table_id)
+        self.assertEqual(False, database.tables[0].columns[0].is_null_allowed)
+
+    def test_update_database_create_succeeds(self):
+        # test
+        database = OpenSearchClient().update_database(database_id=req.id, data=req)
+        self.assertEqual(1, database.id)
+        self.assertEqual("Test", database.name)
+        self.assertEqual("test_tuw1", database.internal_name)
+        self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.owner.id)
+        self.assertEqual("foo", database.owner.username)
+        self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.contact.id)
+        self.assertEqual("foo", database.contact.username)
+        self.assertEqual("dbrepo", database.exchange_name)
+        self.assertEqual(True, database.is_public)
+        self.assertEqual(1, database.container.id)
+        # ...
+        self.assertEqual(1, database.container.image.id)
+        # ...
+        self.assertEqual(1, len(database.tables))
+
+    def test_update_database_malformed_fails(self):
+        os.environ['OPENSEARCH_USERNAME'] = 'i_do_not_exist'
+
+        # test
+        try:
+            database = OpenSearchClient().update_database(database_id=req.id, data=req)
+        except opensearchpy.exceptions.TransportError:
+            pass
+
+    def test_delete_database_fails(self):
+
+        # test
+        try:
+            OpenSearchClient().delete_database(database_id=9999)
+        except opensearchpy.exceptions.NotFoundError:
+            pass
+
+    def test_delete_database_succeeds(self):
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        OpenSearchClient().delete_database(database_id=req.id)
+
+    def test_get_fields_for_index_database_succeeds(self):
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        response = OpenSearchClient().get_fields_for_index(field_type="database")
+        self.assertTrue(len(response) > 0)
+
+    def test_get_fields_for_index_user_succeeds(self):
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        response = OpenSearchClient().get_fields_for_index(field_type="user")
+        self.assertTrue(len(response) > 0)
+
+    def test_fuzzy_search_succeeds(self):
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        OpenSearchClient().fuzzy_search(search_term="test_tuw")
+
+    def test_unit_independent_search_fails(self):
+        # mock
+        OpenSearchClient().update_database(database_id=req.id, data=req)
+
+        # test
+        try:
+            OpenSearchClient().unit_independent_search(0, 100, {
+                "unit.uri": "http://www.ontology-of-units-of-measure.org/resource/om-2/degreeCelsius"})
+            self.fail()
+        except NotFoundError:
+            pass
diff --git a/dbrepo-search-service/test.sh b/dbrepo-search-service/test.sh
deleted file mode 100644
index 40328cd5dd..0000000000
--- a/dbrepo-search-service/test.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-PIPENV_PIPFILE=./dbrepo-search-service/Pipfile
-source ./dbrepo-search-service/venv/bin/activate
-pip install pipenv
-pipenv install gunicorn && pipenv install --dev --system --deploy
-cd ./dbrepo-search-service/ && coverage run -m pytest test/test_app.py test/test_jwt.py test/test_opensearch_client.py test/test_keycloak_client.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
-cat ./coverage.txt | grep -o 'TOTAL[^%]*%'
\ No newline at end of file
diff --git a/dbrepo-search-service/test/test_keycloak_client.py b/dbrepo-search-service/test/test_keycloak_client.py
deleted file mode 100644
index 453a9b802b..0000000000
--- a/dbrepo-search-service/test/test_keycloak_client.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import time
-import unittest
-
-import jwt
-import requests_mock
-
-from app import app
-from clients.keycloak_client import KeycloakClient
-
-
-class JwtTest(unittest.TestCase):
-
-    def response(self, username) -> dict:
-        return dict({
-            "client_id": username,
-            "access_token": "eyEY1234"
-        })
-
-    def token(self, username: str, roles: [str], iat: int = int(time.time())) -> str:
-        claims = {
-            'iat': iat,
-            'client_id': username,
-            'realm_access': {
-                'roles': roles
-            }
-        }
-        with open('test/rsa/rs256.key', 'rb') as fh:
-            return jwt.JWT().encode(claims, jwt.jwk_from_pem(fh.read()), alg='RS256')
-
-    def test_obtain_user_token_succeeds(self):
-        with app.app_context():
-            with requests_mock.Mocker() as mock:
-                # mock
-                mock.post('http://auth-service:8080/api/auth/realms/dbrepo/protocol/openid-connect/token',
-                          json=self.response("username"))
-                # test
-                token = KeycloakClient().obtain_user_token("username", "password")
-                self.assertEqual("eyEY1234", token)
-
-    def test_obtain_user_token_malformed_fails(self):
-        with app.app_context():
-            with requests_mock.Mocker() as mock:
-                # mock
-                mock.post('http://auth-service:8080/api/auth/realms/dbrepo/protocol/openid-connect/token',
-                          json={"client_id": "username"})
-                # test
-                try:
-                    KeycloakClient().obtain_user_token("username", "password")
-                    self.fail()
-                except AssertionError:
-                    pass
-
-    def test_verify_jwt_succeeds(self):
-        with app.app_context():
-            # test
-            user = KeycloakClient().verify_jwt(self.token("username", []))
-            self.assertEqual("username", user.username)
diff --git a/dbrepo-search-service/test/test_opensearch_client.py b/dbrepo-search-service/test/test_opensearch_client.py
deleted file mode 100644
index edbdff683d..0000000000
--- a/dbrepo-search-service/test/test_opensearch_client.py
+++ /dev/null
@@ -1,215 +0,0 @@
-import unittest
-
-import opensearchpy
-from dbrepo.api.dto import Database, Table, Column, ColumnType, Constraints, PrimaryKey, \
-    ConceptBrief, UnitBrief, UserBrief, ContainerBrief, ImageBrief, TableBrief, ColumnBrief
-from opensearchpy import NotFoundError
-
-from app import app
-from init.clients.opensearch_client import OpenSearchClient
-
-req = Database(id=1,
-               name="Test",
-               internal_name="test_tuw1",
-               owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
-               contact=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
-               exchange_name="dbrepo",
-               is_public=True,
-               is_schema_public=True,
-               container=ContainerBrief(id=1,
-                                        name="MariaDB",
-                                        internal_name="mariadb",
-                                        host="data-db",
-                                        image=ImageBrief(id=1,
-                                                         name="mariadb",
-                                                         version="11.1.3",
-                                                         jdbc_method="mariadb")),
-               tables=[Table(id=1,
-                             database_id=1,
-                             name="Data",
-                             internal_name="data",
-                             owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
-                             constraints=Constraints(uniques=[], foreign_keys=[], checks=[], primary_key=[]),
-                             is_versioned=False,
-                             queue_name="dbrepo",
-                             routing_key="dbrepo.1.1",
-                             is_public=True,
-                             is_schema_public=True,
-                             columns=[Column(id=1,
-                                             database_id=1,
-                                             table_id=1,
-                                             name="ID",
-                                             ord=0,
-                                             internal_name="id",
-                                             type=ColumnType.BIGINT,
-                                             is_null_allowed=False,
-                                             size=20,
-                                             d=0,
-                                             concept=ConceptBrief(id=1, uri="http://www.wikidata.org/entity/Q2221906"),
-                                             unit=UnitBrief(id=1,
-                                                            uri="http://www.ontology-of-units-of-measure.org/resource/om-2/degreeCelsius"),
-                                             val_min=0,
-                                             val_max=10)]
-                             )])
-
-
-class OpenSearchClientTest(unittest.TestCase):
-
-    def test_update_database_succeeds(self):
-        with app.app_context():
-            req.tables = [Table(id=1,
-                                name="Test Table",
-                                internal_name="test_table",
-                                queue_name="dbrepo",
-                                routing_key="dbrepo.test_tuw1.test_table",
-                                is_public=True,
-                                is_schema_public=True,
-                                database_id=req.id,
-                                constraints=Constraints(uniques=[], foreign_keys=[], checks=[],
-                                                        primary_key=[PrimaryKey(id=1,
-                                                                                table=TableBrief(id=1,
-                                                                                                 database_id=req.id,
-                                                                                                 name="Test Table",
-                                                                                                 internal_name="test_table",
-                                                                                                 is_public=True,
-                                                                                                 is_schema_public=True,
-                                                                                                 is_versioned=True,
-                                                                                                 owned_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502"),
-                                                                                column=ColumnBrief(id=1,
-                                                                                                   name="ID",
-                                                                                                   database_id=req.id,
-                                                                                                   table_id=1,
-                                                                                                   internal_name="id",
-                                                                                                   type=ColumnType.BIGINT))]),
-                                is_versioned=True,
-                                owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"),
-                                columns=[Column(id=1,
-                                                database_id=req.id,
-                                                table_id=1,
-                                                ord=0,
-                                                name="ID",
-                                                internal_name="id",
-                                                type=ColumnType.BIGINT,
-                                                is_null_allowed=False)])]
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            database = OpenSearchClient().update_database(database_id=req.id, data=req)
-            self.assertEqual(1, database.id)
-            self.assertEqual("Test", database.name)
-            self.assertEqual("test_tuw1", database.internal_name)
-            self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.owner.id)
-            self.assertEqual("foo", database.owner.username)
-            self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.contact.id)
-            self.assertEqual("foo", database.contact.username)
-            self.assertEqual("dbrepo", database.exchange_name)
-            self.assertEqual(True, database.is_public)
-            self.assertEqual(1, database.container.id)
-            # ...
-            self.assertEqual(1, database.container.image.id)
-            # ...
-            self.assertEqual(1, len(database.tables))
-            self.assertEqual(1, database.tables[0].id)
-            self.assertEqual("Test Table", database.tables[0].name)
-            self.assertEqual("test_table", database.tables[0].internal_name)
-            self.assertEqual("dbrepo", database.tables[0].queue_name)
-            self.assertEqual("dbrepo.test_tuw1.test_table", database.tables[0].routing_key)
-            self.assertEqual(True, database.tables[0].is_public)
-            self.assertEqual(1, database.tables[0].database_id)
-            self.assertEqual(True, database.tables[0].is_versioned)
-            self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.tables[0].owner.id)
-            self.assertEqual("foo", database.tables[0].owner.username)
-            self.assertEqual(1, len(database.tables[0].columns))
-            self.assertEqual(1, database.tables[0].columns[0].id)
-            self.assertEqual("ID", database.tables[0].columns[0].name)
-            self.assertEqual("id", database.tables[0].columns[0].internal_name)
-            self.assertEqual(ColumnType.BIGINT, database.tables[0].columns[0].type)
-            self.assertEqual(1, database.tables[0].columns[0].database_id)
-            self.assertEqual(1, database.tables[0].columns[0].table_id)
-            self.assertEqual(False, database.tables[0].columns[0].is_null_allowed)
-
-    def test_update_database_create_succeeds(self):
-        with app.app_context():
-            # test
-            database = OpenSearchClient().update_database(database_id=req.id, data=req)
-            self.assertEqual(1, database.id)
-            self.assertEqual("Test", database.name)
-            self.assertEqual("test_tuw1", database.internal_name)
-            self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.owner.id)
-            self.assertEqual("foo", database.owner.username)
-            self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.contact.id)
-            self.assertEqual("foo", database.contact.username)
-            self.assertEqual("dbrepo", database.exchange_name)
-            self.assertEqual(True, database.is_public)
-            self.assertEqual(1, database.container.id)
-            # ...
-            self.assertEqual(1, database.container.image.id)
-            # ...
-            self.assertEqual(1, len(database.tables))
-
-    def test_update_database_malformed_fails(self):
-        with app.app_context():
-            app.config['OPENSEARCH_USERNAME'] = 'i_do_not_exist'
-
-            # test
-            try:
-                database = OpenSearchClient().update_database(database_id=req.id, data=req)
-            except opensearchpy.exceptions.TransportError:
-                pass
-
-    def test_delete_database_fails(self):
-        with app.app_context():
-
-            # test
-            try:
-                OpenSearchClient().delete_database(database_id=9999)
-            except opensearchpy.exceptions.NotFoundError:
-                pass
-
-    def test_delete_database_succeeds(self):
-        with app.app_context():
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            OpenSearchClient().delete_database(database_id=req.id)
-
-    def test_get_fields_for_index_database_succeeds(self):
-        with app.app_context():
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            response = OpenSearchClient().get_fields_for_index(field_type="database")
-            self.assertTrue(len(response) > 0)
-
-    def test_get_fields_for_index_user_succeeds(self):
-        with app.app_context():
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            response = OpenSearchClient().get_fields_for_index(field_type="user")
-            self.assertTrue(len(response) > 0)
-
-    def test_fuzzy_search_succeeds(self):
-        with app.app_context():
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            OpenSearchClient().fuzzy_search(search_term="test_tuw")
-
-    def test_unit_independent_search_fails(self):
-        with app.app_context():
-            # mock
-            OpenSearchClient().update_database(database_id=req.id, data=req)
-
-            # test
-            try:
-                OpenSearchClient().unit_independent_search(0, 100, {
-                    "unit.uri": "http://www.ontology-of-units-of-measure.org/resource/om-2/degreeCelsius"})
-                self.fail()
-            except NotFoundError:
-                pass
-- 
GitLab