diff --git a/.gitignore b/.gitignore index a9134f20..96807427 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .pytest_cache -./data +/data dist venv output diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..b5a5096b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --ignore=src/opengeodeweb_back/ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index edae6731..eba5af2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,22 +10,22 @@ attrs==24.2.0 # via # jsonschema # referencing -blinker==1.8.2 +blinker==1.9.0 # via flask click==8.1.7 # via flask -flask[async]==3.0.3 +flask[async]==3.1.0 # via # -r requirements.in # flask-cors flask-cors==5.0.0 # via -r requirements.in -geode-background==8.7.0 +geode-background==8.7.5 # via # geode-explicit # geode-implicit # geode-simplex -geode-common==33.0.9 +geode-common==33.1.3 # via # -r requirements.in # geode-background @@ -35,26 +35,26 @@ geode-common==33.0.9 # geode-numerics # geode-simplex # geode-viewables -geode-conversion==6.0.14 +geode-conversion==6.0.18 # via # geode-explicit # geode-implicit -geode-explicit==6.1.14 +geode-explicit==6.1.17 # via # -r requirements.in # geode-implicit -geode-implicit==3.3.2 +geode-implicit==3.3.5 # via -r requirements.in -geode-numerics==5.1.8 +geode-numerics==5.2.0 # via # -r requirements.in # geode-implicit # geode-simplex -geode-simplex==8.2.15 +geode-simplex==8.2.18 # via # -r requirements.in # geode-implicit -geode-viewables==3.0.6 +geode-viewables==3.0.8 # via -r requirements.in itsdangerous==2.2.0 # via flask @@ -68,7 +68,7 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -opengeode-core==15.6.3 +opengeode-core==15.6.7 # via # -r requirements.in # geode-background @@ -83,23 +83,23 @@ opengeode-core==15.6.3 # opengeode-geosciencesio # opengeode-inspector # opengeode-io -opengeode-geosciences==8.1.1 +opengeode-geosciences==8.2.0 # via # -r requirements.in # geode-implicit # geode-viewables # opengeode-geosciencesio -opengeode-geosciencesio==5.2.3 +opengeode-geosciencesio==5.3.2 # via # -r requirements.in # geode-implicit -opengeode-inspector==6.1.12 +opengeode-inspector==6.1.15 # via # -r requirements.in # geode-explicit # geode-implicit # geode-simplex -opengeode-io==7.0.5 +opengeode-io==7.0.7 # via # -r requirements.in # geode-implicit @@ -115,7 +115,7 @@ rpds-py==0.21.0 # referencing typing-extensions==4.12.2 # via asgiref -werkzeug==3.1.2 +werkzeug==3.1.3 # via # -r requirements.in # flask diff --git a/src/opengeodeweb_back/geode_objects.py b/src/opengeodeweb_back/geode_objects.py index dbef8a76..77d2ac30 100644 --- a/src/opengeodeweb_back/geode_objects.py +++ b/src/opengeodeweb_back/geode_objects.py @@ -1,3 +1,6 @@ +# Standard library imports + +# Third party imports import opengeode as og import opengeode_io as og_io import opengeode_inspector as og_inspector @@ -5,6 +8,8 @@ import opengeode_geosciencesio as og_gs_io import geode_viewables as g_v +# Local application imports + def geode_objects_dict(): return { diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 3d4a2c6f..47bb4ab9 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -304,6 +304,68 @@ def create_point(): ) +with open( + os.path.join(schemas, "vertex_attribute_names.json"), + "r", +) as file: + vertex_attribute_names_json = json.load(file) + + +@routes.route( + vertex_attribute_names_json["route"], + methods=vertex_attribute_names_json["methods"], +) +def vertex_attribute_names(): + + UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] + utils_functions.validate_request(flask.request, vertex_attribute_names_json) + file_absolute_path = os.path.join( + UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"]) + ) + data = geode_functions.load( + flask.request.json["input_geode_object"], file_absolute_path + ) + vertex_attribute_names = data.vertex_attribute_manager().attribute_names() + + return flask.make_response( + { + "vertex_attribute_names": vertex_attribute_names, + }, + 200, + ) + + +with open( + os.path.join(schemas, "polygon_attribute_names.json"), + "r", +) as file: + polygon_attribute_names_json = json.load(file) + + +@routes.route( + polygon_attribute_names_json["route"], + methods=polygon_attribute_names_json["methods"], +) +def polygon_attribute_names(): + + UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] + utils_functions.validate_request(flask.request, vertex_attribute_names_json) + file_absolute_path = os.path.join( + UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"]) + ) + data = geode_functions.load( + flask.request.json["input_geode_object"], file_absolute_path + ) + polygon_attribute_names = data.polygon_attribute_manager().attribute_names() + + return flask.make_response( + { + "polygon_attribute_names": polygon_attribute_names, + }, + 200, + ) + + with open( os.path.join(schemas, "ping.json"), "r", diff --git a/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json b/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json new file mode 100644 index 00000000..6f1f8b2c --- /dev/null +++ b/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json @@ -0,0 +1,15 @@ +{ + "route": "/polygon_attribute_names", + "methods": ["POST"], + "type": "object", + "properties": { + "input_geode_object": { + "type": "string" + }, + "filename": { + "type": "string" + } + }, + "required": ["input_geode_object", "filename"], + "additionalProperties": false +} diff --git a/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json b/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json new file mode 100644 index 00000000..963597af --- /dev/null +++ b/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json @@ -0,0 +1,15 @@ +{ + "route": "/vertex_attribute_names", + "methods": ["POST"], + "type": "object", + "properties": { + "input_geode_object": { + "type": "string" + }, + "filename": { + "type": "string" + } + }, + "required": ["input_geode_object", "filename"], + "additionalProperties": false +} diff --git a/src/opengeodeweb_back/test_utils.py b/src/opengeodeweb_back/test_utils.py new file mode 100644 index 00000000..0d4c6176 --- /dev/null +++ b/src/opengeodeweb_back/test_utils.py @@ -0,0 +1,25 @@ +# Standard library imports + +# Third party imports + +# Local application imports + + +def test_route_wrong_params(client, route, get_full_data): + for key, value in get_full_data().items(): + json = get_full_data() + json.pop(key) + response = client.post(route, json=json) + assert response.status_code == 400 + error_description = response.json["description"] + assert error_description == f"Validation error: '{key}' is a required property" + + json = get_full_data() + json["dumb_key"] = "dumb_value" + response = client.post(route, json=json) + assert response.status_code == 400 + error_description = response.json["description"] + assert ( + error_description + == "Validation error: Additional properties are not allowed ('dumb_key' was unexpected)" + ) diff --git a/tests/conftest.py b/tests/conftest.py index 5642ba2c..efe7e6c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,11 @@ +# Standard library imports +import time + +# Third party imports import pytest + +# Local application imports from app import app -import time @pytest.fixture diff --git a/tests/polygon_attribute.vtp b/tests/polygon_attribute.vtp new file mode 100644 index 00000000..09e313a5 --- /dev/null +++ b/tests/polygon_attribute.vtp @@ -0,0 +1,25 @@ + + + + + + -5.03873 -3.56819 -3.29213 -0.379219 0.386424 -1.97966 -0.310684 1.50807 1.80048 2.25409 2.56229 0.235826 -0.162775 -1.4339 + 1.1 4.5 3 5 1.2 6 3 8 5 8.2 5 5.5 1.1 8.3 2 10 4 10 6 10 7 10 5.9 7.7 6.1 7 5.8 5.8 + 32 33 36 37 42 34 38 39 40 41 43 44 45 35 + 0.441452 1.06497 0 0.509268 1.09401 0 0.451134 1.22395 0 0.244789 1.03019 0 0.306431 0.898241 0 0.520398 0.854144 0 0.0558145 0.936744 0 0.17577 1.03026 0 0.179188 0.94981 0 0.242737 0.863881 0 0.312089 0.886676 0 0.328556 0.855054 0 nan nan nan 0.353402 0.897993 0 + + + 1.1 4.5 0 3 5 0 1.2 6 0 3 8 0 5 8.2 0 5 5.5 0 1.1 8.3 0 2 10 0 4 10 0 6 10 0 7 10 0 5.9 7.7 0 6.1 7 0 5.8 5.8 0 + + + 0 1 2 1 3 2 1 4 3 1 5 4 2 3 6 3 7 6 3 8 7 3 4 8 4 9 8 4 10 9 4 11 10 4 12 11 5 12 4 5 13 12 + 4.29497e+09 1 4.29497e+09 2 4 0 3 7 1 4.29497e+09 12 2 1 5 4.29497e+09 6 4.29497e+09 4 7 4.29497e+09 5 2 8 6 9 4.29497e+09 7 10 4.29497e+09 8 11 4.29497e+09 9 12 4.29497e+09 10 13 11 3 4.29497e+09 4.29497e+09 12 + -3.96635 -2.41318 -1.18699 -1.72048 -1.32735 0.272721 0.976443 0.602562 1.48033 1.73427 1.06151 0.153159 -0.585337 -1.19211 + + + 0 1 2 1 3 2 1 4 3 1 5 4 2 3 6 3 7 6 3 8 7 3 4 8 4 9 8 4 10 9 4 11 10 4 12 11 5 12 4 5 13 12 + 3 6 9 12 15 18 21 24 27 30 33 36 39 42 + + + + diff --git a/tests/test_geode_functions.py b/tests/test_geode_functions.py index 2155acf2..ea0ecb64 100644 --- a/tests/test_geode_functions.py +++ b/tests/test_geode_functions.py @@ -1,5 +1,10 @@ +# Standard library imports import os import uuid + +# Third party imports + +# Local application imports from src.opengeodeweb_back import geode_functions, geode_objects diff --git a/tests/test_routes.py b/tests/test_routes.py index 0320271f..138cd435 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,17 +1,28 @@ -import os +# Standard library imports import base64 +import os + +# Third party imports from werkzeug.datastructures import FileStorage +# Local application imports +from src.opengeodeweb_back import test_utils + def test_allowed_files(client): route = f"/allowed_files" - response = client.post(route, json={"supported_feature": None}) + get_full_data = lambda: {"supported_feature": "None"} + json = get_full_data() + response = client.post(route, json=json) assert response.status_code == 200 extensions = response.json["extensions"] assert type(extensions) is list for extension in extensions: assert type(extension) is str + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) + def test_allowed_objects(client): route = f"/allowed_objects" @@ -30,13 +41,8 @@ def get_full_data(): for allowed_object in allowed_objects: assert type(allowed_object) is str - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_upload_file(client): @@ -58,11 +64,7 @@ def get_full_data(): } json = get_full_data() - response = client.post( - route, - json=json, - ) - + response = client.post(route, json=json) assert response.status_code == 200 has_missing_files = response.json["has_missing_files"] mandatory_files = response.json["mandatory_files"] @@ -71,33 +73,23 @@ def get_full_data(): assert type(mandatory_files) is list assert type(additional_files) is list - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_geographic_coordinate_systems(client): route = f"/geographic_coordinate_systems" - + get_full_data = lambda: {"input_geode_object": "BRep"} # Normal test with geode_object 'BRep' - response = client.post(route, json={"input_geode_object": "BRep"}) + response = client.post(route, json=get_full_data()) assert response.status_code == 200 crs_list = response.json["crs_list"] assert type(crs_list) is list for crs in crs_list: assert type(crs) is dict - # Test without geode_object - response = client.post(route, json={}) - assert response.status_code == 400 - error_message = response.json["description"] - assert ( - error_message == "Validation error: 'input_geode_object' is a required property" - ) + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_inspect_file(client): @@ -117,13 +109,8 @@ def get_full_data(): inspection_result = response.json["inspection_result"] assert type(inspection_result) is dict - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_geode_objects_and_output_extensions(client): @@ -148,13 +135,8 @@ def get_full_data(): assert type(value) is dict assert type(value["is_saveable"]) is bool - # Test without input_geode_object - response = client.post(route, json={}) - assert response.status_code == 400 - error_message = response.json["description"] - assert ( - error_message == "Validation error: 'input_geode_object' is a required property" - ) + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_save_viewable_file(client): @@ -178,20 +160,66 @@ def get_full_data(): id = response.json["id"] assert type(id) is str - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) -def test_create_point(client): - route = f"/create_point" +def test_vertex_attribute_names(client): + response = client.put( + f"/upload_file", + data={"file": FileStorage(open("./tests/vertex_attribute.vtp", "rb"))}, + ) + assert response.status_code == 201 + + route = f"/vertex_attribute_names" + + def get_full_data(): + return { + "input_geode_object": "PolygonalSurface3D", + "filename": "vertex_attribute.vtp", + } + + # Normal test with filename 'vertex_attribute.vtp' + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + vertex_attribute_names = response.json["vertex_attribute_names"] + assert type(vertex_attribute_names) is list + for vertex_attribute_name in vertex_attribute_names: + assert type(vertex_attribute_name) is str + + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_polygon_attribute_names(client): + response = client.put( + f"/upload_file", + data={"file": FileStorage(open("./tests/polygon_attribute.vtp", "rb"))}, + ) + assert response.status_code == 201 + + route = f"/polygon_attribute_names" def get_full_data(): - return {"x": 1, "y": 2, "z": 3} + return { + "input_geode_object": "PolygonalSurface3D", + "filename": "polygon_attribute.vtp", + } + + # Normal test with filename 'vertex_attribute.vtp' + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + polygon_attribute_names = response.json["polygon_attribute_names"] + assert type(polygon_attribute_names) is list + for polygon_attribute_name in polygon_attribute_names: + assert type(polygon_attribute_name) is str + + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_create_point(client): + route = f"/create_point" + get_full_data = lambda: {"x": 1, "y": 2, "z": 3} # Normal test with all keys response = client.post(route, json=get_full_data()) @@ -201,10 +229,5 @@ def get_full_data(): id = response.json["id"] assert type(id) is str - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 8416539d..59fbfcc9 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -1,4 +1,9 @@ +# Standard library imports + +# Third party imports import flask + +# Local application imports from src.opengeodeweb_back import utils_functions diff --git a/tests/vertex_attribute.vtp b/tests/vertex_attribute.vtp new file mode 100644 index 00000000..dfe9f191 --- /dev/null +++ b/tests/vertex_attribute.vtp @@ -0,0 +1,25 @@ + + + + + + -4.81986 -4.24426 -3.08307 -2.67472 -3.78427 -1.72952 -3.51395 -1.54156 -3.26163 -1.37705 1.49282 0.476446 3.07847 1.81965 0.15148 -0.00219692 -2.03758 -1.21643 -0.392793 1.04446 -0.159125 -1.41509 -1.36066 + 1.1 1.5 4.5 1.4 2.9 3 4.9 3.1 6.4 1.7 6.3 4 8.2 1.9 8.5 4 11 2 10.9 4 10.7 6.9 9.4 6 10.6 8.5 8 7.5 7 5.95 5.8 5.8 1.1 4.5 3 5 5 5.5 6.1 7 5.8 5.8 5.35 4.45 5.35 4.45 + 0.212913 0.898437 0 0.17411 0.88882 0 0.188674 0.91656 0 0.129386 0.902004 0 0.0522603 0.892251 0 0.0913028 0.902984 0 0.0494319 0.925332 0 0.0790874 0.944317 0 0.0630148 0.926847 0 0.0606951 0.95661 0 0.0916888 0.990159 0 0.102275 0.986827 0 0.0910002 0.997784 0 0.149679 1.01122 0 0.129961 0.95844 0 nan nan nan 0.18944 0.925768 0 0.188704 0.925194 0 0.172618 0.985506 0 0.149488 0.967933 0 0.14023 0.965619 0 0.0974158 0.875532 0 nan nan nan + 50 51 55 56 52 57 53 58 54 59 62 61 47 46 60 35 32 33 34 45 35 63 63 + + + 1.1 1.5 0 4.5 1.4 0 2.9 3 3 4.9 3.1 0 6.4 1.7 0 6.3 4 0 8.2 1.9 0 8.5 4 0 11 2 0 10.9 4 3 10.7 6.9 0 9.4 6 0 10.6 8.5 0 8 7.5 0 7 5.95 0 5.8 5.8 0 1.1 4.5 0 3 5 0 5 5.5 0 6.1 7 3 5.8 5.8 0 5.35 4.45 0 5.35 4.45 0 + + + -4.04907 -3.33402 -3.56775 -2.7295 -3.00925 -2.26168 -2.77238 -2.06008 0.197405 1.68258 1.79152 -0.814056 0.815858 -0.304546 -1.03987 -0.579057 -3.3135 -2.11236 -2.32474 -1.42798 0.345606 1.0052 -1.10125 -1.93978 -1.47606 -0.585217 + 4.29497e+09 1 16 2 18 0 4.29497e+09 3 1 4 23 2 4.29497e+09 5 3 6 14 4 4.29497e+09 7 5 4.29497e+09 11 6 4.29497e+09 9 11 4.29497e+09 10 8 9 4.29497e+09 12 7 8 13 13 10 21 11 12 14 5 13 15 14 20 22 0 17 4.29497e+09 16 18 4.29497e+09 1 19 17 24 4.29497e+09 18 15 21 4.29497e+09 12 4.29497e+09 20 4.29497e+09 23 15 4.29497e+09 3 22 4.29497e+09 25 19 4.29497e+09 4.29497e+09 24 + 0 1 2 1 3 2 1 4 3 4 5 3 4 6 5 6 7 5 6 8 7 8 9 7 9 10 11 10 12 11 11 12 13 7 9 11 14 11 13 7 11 14 5 7 14 5 14 20 0 2 16 16 2 17 2 3 17 3 18 17 20 14 19 14 13 19 20 21 5 21 3 5 3 22 18 22 15 18 + + + 0 1 2 1 3 2 1 4 3 4 5 3 4 6 5 6 7 5 6 8 7 8 9 7 9 10 11 10 12 11 11 12 13 7 9 11 14 11 13 7 11 14 5 7 14 5 14 20 0 2 16 16 2 17 2 3 17 3 18 17 20 14 19 14 13 19 20 21 5 21 3 5 3 22 18 22 15 18 + 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 + + + +