diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 42e7907b8d802faa6a4fc20d89ce1c677a85e6df..d0d0982745ac95edadccfef823ac0931c6a5e39e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -163,7 +163,6 @@ test: dependencies: [cert] timeout: 3h artifacts: - when: on_failure paths: - caosdb_log.txt - mariadb_log.txt @@ -218,10 +217,12 @@ cert: script: - cd .docker - CAOSHOSTNAME=caosdb-server ./cert.sh + style: tags: [docker] stage: style image: $CI_REGISTRY_IMAGE + needs: [] script: - autopep8 -r --diff --exit-code . allow_failure: true diff --git a/CHANGELOG.md b/CHANGELOG.md index dd58edd8e7fa711540aa8e20f981c155c3945711..c997d3e3d77a949be42440117a4fcba538de7926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [caosdb-server#155](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/155) have been resolved. * Tests for caosdb-server#154 +* Tests for caosdb-server#217 * Tests for caosdb-pylib#61 ### Changed (for changes in existing functionality) diff --git a/Makefile b/Makefile index 9689f1e3b68d0bd7f501879bcb6c854c0153a88e..d81534bd53fe76e6244a998aa393a6a86f52ac69 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ help: # Run the tests through autopep8. autopep8: - autopep8 -ri tests + autopep8 -ri . # Meta target to call the other targets. all: autopep8 test diff --git a/pycaosdb.ini.template b/pycaosdb.ini.template index c94af17ae01408f9d7f24473ce36ce738c75331d..7530d6af2e24d564c3d02b88b3f7dd38eb4de073 100644 --- a/pycaosdb.ini.template +++ b/pycaosdb.ini.template @@ -2,28 +2,30 @@ ## This sections must exist in addition to the usual section. [IntegrationTests] -########## Server-side scripting ################## +########## Server-side scripting paths ################## ## These are used by tests of server side scripting. Both paths have ## to point to existing directories in which the CaosDB server has the ## permissions to create and execute scripts. -# location of the scripting bin dir which is used for the test scripts from the -# pyinttest's perspective. +# Location of the scripting bin dir which is used for the test scripts from the +# pyinttest's perspective. Probably the scripting/bin dir in the caosdb-server sources. #test_server_side_scripting.bin_dir.local=/path/to/scripting/bin -# location of the scripting bin dir which is used for the test scripts from the +# Location of the scripting bin dir which is used for the test scripts from the # server's perspective. #test_server_side_scripting.bin_dir.server=/opt/caosdb/git/caosdb-server/scripting/bin -########## Files ################## +########## Files paths ################## ## Used by tests of file handling. Specify the path to an existing ## directory in which file tests are performed, once as seen by the ## host and once as seen by the server. -# location of the files from the pyinttest (i.e. host) perspective +# Location of the files from the pyinttest (i.e. host) perspective. Probably the local extroot +# path plus `/test_insert_files_in_dir/`. #test_files.test_insert_files_in_dir.local=/extroot/test_insert_files_in_dir/ -# location of the files from the caosdb server's perspective +# Location of the files from the caosdb server's perspective. Probably with the same last +# component(s) as the local variant (above). #test_files.test_insert_files_in_dir.server=/opt/caosdb/mnt/extroot/test_insert_files_in_dir/ ########## Authentication tokens ################## diff --git a/tests/test_issues_server.py b/tests/test_issues_server.py index a61be841f25ef8c619084f912be37cba31f59349..e9a93685cfd222000a50b399d44ec32692137e4f 100644 --- a/tests/test_issues_server.py +++ b/tests/test_issues_server.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -# -# ** header v3.0 # This file is a part of the CaosDB Project. # -# Copyright (c) 2020 IndiScale GmbH <info@indiscale.com> -# Copyright (c) 2020 Daniel Hornung <d.hornung@indiscale.com> +# Copyright (c) 2022 IndiScale GmbH <info@indiscale.com> +# Copyright (c) 2022 Daniel Hornung <d.hornung@indiscale.com> # Copyright (c) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com> # # This program is free software: you can redistribute it and/or modify @@ -19,8 +17,6 @@ # # 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/>. -# -# ** end header """Tests for issues on gitlab.com, project caosdb-server.""" @@ -553,7 +549,6 @@ def test_issue_130(): assert row == [("ReferencedRecord",)] # FAILS -@pytest.mark.xfail(reason="fix https://gitlab.com/caosdb/caosdb-server/-/issues/132") def test_issue_132(): """Query: Parenthesis around subproperties. @@ -604,3 +599,108 @@ def test_issue_132(): # this one has the wront scope of the conjunction. query = "FIND RECORD TestRT WITH TestRT_Foo.TestP_Bar = val1 AND TestP_Baz = 'the other one'" assert len(db.execute_query(query)) == 0 + + +def test_issue_217(): + """Server gets list property datatype wrong if description is updated.""" + # @review Florian Spreckelsen 2022-03-15 + + rt = db.RecordType(name="TestRT").insert() + # prop = db.Property(name="LP", datatype=db.LIST("TestRT")).insert() + r = db.Record().add_parent(id=rt.id).add_property(name=rt.name, + datatype=db.LIST( + db.INTEGER), + value=[10001, 10002]) + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + r.insert() + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + r.retrieve() + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + r.retrieve(flags={"cache": "false"}) + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + r.description = "Changed description" + r.update() + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + # This line fails in the bug report with invalid XML. + r.retrieve() + assert r.get_property(rt.name).datatype == db.LIST(db.INTEGER) + assert r.get_property(rt.name).value == [10001, 10002] + + +def test_issue_217_2(): + """Server gets overridden name of property wrong when the description of record is being updated.""" + # @review Florian Spreckelsen 2022-03-15 + + rt = db.RecordType(name="TestRT").insert() + # prop = db.Property(name="LP", datatype=db.LIST("TestRT")).insert() + overridden_name = "TestRT-overridden" + r = db.Record().add_parent(id=rt.id).add_property(name=overridden_name, + id=rt.id) + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + r.insert() + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + r.retrieve() + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + r.retrieve(flags={"cache": "false"}) + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + r.description = "Changed description" # change description of the record + r.update() + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + r.retrieve() + assert r.get_property(overridden_name) is not None + assert r.get_property(overridden_name).id == rt.id + + +def test_issue_217_3(): + """Server gets overridden description of property wrong when the description of record is being updated.""" + # @review Florian Spreckelsen 2022-03-15 + + rt = db.RecordType(name="TestRT", description="Desc").insert() + # prop = db.Property(name="LP", datatype=db.LIST("TestRT")).insert() + r = db.Record().add_parent(id=rt.id).add_property(description="Desc-overridden", + id=rt.id) + r.insert() + assert r.get_property(rt.name).id == rt.id + assert r.get_property(rt.name).description == "Desc-overridden" + r.retrieve() + assert r.get_property(rt.name).id == rt.id + assert r.get_property(rt.name).description == "Desc-overridden" + r.retrieve(flags={"cache": "false"}) + assert r.get_property(rt.name).id == rt.id + assert r.get_property(rt.name).description == "Desc-overridden" + r.description = "Changed description" # change description of the record + r.update() + assert r.get_property(rt.name).id == rt.id + assert r.get_property(rt.name).description == "Desc-overridden" + r.retrieve() + assert r.get_property(rt.name).id == rt.id + assert r.get_property(rt.name).description == "Desc-overridden" + + +def test_issue_221(): + """Unknown error during update of property leaving out datatype""" + + rt = db.RecordType("A") + r = db.Record() + r.add_parent(rt) + p = db.Property(name="B", datatype=db.INTEGER) + r.add_property(name="B", value=5) + db.Container().extend([rt, r, p]).insert() + + r2 = db.Record(id=r.id).retrieve() + r2.remove_property("B") + r2.add_property(p, value=7) + r2.update() + assert r2.get_property("B").value == 7 + assert r2.get_property("B").datatype == db.INTEGER + assert r2.get_property("B").id == p.id diff --git a/tests/test_state.py b/tests/test_state.py index eee4b1dad6485ab9e2c238a0cc87398eab1dbafa..0b2c510d5c78609e9f9797336687446a7b20e2d6 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1,3 +1,25 @@ +# encoding: utf-8 +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2020-2022 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2020-2022 Timm Fitschen <t.fitschen@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/>. +# +# ** end header +# import pytest import caosdb as db from caosdb import administration as admin @@ -111,6 +133,7 @@ def setup_module(): "ACL": None}) state_acl = db.ACL() state_acl.grant(role="role1", permission="UPDATE:DESCRIPTION") + state_acl.deny(role="anonymous", permission="RETRIEVE:ENTITY") state_acl = db.State.create_state_acl(state_acl) st1.acl = state_acl.combine(st1.acl) st1.update_acl() @@ -146,6 +169,8 @@ def setup_module(): def teardown_function(function): switch_to_admin_user() + # deactivate anonymous user + db.administration.set_server_property("AUTH_OPTIONAL", "FALSE") d = db.execute_query("FIND TestRT") if len(d) > 0: d.delete(flags={"forceFinalState": "true"}) @@ -480,10 +505,16 @@ def test_transfer_state_acl(): rec.state = db.State(model="Model1", name="State1") insert_rec = rec.insert(flags={"ACL": None}) - state_acl = db.ACL().combine(db.get_global_acl()) + state_acl = db.ACL() state_acl.grant(role="role1", permission="UPDATE:DESCRIPTION") + state_acl.deny(role="anonymous", permission="RETRIEVE:ENTITY") + state_acl = state_acl.combine(db.get_global_acl()) # the acl has been transfered from the state record + assert insert_rec.acl.get_permissions_for_role("role1") == { + "UPDATE:DESCRIPTION"} + assert "RETRIEVE:ENTITY" not in insert_rec.acl.get_permissions_for_role( + "anonymous") assert insert_rec.acl == state_acl @@ -806,3 +837,29 @@ def test_transitions_included_after_empty_update(): db.Transition(name="Transition4", from_state="State2", to_state="State2")} + + +def test_missing_retrieve_permission(): + """When the retrieve permission is missing, the state must not be leaked.""" + # @review Florian Spreckelsen 2022-03-22 + rec = db.Record() + rec.description = "old description" + rec.add_parent("TestRT") + rec.state = db.State(model="Model1", name="State1") + rec.insert(flags={"ACL": None}) + print(rec) + + # switch to anonymous + db.administration.set_server_property("AUTH_OPTIONAL", "TRUE") + db.configure_connection(password_method="unauthenticated") + assert db.Info().user_info.roles == ["anonymous"] + + rec2 = db.Record(id=rec.id) + with pytest.raises(db.TransactionError) as te: + rec2.retrieve() + assert te.value.has_error(db.AuthorizationError) + + rec2 = db.Record(id=rec.id) + rec2.retrieve(raise_exception_on_error=False) + assert len(rec2.get_errors()) > 0 + assert rec2.state is None diff --git a/tox.ini b/tox.ini index 26c5d1594c48fe755a355a3aee711fd6608c6f97..03ab50d132a82e6f7ee4b26cb55156e211f24b01 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,10 @@ [tox] -envlist=py36, py37, py38, py39 +envlist=py36, py37, py38, py39, py310 skip_missing_interpreters = true + +[pycodestyle] +max_line_length = 100 + [testenv] setenv = PASSWORD_STORE_DIR = {env:HOME}/.password-store passenv = PYCAOSDBINI