Skip to content
Snippets Groups Projects
Verified Commit d813afb7 authored by Martin Weise's avatar Martin Weise
Browse files

Merge branch 'dev' of gitlab.phaidra.org:fair-data-austria-db-repository/fda-services into dev

parents 4d0ac7d0 2af0f10f
Branches
Tags
5 merge requests!345Updated docs and endpoints:,!341Fixed mapping problem where UK and FK share columns they are inserted,!339Fixed mapping problem where UK and FK share columns they are inserted,!338Fixed mapping problem where UK and FK share columns they are inserted,!334Fixed mapping problem where UK and FK share columns they are inserted
Showing
with 730 additions and 182 deletions
......@@ -266,7 +266,7 @@ test-search-service:
script:
- "pip install pipenv"
- "pipenv install gunicorn && pipenv install --dev --system --deploy"
- cd ./dbrepo-search-service/ && coverage run -m pytest test/test_opensearch_client.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
- 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[^%]*%'"
artifacts:
when: always
......@@ -290,7 +290,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 --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
- "cat ./coverage.txt | grep -o 'TOTAL[^%]*%'"
artifacts:
when: always
......
......@@ -2,3 +2,4 @@
omit =
*/test/*
*/omlib/*
*/init/*
\ No newline at end of file
......@@ -9,8 +9,8 @@ __pycache__/
# Generated
coverage.txt
report.xml
^clients/
^omlib/
clients/
omlib/
# Libraries
./lib/dbrepo-1.4.4*
......
......@@ -24,6 +24,7 @@ gunicorn = "*"
[dev-packages]
coverage = "*"
pytest = "*"
requests-mock = "*"
[requires]
python_version = "3.11"
{
"_meta": {
"hash": {
"sha256": "e82f3e09ac14fafd1be12c5b57dfcbfafecfd7c8ac2d098ce101264da2d22d42"
"sha256": "491e5f6ada48e8af417dfa7d6a0b4d98ccf9b9072df53b44d8de014b687fc80c"
},
"pipfile-spec": 6,
"requires": {
......@@ -390,7 +390,6 @@
"hashes": [
"sha256:84607677b0826bb9b2fa120aacdf56d16c8d9ae423f435b2bd2c22b1c965a33c"
],
"markers": "python_version >= '3.11'",
"path": "./lib/dbrepo-1.4.7.tar.gz"
},
"docker": {
......@@ -616,7 +615,7 @@
"sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79",
"sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"
],
"markers": "python_version < '3.13' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
"markers": "python_version < '3.13' and (platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))))",
"version": "==3.1.1"
},
"gunicorn": {
......@@ -1265,7 +1264,7 @@
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==2.9.0.post0"
},
"python-dotenv": {
......@@ -1482,7 +1481,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"sqlalchemy": {
......@@ -1794,6 +1793,125 @@
}
},
"develop": {
"certifi": {
"hashes": [
"sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
"sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
],
"markers": "python_version >= '3.6'",
"version": "==2024.8.30"
},
"charset-normalizer": {
"hashes": [
"sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621",
"sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6",
"sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8",
"sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912",
"sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c",
"sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b",
"sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d",
"sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d",
"sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95",
"sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e",
"sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565",
"sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64",
"sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab",
"sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be",
"sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e",
"sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907",
"sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0",
"sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2",
"sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62",
"sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62",
"sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23",
"sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc",
"sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284",
"sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca",
"sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455",
"sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858",
"sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b",
"sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594",
"sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc",
"sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db",
"sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b",
"sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea",
"sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6",
"sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920",
"sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749",
"sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7",
"sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd",
"sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99",
"sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242",
"sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee",
"sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129",
"sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2",
"sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51",
"sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee",
"sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8",
"sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b",
"sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613",
"sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742",
"sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe",
"sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3",
"sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5",
"sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631",
"sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7",
"sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15",
"sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c",
"sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea",
"sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417",
"sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250",
"sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88",
"sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca",
"sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa",
"sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99",
"sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149",
"sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41",
"sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574",
"sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0",
"sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f",
"sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d",
"sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654",
"sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3",
"sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19",
"sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90",
"sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578",
"sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9",
"sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1",
"sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51",
"sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719",
"sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236",
"sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a",
"sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c",
"sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade",
"sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944",
"sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc",
"sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6",
"sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6",
"sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27",
"sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6",
"sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2",
"sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12",
"sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf",
"sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114",
"sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7",
"sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf",
"sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d",
"sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b",
"sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed",
"sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03",
"sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4",
"sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67",
"sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365",
"sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a",
"sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748",
"sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b",
"sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079",
"sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.4.0"
},
"coverage": {
"hashes": [
"sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6",
......@@ -1863,6 +1981,14 @@
"markers": "python_version >= '3.9'",
"version": "==7.6.3"
},
"idna": {
"hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
"sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
],
"markers": "python_version >= '3.6'",
"version": "==3.10"
},
"iniconfig": {
"hashes": [
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
......@@ -1895,6 +2021,31 @@
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==8.3.3"
},
"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:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac",
"sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"
],
"markers": "python_version >= '3.10'",
"version": "==2.2.3"
}
}
}
......@@ -2,6 +2,7 @@ import math
import os
import logging
from ast import literal_eval
from json import dumps
from typing import List, Any
import requests
......@@ -10,6 +11,7 @@ from flasgger import LazyJSONEncoder, Swagger, swag_from
from flask import Flask, request
from flask_cors import CORS
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
from jwt.exceptions import JWTDecodeError
from opensearchpy import TransportError, NotFoundError
from prometheus_flask_exporter import PrometheusMetrics
from pydantic import ValidationError
......@@ -206,9 +208,6 @@ app.config["OPENSEARCH_PASSWORD"] = os.getenv('OPENSEARCH_PASSWORD', 'admin')
app.json_encoder = LazyJSONEncoder
available_types = literal_eval(
os.getenv("COLLECTION", "['database','table','column','identifier','unit','concept','user','view']"))
@token_auth.verify_token
def verify_token(token: str):
......@@ -217,7 +216,7 @@ def verify_token(token: str):
try:
client = KeycloakClient()
return client.verify_jwt(access_token=token)
except AssertionError:
except JWTDecodeError as error:
return False
......@@ -268,8 +267,7 @@ def general_filter(index, results):
"view": ["id", "name", "creator", " created"],
}
if index not in important_keys.keys():
error_msg = "the keys to be returned to the user for your index aren't specified in the important Keys dict"
raise KeyError(error_msg)
raise KeyError(f"Failed to find index {index} in: {important_keys.keys()}")
for result in results:
result_keys_copy = tuple(result.keys())
for key in result_keys_copy:
......@@ -294,10 +292,8 @@ def get_index(index: str):
:return: list of the results
"""
logging.info(f'Searching for index: {index}')
if index not in available_types:
return ApiError(status='NOT_FOUND', message='Failed to find index',
code='search.index.missing').model_dump(), 404
results = OpenSearchClient().query_index_by_term_opensearch("*", "contains")
try:
results = general_filter(index, results)
results_per_page = min(request.args.get("results_per_page", 50, type=int), 500)
......@@ -305,24 +301,28 @@ def get_index(index: str):
page = min(request.args.get("page", 1, type=int), max_pages)
results = results[(results_per_page * (page - 1)): (results_per_page * page)]
return dict({"results": results}), 200
except KeyError:
return ApiError(status='NOT_FOUND', message=f'Failed to find get index: {index}',
code='search.index.missing').model_dump(), 404
@app.route("/api/search/<string:type>/fields", methods=["GET"], endpoint="search_get_index_fields")
@app.route("/api/search/<string:field_type>/fields", methods=["GET"], endpoint="search_get_index_fields")
@metrics.gauge(name='dbrepo_search_type_list', description='Time needed to list search types')
@swag_from("os-yml/get_fields.yml")
def get_fields(type: str):
def get_fields(field_type: str):
"""
returns a list of attributes of the data for a specific index.
:param type: The search type
:param field_type: The search type
:return:
"""
logging.info(f'Searching in index database for type: {type}')
if type not in available_types:
return ApiError(status='NOT_FOUND', message='Failed to find type',
code='search.type.missing').model_dump(), 404
fields = OpenSearchClient().get_fields_for_index(type)
logging.debug(f'get fields for type {type} resulted in {len(fields)} field(s)')
logging.info(f'Searching in index database for type: {field_type}')
try:
fields = OpenSearchClient().get_fields_for_index(field_type)
logging.debug(f'get fields for field_type {field_type} resulted in {len(fields)} field(s)')
return fields, 200
except NotFoundError:
return ApiError(status='NOT_FOUND', message=f'Failed to find fields for search type {field_type}',
code='search.type.missing').model_dump(), 404
@app.route("/api/search", methods=["GET"], endpoint="search_fuzzy_search")
......@@ -344,10 +344,10 @@ def get_fuzzy_search():
return dict({"results": results}), 200
@app.route("/api/search/<string:type>", methods=["POST"], endpoint="search_post_general_search")
@app.route("/api/search/<string:field_type>", methods=["POST"], endpoint="search_post_general_search")
@metrics.gauge(name='dbrepo_search_type', description='Time needed to search by type')
@swag_from("os-yml/post_general_search.yml")
def post_general_search(type):
def post_general_search(field_type):
"""
Main endpoint for fuzzy searching.
:return:
......@@ -356,11 +356,7 @@ def post_general_search(type):
return ApiError(status='UNSUPPORTED_MEDIA_TYPE', message='Content type needs to be application/json',
code='search.general.media').model_dump(), 415
req_body = request.json
logging.info(f'Searching in index database for type: {type}')
logging.debug(f"search request body: {req_body}")
if type is not None and type not in available_types:
return ApiError(status='NOT_FOUND', message=f'Type {type} is not in collection: {available_types}',
code='search.general.missing').model_dump(), 404
logging.info(f'Searching in index database for type: {field_type}')
t1 = request.args.get("t1")
if not str(t1).isdigit():
t1 = None
......@@ -370,9 +366,9 @@ def post_general_search(type):
if t1 is not None and t2 is not None and "unit.uri" in req_body and "concept.uri" in req_body:
response = OpenSearchClient().unit_independent_search(t1, t2, req_body)
else:
response = OpenSearchClient().general_search(type, req_body)
response = OpenSearchClient().general_search(field_type, req_body)
# filter by type
if type == 'table':
if field_type == 'table':
tmp = []
for database in response:
if database["tables"] is not None:
......@@ -380,7 +376,7 @@ def post_general_search(type):
table["is_public"] = database["is_public"]
tmp.append(table)
response = tmp
if type == 'identifier':
if field_type == 'identifier':
tmp = []
for database in response:
if database["identifiers"] is not None:
......@@ -398,30 +394,30 @@ def post_general_search(type):
if 'identifier' in view:
tmp.append(view['identifier'])
response = tmp
elif type == 'column':
elif field_type == 'column':
response = [x for xs in response for x in xs["tables"]]
for table in response:
for column in table["columns"]:
column["table_id"] = table["id"]
column["database_id"] = table["database_id"]
response = [x for xs in response for x in xs["columns"]]
elif type == 'concept':
elif field_type == 'concept':
tmp = []
tables = [x for xs in response for x in xs["tables"]]
for column in [x for xs in tables for x in xs["columns"]]:
if 'concept' in column and column["concept"] is not None:
tmp.append(column["concept"])
response = tmp
elif type == 'unit':
elif field_type == 'unit':
tmp = []
tables = [x for xs in response for x in xs["tables"]]
for column in [x for xs in tables for x in xs["columns"]]:
if 'unit' in column and column["unit"] is not None:
tmp.append(column["unit"])
response = tmp
elif type == 'view':
elif field_type == 'view':
response = [x for xs in response for x in xs["views"]]
return dict({'results': response, 'type': type}), 200
return dict({'results': response, 'type': field_type}), 200
@app.route("/api/search/database/<int:database_id>", methods=["PUT"], endpoint="search_put_database")
......@@ -436,16 +432,9 @@ def update_database(database_id: int) -> Database | ApiError:
logging.error(f"Failed to validate: {e}")
return ApiError(status='BAD_REQUEST', message=f'Malformed payload: {e}',
code='search.general.missing').model_dump(), 400
try:
database = OpenSearchClient().update_database(database_id, payload)
logging.info(f"Updated database with id : {database_id}")
return database.model_dump(), 202
except NotFoundError:
return ApiError(status='NOT_FOUND', message='Failed to find database',
code='search.database.missing').model_dump(), 404
except TransportError:
return ApiError(status='BAD_REQUEST', message='Failed to update database',
code='search.database.invalid').model_dump(), 400
@app.route("/api/search/database/<int:database_id>", methods=["DELETE"], endpoint="database_delete_database")
......@@ -455,7 +444,7 @@ def update_database(database_id: int) -> Database | ApiError:
def delete_database(database_id: int):
try:
OpenSearchClient().delete_database(database_id)
return None, 202
return dumps({}), 202
except NotFoundError:
return ApiError(status='NOT_FOUND', message='Failed to find database',
code='search.database.missing').model_dump(), 404
......@@ -8,7 +8,7 @@ import logging
from dbrepo.api.dto import Database
from collections.abc import MutableMapping
from opensearchpy import OpenSearch, TransportError, RequestError
from opensearchpy import OpenSearch, TransportError, RequestError, NotFoundError
from omlib.measure import om
from omlib.constants import OM_IDS
......@@ -67,16 +67,8 @@ class OpenSearchClient:
@throws: opensearchpy.exceptions.NotFoundError If the database was not found in the Search Database.
"""
logging.debug(f"updating database with id: {database_id} in search database")
try:
self._instance().index(index="database", id=database_id, body=dumps(data.model_dump()))
except RequestError as e:
logging.error(f"Failed to update in search database: {e.info}")
raise e
try:
response: dict = self._instance().get(index="database", id=database_id)
except TransportError as e:
logging.error(f"Failed to get updated database in search database: {e.status_code}")
raise e
database = Database.parse_obj(response["_source"])
logging.info(f"Updated database with id {database_id} in index 'database'")
return database
......@@ -118,10 +110,10 @@ class OpenSearchClient:
results = [hit["_source"] for hit in response["hits"]["hits"]]
return results
def get_fields_for_index(self, type: str):
def get_fields_for_index(self, field_type: str):
"""
returns a list of attributes of the data for a specific index.
:param type: The search type
:param field_type: The search type
:return: list of fields
"""
fields = {
......@@ -134,8 +126,10 @@ class OpenSearchClient:
"view": "views.*",
"user": "creator.*",
}
logging.debug(f'requesting field(s) {fields[type]} for filter: {type}')
fields = self._instance().indices.get_field_mapping(fields[type])
if field_type not in fields.keys():
raise NotFoundError(f"Failed to find field type: {field_type}")
logging.debug(f'requesting field(s) {fields[field_type]} for filter: {field_type}')
fields = self._instance().indices.get_field_mapping(fields[field_type])
fields_list = []
fd = flatten_dict(fields)
for key in fd.keys():
......@@ -169,13 +163,13 @@ class OpenSearchClient:
logging.info(f"Found {len(response['hits']['hits'])} result(s)")
return response
def general_search(self, type: str = None, field_value_pairs: dict = None):
def general_search(self, field_type: str = None, field_value_pairs: dict = None):
"""
Main method for searching stuff in the opensearch db
all parameters are optional
:param type: The index to be searched. Optional.
:param field_type: The index to be searched. Optional.
:param field_value_pairs: The key-value pair of properties that need to match. Optional.
:return: The object of results and HTTP status code. e.g. { "hits": { "hits": [] } }, 200
"""
......@@ -204,7 +198,7 @@ class OpenSearchClient:
body = {
"query": {"bool": {"must": musts}}
}
logging.debug(f'search in index database for type: {type}')
logging.debug(f'search in index database for type: {field_type}')
logging.debug(f'search body: {dumps(body)}')
response = self._instance().search(
index="database",
......@@ -213,12 +207,10 @@ class OpenSearchClient:
results = [hit["_source"] for hit in response["hits"]["hits"]]
return results
def unit_independent_search(self, t1=None, t2=None, field_value_pairs=None):
def unit_independent_search(self, t1: float, t2: float, field_value_pairs):
"""
Main method for searching stuff in the opensearch db
all parameters are optional
:param t1: start value
:param t2: end value
:param field_value_pairs: the key-value pairs
......@@ -240,6 +232,8 @@ class OpenSearchClient:
)
unit_uris = [hit["key"] for hit in response["aggregations"]["units"]["buckets"]]
logging.debug(f"found {len(unit_uris)} unit(s) in column index")
if len(unit_uris) == 0:
raise NotFoundError("Failed to search: no unit assigned")
base_unit = unit_uri_to_unit(field_value_pairs["unit.uri"])
for unit_uri in unit_uris:
gte = t1
......
#!/bin/bash
cd ./dbrepo-search-service
pip install pipenv
pipenv install --dev
coverage run -m pytest ./test/test_opensearch_client.py
echo "[INFO] Start testing ./init"
(cd ./init && pipenv install --dev)
coverage run -m pytest ./init/test/test_app.py
\ No newline at end of file
import logging
import os
import pytest
import os
import json
from testcontainers.opensearch import OpenSearchContainer
......@@ -30,20 +31,17 @@ def session(request):
request.addfinalizer(stop_opensearch)
return container
# @pytest.fixture(scope="function", autouse=True)
# def cleanup(request, session):
# """
# Clean up after each test by removing the buckets and re-adding them (=so they are empty again)
# :param request: /
# :param session: /
# :return:
# """
# logging.info("[fixture] truncate buckets")
# for bucket in ["dbrepo-upload", "dbrepo-download"]:
# objects = []
# for obj in session.get_client().list_objects(bucket):
# objects.append(DeleteObject(obj.object_name))
# logging.info(f'request to remove objects {objects}')
# errors = session.get_client().remove_objects(bucket, objects)
# for error in errors:
# raise ConnectionError(f'Failed to delete object with key {error.object_name} of bucket {bucket}')
@pytest.fixture(scope="function", autouse=True)
def cleanup(request, session):
"""
Clean up after each test by removing the index and re-adding it (=so it's empty again)
:param request: /
:param session: /
:return:
"""
logging.info("[fixture] clean schema")
with open('./init/database.json', 'r') as f:
if session.get_client().indices.exists(index="database"):
session.get_client().indices.delete(index="database")
session.get_client().indices.create(index="database", body=json.load(f))
-----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
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
-----END PUBLIC KEY-----
import json
import time
import unittest
import datetime
import jwt
from dbrepo.api.dto import Database, User, UserAttributes, Container, Image, Table, Constraints, Column, ColumnType, \
Concept, Unit
from app import app
req = Database(id=1,
name="Test",
internal_name="test_tuw1",
creator=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
username="foo",
attributes=UserAttributes(theme="dark")),
owner=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
username="foo",
attributes=UserAttributes(theme="dark")),
contact=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
username="foo",
attributes=UserAttributes(theme="dark")),
created=datetime.datetime(2024, 3, 25, 16, tzinfo=datetime.timezone.utc),
exchange_name="dbrepo",
is_public=True,
container=Container(id=1,
name="MariaDB",
internal_name="mariadb",
host="data-db",
port="3306",
created=datetime.datetime(2024, 3, 1, 10, tzinfo=datetime.timezone.utc),
sidecar_host="data-db-sidecar",
sidecar_port=3305,
image=Image(id=1,
registry="docker.io",
name="mariadb",
version="11.1.3",
dialect="org.hibernate.dialect.MariaDBDialect",
driver_class="org.mariadb.jdbc.Driver",
jdbc_method="mariadb",
default_port=3306)),
tables=[Table(id=1, database_id=1, name="Data", internal_name="data",
creator=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
username="foo",
attributes=UserAttributes(theme="dark")),
owner=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
username="foo",
attributes=UserAttributes(theme="dark")),
created=datetime.datetime(2024, 3, 1, 10, tzinfo=datetime.timezone.utc),
constraints=Constraints(uniques=[], foreign_keys=[], checks=[], primary_key=[]),
is_versioned=False,
created_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
queue_name="dbrepo",
routing_key="dbrepo.1.1",
is_public=True,
columns=[Column(id=1, database_id=1, table_id=1, name="ID", internal_name="id",
column_type=ColumnType.BIGINT, is_public=True, is_null_allowed=False,
size=20, d=0,
concept=Concept(id=1, uri="http://www.wikidata.org/entity/Q2221906",
created=datetime.datetime(2024, 3, 1, 10,
tzinfo=datetime.timezone.utc)),
unit=Unit(id=1,
uri="http://www.ontology-of-units-of-measure.org/resource/om-2/degreeCelsius",
created=datetime.datetime(2024, 3, 1, 10,
tzinfo=datetime.timezone.utc)),
val_min=0,
val_max=10)]
)])
class JwtTest(unittest.TestCase):
def token(self, roles: [str], iat: int = int(time.time())):
claims = {
'iat': iat,
'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_update_database_media_type_fails(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}'})
self.assertEqual(415, response.status_code)
def test_health_succeeds(self):
with app.test_client() as test_client:
# test
response = test_client.get('/health')
self.assertEqual(200, response.status_code)
def test_update_database_no_auth_fails(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1')
self.assertEqual(401, response.status_code)
def test_update_database_no_body_fails(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'})
self.assertEqual(400, response.status_code)
def test_update_database_empty_body_fails(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data={})
self.assertEqual(400, response.status_code)
def test_update_database_malformed_body_fails(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=dict({"id": 1}))
self.assertEqual(400, response.status_code)
def test_update_database_succeeds(self):
with app.test_client() as test_client:
# test
response = test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
self.assertEqual(202, response.status_code)
def test_get_fields_succeeds(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search/database/fields', headers={'Content-Type': 'application/json'})
self.assertEqual(200, response.status_code)
def test_get_fields_fails(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search/unknown/fields', headers={'Content-Type': 'application/json'})
self.assertEqual(404, response.status_code)
def test_delete_database_no_auth_fails(self):
with app.test_client() as test_client:
# test
response = test_client.delete('/api/search/database/1')
self.assertEqual(401, response.status_code)
def test_delete_database_no_role_fails(self):
with app.test_client() as test_client:
# test
response = test_client.delete('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token([])}'})
self.assertEqual(403, response.status_code)
def test_delete_database_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.delete('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["admin"])}'})
self.assertEqual(202, response.status_code)
def test_delete_database_not_found_fails(self):
with app.test_client() as test_client:
# test
response = test_client.delete('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["admin"])}'})
self.assertEqual(404, response.status_code)
def test_get_fuzzy_search_succeeds(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search?q=test')
self.assertEqual(200, response.status_code)
def test_get_fuzzy_search_no_query_fails(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search')
self.assertEqual(400, response.status_code)
def test_get_index_succeeds(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search/table')
self.assertEqual(200, response.status_code)
def test_get_index_fails(self):
with app.test_client() as test_client:
# test
response = test_client.get('/api/search/unknown')
self.assertEqual(404, response.status_code)
def test_post_general_search_media_type_fails(self):
with app.test_client() as test_client:
# test
response = test_client.post('/api/search/database')
self.assertEqual(415, response.status_code)
def test_post_general_search_no_body_fails(self):
with app.test_client() as test_client:
# test
response = test_client.post('/api/search/database', headers={'Content-Type': 'application/json'})
self.assertEqual(400, response.status_code)
def test_post_general_search_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/database', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_table_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/table', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_column_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/column', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_identifier_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/identifier', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_concept_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/concept', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_unit_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/unit', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
def test_post_general_search_view_succeeds(self):
with app.test_client() as test_client:
# mock
test_client.put('/api/search/database/1',
headers={'Authorization': f'Bearer {self.token(["update-search-index"])}',
'Content-Type': 'application/json'},
data=req.model_dump_json())
# test
response = test_client.post('/api/search/view', headers={'Content-Type': 'application/json'},
data=json.dumps({'id': 1}))
self.assertEqual(200, response.status_code)
import time
import unittest
import jwt
import requests_mock
from app import verify_token, app, verify_password, get_user_roles
from clients.keycloak_client import User
class JwtTest(unittest.TestCase):
def response(self, roles: [str]) -> dict:
return dict({
"client_id": "username",
"realm_access": {
"roles": roles
}
})
def token(self, roles: [str], iat: int = int(time.time())) -> str:
claims = {
'iat': iat,
'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_verify_token_no_token_fails(self):
with app.app_context():
# test
user = verify_token(None)
self.assertFalse(user)
def test_verify_token_empty_token_fails(self):
with app.app_context():
# test
user = verify_token("")
self.assertFalse(user)
def test_verify_token_malformed_token_fails(self):
with app.app_context():
# test
user = verify_token("eyEYEY12345")
self.assertFalse(user)
def test_verify_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([]))
# test
user = verify_token(self.token([]))
self.assertEqual([], user.roles)
def test_verify_password_no_username_fails(self):
with app.app_context():
# test
user = verify_password(None, "pass")
self.assertFalse(user)
def test_verify_password_empty_username_fails(self):
with app.app_context():
# test
user = verify_password("", "pass")
self.assertFalse(user)
def test_verify_password_no_password_fails(self):
with app.app_context():
# test
user = verify_password("username", None)
self.assertFalse(user)
def test_verify_password_empty_password_fails(self):
with app.app_context():
# test
user = verify_password("username", "")
self.assertFalse(user)
def test_verify_password_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([]))
# test
user = verify_password("username", "password")
self.assertIsNotNone(user)
def test_get_user_roles_succeeds(self):
with app.app_context():
# test
roles: [str] = get_user_roles(User(username="username", roles=[]))
self.assertEqual([], roles)
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)
......@@ -4,6 +4,7 @@ import unittest
import opensearchpy
from dbrepo.api.dto import Database, User, UserAttributes, Container, Image, Table, Column, ColumnType, Constraints, \
PrimaryKey, TableMinimal, ColumnMinimal, Concept, Unit
from opensearchpy import NotFoundError
from app import app
......@@ -73,9 +74,8 @@ class OpenSearchClientTest(unittest.TestCase):
def test_update_database_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=1, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
req.tables = [Table(id=1,
......@@ -87,9 +87,10 @@ class OpenSearchClientTest(unittest.TestCase):
database_id=req.id,
constraints=Constraints(uniques=[], foreign_keys=[], checks=[],
primary_key=[PrimaryKey(id=1,
table=TableMinimal(id=1, database_id=1),
table=TableMinimal(id=1,
database_id=req.id),
column=ColumnMinimal(id=1, table_id=1,
database_id=1))]),
database_id=req.id))]),
is_versioned=True,
created_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
creator=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502",
......@@ -107,7 +108,7 @@ class OpenSearchClientTest(unittest.TestCase):
column_type=ColumnType.BIGINT,
is_public=True,
is_null_allowed=False)])]
database = client.update_database(database_id=1, data=req)
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)
......@@ -157,10 +158,8 @@ class OpenSearchClientTest(unittest.TestCase):
def test_update_database_create_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# test
database = client.update_database(database_id=1, data=req)
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)
......@@ -185,124 +184,87 @@ class OpenSearchClientTest(unittest.TestCase):
def test_update_database_malformed_fails(self):
with app.app_context():
app.config['OPENSEARCH_USERNAME'] = 'i_do_not_exist'
client = OpenSearchClient()
# test
try:
database = client.update_database(database_id=1, data=req)
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():
client = OpenSearchClient()
# test
try:
client.delete_database(database_id=9999)
OpenSearchClient().delete_database(database_id=9999)
except opensearchpy.exceptions.NotFoundError:
pass
def test_delete_database_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=req.id, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
client.delete_database(database_id=req.id)
OpenSearchClient().delete_database(database_id=req.id)
def test_find_database_succeeds(self):
def test_get_database_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=req.id, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
client.get_database(database_id=req.id)
database = OpenSearchClient().get_database(database_id=req.id)
self.assertEqual(req.id, database.id)
def test_find_database_fails(self):
def test_get_database_fails(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=1, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
try:
client.get_database(database_id=1)
OpenSearchClient().get_database(database_id=req.id)
except opensearchpy.exceptions.NotFoundError:
pass
# def test_query_index_by_term_opensearch_contains_succeeds(self):
# with app.app_context():
# client = OpenSearchClient()
#
# # mock
# client.update_database(database_id=1, data=req)
#
# # test
# response = client.query_index_by_term_opensearch(term="test", mode="contains")
# self.assertEqual(1, len(response))
# self.assertEqual(1, response[0]['id'])
# self.assertEqual('Test', response[0]['name'])
# def test_query_index_by_term_opensearch_exact_succeeds(self):
# with app.app_context():
# client = OpenSearchClient()
#
# # mock
# client.update_database(database_id=1, data=req)
#
# # test
# response = client.query_index_by_term_opensearch(term="test", mode="exact")
# self.assertEqual(1, len(response))
# self.assertEqual(1, response[0]['id'])
# self.assertEqual('Test', response[0]['name'])
def test_get_fields_for_index_database_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=1, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
response = client.get_fields_for_index(type="database")
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():
client = OpenSearchClient()
# mock
client.update_database(database_id=1, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
response = client.get_fields_for_index(type="user")
response = OpenSearchClient().get_fields_for_index(field_type="user")
self.assertTrue(len(response) > 0)
def test_fuzzy_search_succeeds(self):
with app.app_context():
client = OpenSearchClient()
# mock
client.update_database(database_id=1, data=req)
OpenSearchClient().update_database(database_id=req.id, data=req)
# test
response = client.fuzzy_search(search_term="test")
response = OpenSearchClient().fuzzy_search(search_term="test")
self.assertTrue(len(response) > 0)
# def test_general_search_succeeds(self):
# with app.app_context():
# client = OpenSearchClient()
#
# # mock
# client.update_database(database_id=1, data=req)
#
# # test
# response = client.general_search(type="database", field_value_pairs={"name": "Test",
# "id": None})
# self.assertTrue(len(response) > 0)
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment