diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a4c15b8926f0311493561131634c0adf878f67b..ca3a77a2aaf04e14950f9310cbbe70d5349dc785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Tests for `BEFORE`, `AFTER`, `UNTIL`, `SINCE` keywords for query transaction filters [caosdb-server#132](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/132) +* Tests for missing obligatory Properties + [caosdb-server#146](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/146) + ### Changed (for changes in existing functionality) diff --git a/tests/test_importance.py b/tests/test_importance.py new file mode 100644 index 0000000000000000000000000000000000000000..afa63effa4d70b32728777d3304a09c212c318e1 --- /dev/null +++ b/tests/test_importance.py @@ -0,0 +1,184 @@ +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2021 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/>. +# + +from pytest import raises +import caosdb as db + + +def setup(): + teardown_module() + + +def teardown_module(): + clean = db.execute_query("FIND Test*") + if len(clean) > 0: + clean.delete() + + +def test_obl_missing_error(): + p = db.Property(name="TestOblProperty", datatype=db.TEXT).insert() + p_dummy = db.Property(name="TestDummyProperty", datatype=db.TEXT).insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty", importance=db.OBLIGATORY) + rt1.insert() + + # normal behaviour, should be identical to "error" level + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestDummyProperty") + with raises(db.TransactionError) as exc: + rt2.insert() + assert exc.value.errors[0].msg == "An obligatory property is missing." + + # force error + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestDummyProperty") + rt2.set_flag("force-missing-obligatory", "error") + with raises(db.TransactionError) as exc: + rt2.insert() + assert exc.value.errors[0].msg == "An obligatory property is missing." + + # warning is treated as error because "strict" flag + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestDummyProperty") + rt2.set_flag("force-missing-obligatory", "warn") + with raises(db.TransactionError) as exc: + rt2.insert(strict=True) + assert exc.value.errors[0].msg == ( + "A warning occured while processing an entity with the strict flag.") + assert "An obligatory property is missing." in [ + w.description for w in rt2.get_warnings()] + + # insert with warning + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestDummyProperty") + rt2.set_flag("force-missing-obligatory", "warn") + rt2.insert() + assert len(rt2.get_errors()) == 0 + assert (rt2.get_warnings()[0].description + == "An obligatory property is missing.") + + # insert silently + rt3 = db.RecordType(name="TestRecordType3") + rt3.add_parent("TestRecordType1", inheritance=db.NONE) + rt3.add_property("TestDummyProperty") + rt3.set_flag("force-missing-obligatory", "ignore") + rt3.insert() + assert len(rt3.get_errors()) == 0 + assert len(rt3.get_warnings()) == 0 + + +def test_obl_missing_parent_in_same_container(): + p = db.Property(name="TestOblProperty", datatype=db.TEXT).insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty", importance=db.OBLIGATORY) + + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + + c = db.Container() + c.extend([rt1, rt2]) + with raises(db.TransactionError) as exc: + c.insert() + assert exc.value.errors[0].msg == "An obligatory property is missing." + + +def test_obl_missing_parent_in_same_container_override(): + """`force-missing-obligatory` flags of entities override flags of containing containers. + """ + p = db.Property(name="TestOblProperty", datatype=db.TEXT).insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty", importance=db.OBLIGATORY) + + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.set_flag("force-missing-obligatory", "error") + + c = db.Container() + c.extend([rt1, rt2]) + with raises(db.TransactionError) as exc: + c.insert(flags={"force-missing-obligatory": "ignore"}) + assert exc.value.errors[0].msg == "An obligatory property is missing." + + +def test_sub_property_of_obl(): + """If p1 <|- p2, then p2 should work for an obligatory p1. + """ + p1 = db.Property(name="TestOblProperty1", datatype=db.TEXT).insert() + p2 = db.Property(name="TestOblProperty2", datatype=db.TEXT) + p2.add_parent("TestOblProperty1") + p2.insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty1", importance=db.OBLIGATORY) + rt1.insert() + + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + + # add subtype of TestOblProperty1 + rt2.add_property("TestOblProperty2") + rt2.insert() # everything ok! + + +def test_sub_property_of_obl_in_same_container(): + + p1 = db.Property(name="TestOblProperty1", datatype=db.TEXT).insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty1", importance=db.OBLIGATORY) + rt1.insert() + + p2 = db.Property(name="TestOblProperty2", datatype=db.TEXT) + p2.add_parent("TestOblProperty1") + + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestOblProperty2") + + c = db.Container() + c.extend([rt2, p2]) + c.insert() # everything ok! + + +def test_illegal_flag_value(): + p = db.Property(name="TestOblProperty", datatype=db.TEXT).insert() + p_dummy = db.Property(name="TestDummyProperty", datatype=db.TEXT).insert() + + rt1 = db.RecordType(name="TestRecordType1") + rt1.add_property(name="TestOblProperty", importance=db.OBLIGATORY) + rt1.insert() + + rt2 = db.RecordType(name="TestRecordType2") + rt2.add_parent("TestRecordType1", inheritance=db.NONE) + rt2.add_property("TestDummyProperty") + rt2.set_flag("force-missing-obligatory", "illegal!!!!") + with raises(db.TransactionError) as exc: + rt2.insert() + # default behavior + warning + assert exc.value.errors[0].msg == "An obligatory property is missing." + assert "Illegal value for flag 'force-missing-obligatory'." in [ + w.description for w in rt2.get_warnings()]