diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9bb5adb6ed4fedf57694d6ec7843942c46296269..1dc09269a92c486e9d80a8ae5ceb0e51dc50bd17 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,6 +39,7 @@ stages: code_style: tags: [ docker ] stage: code_style + needs: [ ] script: - make style allow_failure: true @@ -47,17 +48,19 @@ code_style: pylint: tags: [ docker ] stage: linting + needs: [ ] script: - make lint allow_failure: true -# run tests -test: +# run unit tests +unittest: tags: [ docker ] stage: test + needs: [ ] script: - touch ~/.pycaosdb.ini - - tox -r + - make unittest # Trigger building of server image and integration tests trigger_build: @@ -96,6 +99,7 @@ build-testenv: pages_prepare: &pages_prepare tags: [ cached-dind ] stage: deploy + needs: [ code_style, pylint, unittest ] only: refs: - /^release-.*$/i diff --git a/Makefile b/Makefile index 192337853d8db8812e14f75fca8986006de82180..0a0888ad0484c0307583e139e65058c38574ed3a 100644 --- a/Makefile +++ b/Makefile @@ -42,3 +42,7 @@ style: lint: pylint --unsafe-load-any-extension=y -d all -e E,F src/caosdb/common .PHONY: lint + +unittest: + tox -r +.PHONY: unittest diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index ec45dec070d1ea731648fe8d45e44ba89e393f76..80a6ee11e707fb3776fc96b42a16b649ac575f66 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -55,6 +55,7 @@ from caosdb.exceptions import (AmbiguousEntityError, AuthorizationError, EntityDoesNotExistError, EntityError, EntityHasNoDatatypeError, HTTPURITooLongError, MismatchingEntitiesError, QueryNotUniqueError, + ServerConfigurationException, TransactionError, UniqueNamesError, UnqualifiedParentsError, UnqualifiedPropertiesError) @@ -1195,6 +1196,10 @@ class Entity(object): def _parse_value(datatype, value): + """Parse the value (from XML input) according to the given datatype + """ + + # Simple values if value is None: return value @@ -1215,12 +1220,12 @@ def _parse_value(datatype, value): else: raise ValueError("Boolean value was {}.".format(value)) + # Datetime and text are returned as-is if datatype in [DATETIME, TEXT]: if isinstance(value, str): return value # deal with collections - if isinstance(datatype, str): matcher = re.compile(r"^(?P<col>[^<]+)<(?P<dt>[^>]+)>$") m = matcher.match(datatype) @@ -1245,12 +1250,10 @@ def _parse_value(datatype, value): # This is for a special case, where the xml parser could not differentiate # between single values and lists with one element. As - if hasattr(value, "__len__") and len(value) == 1: return _parse_value(datatype, value[0]) # deal with references - if isinstance(value, Entity): return value @@ -1266,6 +1269,12 @@ def _parse_value(datatype, value): # reference via name return str(value) + except TypeError: + # deal with invalid XML: List of values without appropriate datatype + if isinstance(value, list): + raise ServerConfigurationException( + "The server sent an invalid XML: List valued properties must be announced by " + "the datatype.\n" + f"Datatype: {datatype}\nvalue: {value}") def _log_request(request, xml_body=None): diff --git a/unittests/data/list_in_value.xml b/unittests/data/list_in_value.xml new file mode 100644 index 0000000000000000000000000000000000000000..0f92610d82caa5ced443b2f437f35da05b9e121a --- /dev/null +++ b/unittests/data/list_in_value.xml @@ -0,0 +1,12 @@ +<Record id="1002" description="A description of this example experiment."> + <Version id="945c6858819d2609a5475ee4df64571984acd039" head="true"> + <Predecessor id="0df3cfe164fbafe9777f9356d0be2403890c54cd" /> + </Version> + <Parent id="1001" name="Experiment" /> + <Property datatype="SomeRecordType" id="1003" name="DepthTest" importance="FIX"> + <Value>1004</Value> + <Value>1005</Value> + </Property> +</Record> + +<!-- Note: This XML is invalid, because list-valued Properties must have a LIST-Datatype --> diff --git a/unittests/test_issues.py b/unittests/test_issues.py new file mode 100644 index 0000000000000000000000000000000000000000..1e649db4f23de67e55301e0a053fba70d14680b4 --- /dev/null +++ b/unittests/test_issues.py @@ -0,0 +1,39 @@ +# This file is a part of the CaosDB Project. +# +# Copyright (c) 2022 IndiScale GmbH +# Copyright (c) 2022 Daniel Hornung (d.hornung@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +"""Test known issues to prevent regressions. +""" + +import os + +import lxml +import caosdb as db + +from pytest import raises + + +def test_issue_100(): + """_parse_value() fails for some list-valued content + """ + + # Parse from (invalid) XML file + filename = os.path.join(os.path.dirname(__file__), "data", "list_in_value.xml") + xml_el = lxml.etree.parse(filename).getroot() + with raises(db.ServerConfigurationException) as exc_info: + db.common.models._parse_single_xml_element(xml_el) + assert "invalid XML: List valued properties" in exc_info.value.msg