diff --git a/.gitignore b/.gitignore index b522b1da9176e59756bffe89cd4eafe0d751a23c..55fb3f0d1bc6c101704557da8f35d6e784b5ea89 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ build/ src/caosdb/version.py # documentation -_apidoc \ No newline at end of file +_apidoc +*~ diff --git a/CHANGELOG.md b/CHANGELOG.md index f51b1e3bc534ca2d3f3e0804febc7716e43d80b7..e594d9c23d4a5d5791cd437e25fa08f953179e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,31 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.8.0] - 2022-07-12 -(Timm Fitschen) +## [0.9.0] - 2022-10-24 +(Florian Spreckelsen) ### Added ### -### Changed ### +* Add TimeZone class and parse the server's time zone in the Info response. -### Deprecated ### +### Fixed ### + +* [#141](https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/141) + `password_method = unauthenticated` not allowed by schema +* Set PyYAML dependency back to PyYaml>=5.4.1 (from 6.0) for better + compatibility with docker-compose + +### Documentation ### + +* Added curator role permissions example to code gallery + +## [0.8.0] - 2022-07-12 +(Timm Fitschen) ### Removed ### * Support for Python 3.6 and Python 3.7 ### Fixed ### -* [#75](https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/75), [#103](https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/103) Fixed JSON schema to allow more sections, and correct requirements for - password method. -- `read()` of MockupResponse returns now an appropriate type on modern systems +* `read()` of MockupResponse returns now an appropriate type on modern systems * [caosdb-server#142](https://gitlab.com/caosdb/caosdb-server/-/issues/142) Can't create users with dots in their user names -### Security ### - -### Documentation ### - ## [0.7.4] - 2022-05-31 (Florian Spreckelsen) @@ -210,7 +216,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### -* Dynamic exception type `EntityMultiError`. +* Dynamic exception type `EntityMultiError`. * `get_something` functions from all error object in `exceptions.py` * `AmbiguityException` diff --git a/README.md b/README.md index 04b34cbc07c98e73740b13200ed83fe067af99d2..602df33cecfc8ec37fd791e3257221e66f120cb3 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ However, you can also create an issue for it. * Copyright (C) 2018 Research Group Biomedical Physics, Max Planck Institute for Dynamics and Self-Organization Göttingen. -* Copyright (C) 2020-2021 Indiscale GmbH <info@indiscale.com> +* Copyright (C) 2020-2022 Indiscale GmbH <info@indiscale.com> All files in this repository are licensed under a [GNU Affero General Public License](LICENCE.md) (version 3 or later). diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md index b4e38d643756798f0ba8b07d6eceec529cbb3054..863afb8f3ac7d6770c372620523638b900785227 100644 --- a/RELEASE_GUIDELINES.md +++ b/RELEASE_GUIDELINES.md @@ -40,6 +40,9 @@ guidelines of the CaosDB Project 11. Merge the main branch back into the dev branch. -12. After the merge of main to dev, start a new development version by - setting `ISRELEASED` to `False` and by increasing at least the `MICRO` - version in [setup.py](./setup.py) and preparing CHANGELOG.md. +12. After the merge of main to dev, start a new development version by setting + `ISRELEASED` to `False` and by increasing at least the `MICRO` version in + [setup.py](./setup.py). Please note that due to a bug in pip, the `PRE` + version has to remain empty in the setup.py. + Also update CHANGELOG.md (new "Unreleased" section). Also update + `src/doc/conf.py`. diff --git a/setup.py b/setup.py index af9e680981ba7a802e22fcde706cb6b73dc7f6dc..9618cd53077e58c35cafb4611b3520b9355eead9 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ from setuptools import find_packages, setup ISRELEASED = True MAJOR = 0 -MINOR = 8 +MINOR = 9 MICRO = 0 # Do not tag as pre-release until this commit # https://github.com/pypa/packaging/pull/515 @@ -171,7 +171,7 @@ def setup_package(): python_requires='>=3.8', package_dir={'': 'src'}, install_requires=['lxml>=4.6.3', - 'PyYAML>=6.0', 'future', 'PySocks>=1.6.7'], + 'PyYAML>=5.4.1', 'future', 'PySocks>=1.6.7'], extras_require={'keyring': ['keyring>=13.0.0'], 'jsonschema': ['jsonschema>=4.4.0']}, setup_requires=["pytest-runner>=2.0,<3dev"], diff --git a/src/caosdb/apiutils.py b/src/caosdb/apiutils.py index 4c8393111bcbb4f9f91e309b81bebdcac55ba626..bd5b0eeca217e1f77d1bd5d5c60e18f33dd76212 100644 --- a/src/caosdb/apiutils.py +++ b/src/caosdb/apiutils.py @@ -338,11 +338,19 @@ def merge_entities(entity_a: Entity, entity_b: Entity): raise NotImplementedError() for attribute in ("datatype", "unit", "value"): - if diff_r1["properties"][key][attribute] is None: - setattr(entity_a.get_property(key), attribute, - diff_r2["properties"][key][attribute]) - else: - raise RuntimeError("Merge conflict.") + if (attribute in diff_r2["properties"][key] and + diff_r2["properties"][key][attribute] is not None): + if (diff_r1["properties"][key][attribute] is None): + setattr(entity_a.get_property(key), attribute, + diff_r2["properties"][key][attribute]) + else: + raise RuntimeError( + f"Merge conflict:\nEntity a ({entity_a.id}, {entity_a.name}) " + f"has a Property '{key}' with {attribute}=" + f"{diff_r2['properties'][key][attribute]}\n" + f"Entity b ({entity_b.id}, {entity_b.name}) " + f"has a Property '{key}' with {attribute}=" + f"{diff_r1['properties'][key][attribute]}") else: # TODO: This is a temporary FIX for # https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/105 diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index f974060f4727e575a94a3afcdd2f86520e6123a9..7000ede917995c6c01b78a822c2d39ac626fcc23 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -51,6 +51,7 @@ from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, INTEGER, TEXT, is_list_datatype, is_reference) from caosdb.common.state import State from caosdb.common.utils import uuid, xml2str +from caosdb.common.timezone import TimeZone from caosdb.common.versioning import Version from caosdb.configuration import get_config from caosdb.connection.connection import get_connection @@ -4327,6 +4328,8 @@ class Info(): if isinstance(m, UserInfo): self.user_info = m + elif isinstance(m, TimeZone): + self.time_zone = m else: self.messages.append(m) @@ -4460,6 +4463,9 @@ def _parse_single_xml_element(elem): return Permissions(xml=elem) elif elem.tag == "UserInfo": return UserInfo(xml=elem) + elif elem.tag == "TimeZone": + return TimeZone(zone_id=elem.get("id"), offset=elem.get("offset"), + display_name=elem.text.strip()) else: return Message(type=elem.tag, code=elem.get( "code"), description=elem.get("description"), body=elem.text) diff --git a/src/caosdb/common/timezone.py b/src/caosdb/common/timezone.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd3d3d4d739118e160f7b3a35757fbb0afe70cb --- /dev/null +++ b/src/caosdb/common/timezone.py @@ -0,0 +1,18 @@ +class TimeZone(): + """ + TimeZone, e.g. CEST, Europe/Berlin, UTC+4. + + + Attributes + ---------- + zone_id : string + ID of the time zone. + offset : int + Offset to UTC in seconds. + display_name : string + A human-friendly name of the time zone: + """ + def __init__(self, zone_id, offset, display_name): + self.zone_id = zone_id + self.offset = offset + self.display_name = display_name diff --git a/src/caosdb/schema-pycaosdb-ini.yml b/src/caosdb/schema-pycaosdb-ini.yml index 5dabdd89795e19a757209e03cc843776be705777..a81bf006523ab7690ee0bf9d27e0a2d57ce8c3c3 100644 --- a/src/caosdb/schema-pycaosdb-ini.yml +++ b/src/caosdb/schema-pycaosdb-ini.yml @@ -26,7 +26,7 @@ schema-pycaosdb-ini: description: The password input method defines how the password is supplied that is used for authentication with the server. type: string default: input - enum: [input, plain, pass, keyring] + enum: [input, unauthenticated, plain, pass, keyring] password_identifier: type: string password: diff --git a/src/caosdb/utils/register_tests.py b/src/caosdb/utils/register_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..9d0afcbb0845e1d8d31622e8ab9926f26f7e78f6 --- /dev/null +++ b/src/caosdb/utils/register_tests.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2022 Alexander Schlemmer <alexander.schlemmer@ds.mpg.de> +# Copyright (C) 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/>. + +import caosdb as db +from caosdb import administration as admin + +""" +This module implements a registration procedure for integration tests which +need a running CaosDB instance. + +It ensures that tests do not accidentally overwrite data in real CaosDB +instances, as it checks whether the running CaosDB instance is actually the +correct one, that +should be used for these tests. + +The test files have to define a global variable TEST_KEY which must be unique +for each test using + +set_test_key("ABCDE") + +The test procedure (invoked by pytest) checks whether a registration +information is stored in one of the server properties or otherwise +- offers to register this test in the currently running database ONLY if this + is empty. +- fails otherwise with a RuntimeError + +NOTE: you probably need to use pytest with the -s option to be able to + register the test interactively. Otherwise, the server property has to be + set before server start-up in the server.conf of the CaosDB server. + +This module is intended to be used with pytest. + +There is a pytest fixture "clear_database" that performs the above mentioned +checks and clears the database in case of success. +""" + +TEST_KEY = None + + +def set_test_key(KEY): + global TEST_KEY + TEST_KEY = KEY + + +def _register_test(): + res = db.execute_query("COUNT Entity") + if not isinstance(res, int): + raise RuntimeError("Response from server for Info could not be interpreted.") + if res > 0: + raise RuntimeError("This instance of CaosDB contains entities already." + "It must be empty in order to register a new test.") + + print("Current host of CaosDB instance is: {}".format( + db.connection.connection.get_connection()._delegate_connection.setup_fields["host"])) + answer = input("This method will register your current test with key {} with the currently" + " running instance of CaosDB. Do you want to continue (y/N)?".format( + TEST_KEY)) + if answer != "y": + raise RuntimeError("Test registration aborted by user.") + + admin.set_server_property("_CAOSDB_INTEGRATION_TEST_SUITE_KEY", + TEST_KEY) + + +def _get_registered_test_key(): + try: + return admin.get_server_property("_CAOSDB_INTEGRATION_TEST_SUITE_KEY") + except KeyError: + return None + + +def _is_registered(): + registered_test_key = _get_registered_test_key() + if not registered_test_key: + return False + elif registered_test_key == TEST_KEY: + return True + else: + raise RuntimeError("The database has been setup for a different test.") + + +def _assure_test_is_registered(): + global TEST_KEY + if TEST_KEY is None: + raise RuntimeError("TEST_KEY is not defined.") + if not _is_registered(): + answer = input("Do you want to register this instance of CaosDB" + " with the current test? Do you want to continue (y/N)?") + if answer == "y": + _register_test() + raise RuntimeError("Test has been registered. Please rerun tests.") + else: + raise RuntimeError("The database has not been setup for this test.") + + +def _clear_database(): + c = db.execute_query("FIND ENTITY WITH ID>99") + c.delete(raise_exception_on_error=False) + return None + + +try: + import pytest + + @pytest.fixture + def clear_database(): + """Remove Records, RecordTypes, Properties, and Files ONLY IF the CaosDB + server the current connection points to was registered with the appropriate key. + + PyTestInfo Records and the corresponding RecordType and Property are preserved. + """ + _assure_test_is_registered() + yield _clear_database() # called before the test function + _clear_database() # called after the test function +except ImportError: + raise Warning("""The register_tests module depends on pytest and is + intended to be used in integration test suites for the + caosdb-pylib library only.""") diff --git a/src/doc/administration.rst b/src/doc/administration.rst index 061acc8364d2ef62f743a20d7b9e6562baac0fc5..eab02e43a833559dc21ea7a9fa5edfaf6431facf 100644 --- a/src/doc/administration.rst +++ b/src/doc/administration.rst @@ -5,10 +5,12 @@ The Python script ``caosdb_admin.py`` should be used for administrative tasks. Call ``caosdb_admin.py --help`` to see how to use it. The most common task is to create a new user (in the CaosDB realm) and set a -password for the user (note that a user typically needs to be activated):: +password for the user (note that a user typically needs to be activated): - caosdb_admin.py create_user anna - caosdb_admin.py set_user_password anna - caosdb_admin.py add_user_roles anna administration - caosdb_admin.py activate_user anna +.. code:: console + + $ caosdb_admin.py create_user anna + $ caosdb_admin.py set_user_password anna + $ caosdb_admin.py add_user_roles anna administration + $ caosdb_admin.py activate_user anna diff --git a/src/doc/conf.py b/src/doc/conf.py index 77531343d49a9bfa9b1d30d7681f040509d0c336..7f5f70a82fc2782cba18891bcb23598a93033b59 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -29,10 +29,10 @@ copyright = '2022, IndiScale GmbH' author = 'Daniel Hornung' # The short X.Y version -version = '0.8.0' +version = '0.9.0' # The full version, including alpha/beta/rc tags # release = '0.5.2-rc2' -release = '0.8.0' +release = '0.9.0' # -- General configuration --------------------------------------------------- diff --git a/src/doc/gallery/curator-permissions.rst b/src/doc/gallery/curator-permissions.rst new file mode 100644 index 0000000000000000000000000000000000000000..fa6b4022b7fbc1d042ed00f265e63a2675794a21 --- /dev/null +++ b/src/doc/gallery/curator-permissions.rst @@ -0,0 +1,123 @@ + +Setting permissions for a curator role +====================================== + +The following example shows how to create and set permissions for a ``curator`` +role that is allowed to insert, update, or delete any entity apart from a set of +RecordTypes and properties that define a "core data model" which can only be +altered with administration permissions. + +In the following, you'll learn how to + +1. create the ``curator`` role. +2. configure the ``global_entity_permissions.xml`` s.th. the ``curator`` role is + allowed to insert, update, or delete any entity by default. +3. use a Python script to override the above configuration for the entities in + the externally defined core data model. + +Prerequisites +------------- + +This example needs some preparations regarding your CaosDB setup that have to +(or, for the sake of simplicity, should) be done outside the actual Python +example script. + +The curator role +~~~~~~~~~~~~~~~~ + +First, a ``curator`` role is created with a meaningful description. We'll use +``caosdb_admin.py`` for this which leads to the following command: + +.. code:: console + + $ caosdb_admin.py create_role "curator" "A user who is permitted to create new Records, Properties, and RecordTypes but who is not allowed to change the core data model." + +To actually see how this role's permissions change, we also need a user with +this role. Assume you already have created and activated (see +:doc:`Administration <../administration>`) a ``test_curator`` user, then +``caosdb_admin.py`` is used again to assign it the correct role: + +.. code:: console + + $ caosdb_admin.py add_user_roles test_curator curator + +.. note:: + + The ``test_curator`` user shouldn't have administration privileges, otherwise + the below changes won't have any effect. + +The core data model and caosdb-advanced-user-tools +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In principle, the following script works with any data model defined in a json +or yaml file (just adapt lines 39-42 accordingly). In this example, we'll use the +`metadata schema <https://github.com/leibniz-zmt/zmt-metadata-schema>`_ that was +developed by J. Schmidt at the `Leibniz Centre for Tropical Marine Research +<https://www.leibniz-zmt.de/en/>`_. + +Clone the schemata into the same directory containing the below script via + +.. code:: console + + $ git clone https://github.com/leibniz-zmt/zmt-metadata-schema.git + +Furthermore, we'll need the `CaosDB Advanced User Tools +<https://gitlab.com/caosdb/caosdb-advanced-user-tools>`_ for loading the +metadata schemata from the json files, so install them via + +.. code:: console + + $ pip install caosadvancedtools + +The global entity permissions file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Users with the ``curator`` role should be able to have any permission for all +entities by default. The exceptions for the core data model entities will be set +with the script below. These default settings are best done via the +``global_entities_permissions.xml`` config file (see the `server documentation +<https://docs.indiscale.com/caosdb-server/permissions.html#how-to-set-permissions>`_). Simply +add the following line to the file + +.. code:: xml + + <Grant priority="true" role="curator"><Permission name="*"/></Grant> + +This means that, by default, all users with the ``curator`` role are **granted** +all entity permissions (including insert, update, and delete as specified in the +beginning) **with priority**. This ensures, that no normal user is allowed to +overrule these permissions (since it is granted with priority), but it can still +be denied for the core data model entities by a **deny** rule with priority. See +the server documentation on `permission +calculation <https://docs.indiscale.com/caosdb-server/permissions.html#permission-calculation>`_ +for more information on which permission rules can or can't be overruled. + +Your complete ``global_entities_permissions.xml`` might then look like + +.. code:: xml + + <globalPermissions> + <Grant priority="false" role="?OWNER?"><Permission name="*"/></Grant> + <Grant priority="false" role="?OTHER?"><Permission name="RETRIEVE:*"/></Grant> + <Grant priority="false" role="?OTHER?"><Permission name="USE:*"/></Grant> + <Grant priority="false" role="anonymous"><Permission name="RETRIEVE:*"/></Grant> + <Grant priority="true" role="curator"><Permission name="*"/></Grant> + <Deny priority="false" role="?OTHER?"><Permission name="UPDATE:*"/></Deny> + <Deny priority="false" role="?OTHER?"><Permission name="DELETE"/></Deny> + <Deny priority="true" role="?OTHER?"><Permission name="EDIT:ACL"/></Deny> + </globalPermissions> + +.. note:: + + Note that you have to restart your CaosDB server after modifying the + ``global_entities_permissions.xml``. + +The code +-------- + +After having applied all of the above prerequisites and restarting your CaosDB +server, execute the following code. + +:download:`Download full code<curator_permissions.py>` + +.. literalinclude:: curator_permissions.py diff --git a/src/doc/gallery/curator_permissions.py b/src/doc/gallery/curator_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..16b4b7f6f1bb9abfb7e191c6a1101181984bce9a --- /dev/null +++ b/src/doc/gallery/curator_permissions.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2022 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@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/>. +# + +import os +import sys + +import caosdb as db +from caosadvancedtools.models.parser import parse_model_from_json_schema +from caosdb import administration as admin + +CURATOR = "curator" + + +def main(): + """Set curator role permissions: Is allowed to edit all Records; is allowed + to create new RTs and Properties and change them, but is not allowed to + change anything defined in the core data model, i.e., in the schemas. + + """ + dataspace_definitions = parse_model_from_json_schema( + "zmt-metadata-schema/schemas/dataspace.schema.json") + dataset_definitions = parse_model_from_json_schema( + "zmt-metadata-schema/schemas/dataset.schema.json") + + # Set general permissions. The curator users should be allowed to perform + # any transaction. + perms = admin._get_permissions(CURATOR) + general_grant_perms = [ + "TRANSACTION:*" + ] + + for p in general_grant_perms: + + g = admin.PermissionRule(action="Grant", permission=p, priority=True) + d = admin.PermissionRule(action="Deny", permission=p, priority=True) + + if g in perms: + perms.remove(g) + if d in perms: + perms.remove(d) + perms.add(g) + + admin._set_permissions(CURATOR, permission_rules=perms) + + # Deny all permissions that could change the data model ... + core_model_deny_permissions = [ + "DELETE", + "UPDATE:*", + "EDIT:ACL" + ] + # ... but allow read-access and of course using the entities as parents, + # properties, ... + core_model_grant_permissions = [ + "RETRIEVE:*", + "USE:*", + ] + + # Iterate over all entities defined in the schemas and update their access control list (ACL) accordingly. + updates = db.Container() + for model in [dataspace_definitions, dataset_definitions]: + + for ent in model.values(): + if ent.name in [u.name for u in updates]: + # Skip entities that have been updated already + continue + # The entity needs to be retrieved with the ACL flag to update the + # ACL down the road + ent.retrieve(flags={"ACL": None}) + for d in core_model_deny_permissions: + ent.deny(role=CURATOR, priority=True, permission=d) + ent.update_acl() + ent.retrieve(flags={"ACL": None}) + for g in core_model_grant_permissions: + ent.grant(role=CURATOR, priority=True, permission=g) + updates.append(ent) + ent.update_acl() + + +if __name__ == "__main__": + + sys.exit(main()) diff --git a/src/doc/gallery/index.rst b/src/doc/gallery/index.rst index a6ef53e4c7d1272c5dbc8c62b4d90a89591cac0f..bfba4317c3556d0692eb402f42ba3699be586d5a 100644 --- a/src/doc/gallery/index.rst +++ b/src/doc/gallery/index.rst @@ -14,3 +14,4 @@ This chapter collects code examples which can be immediately run against an empt :caption: The code examples: simulation + curator-permissions diff --git a/src/doc/tutorials/first_steps.rst b/src/doc/tutorials/first_steps.rst index 34b96bbeca416107fb34feb4707b9ef46fc49fe7..486cd4d437c8b13a253cadc8ed45f49b4a7634e4 100644 --- a/src/doc/tutorials/first_steps.rst +++ b/src/doc/tutorials/first_steps.rst @@ -87,7 +87,7 @@ Ids can also come in handy when searching. Suppose you have some complicated con >>> # This condition is not that complicated and long but let's suppose it was. ->>> record = db.execute_query("FIND Analysis with quality_factor=0.08", unique=True) +>>> record = db.execute_query("FIND MusicalAnalysis with quality_factor=0.08", unique=True) >>> # You can use unique=True when you only expect one result Entity. An error will be >>> # thrown if the number of results is unequal to 1 and the resulting object will be >>> # an Entity and not a Container diff --git a/unittests/docker/Dockerfile b/unittests/docker/Dockerfile index 7fa3f75bd198724628dee48ab328829fa071a639..06f9d6c830068a2c1c85caef79c64f899eaefb33 100644 --- a/unittests/docker/Dockerfile +++ b/unittests/docker/Dockerfile @@ -1,4 +1,8 @@ FROM debian:latest +# Use local package repository +COPY sources.list.local /etc/apt/ +RUN mv /etc/apt/sources.list /etc/apt/sources.list.orig +RUN cat /etc/apt/sources.list.local /etc/apt/sources.list.orig > /etc/apt/sources.list RUN apt-get update && \ apt-get install -y \ pylint3 python3-pip tox git \ diff --git a/unittests/docker/sources.list.local b/unittests/docker/sources.list.local new file mode 100644 index 0000000000000000000000000000000000000000..c0b4107350ba37e77aa95d5a56c31976979e51e1 --- /dev/null +++ b/unittests/docker/sources.list.local @@ -0,0 +1,6 @@ +# Local repositories at Netcup +deb http://debian.netcup.net/debian/ buster main +deb http://mirrors.n-ix.net/debian-security buster/updates main +deb http://debian.netcup.net/debian/ buster-updates main + +# The original content follows here: \ No newline at end of file diff --git a/unittests/test_apiutils.py b/unittests/test_apiutils.py index 43ab8107183f16bf8df1d0ea8e447b378bcf8123..2ebdf95a3aa5ce76b983b2c3c47630e1a8884705 100644 --- a/unittests/test_apiutils.py +++ b/unittests/test_apiutils.py @@ -296,6 +296,19 @@ def test_merge_entities(): assert r2.get_property("F").value == "text" +def test_merge_bug_conflict(): + r = db.Record() + r.add_property(name="C", value=4) + r2 = db.Record() + r2.add_property(name="C", value=4, datatype="TEXT") + merge_entities(r, r2) + + r3 = db.Record() + r3.add_property(name="C", value=4, datatype="INTEGER") + with pytest.raises(RuntimeError) as excinfo: + merge_entities(r3, r2) + + def test_merge_bug_109(): rt = db.RecordType(name="TestBug") p = db.Property(name="test_bug_property", datatype=db.LIST(db.INTEGER)) diff --git a/unittests/test_configs/pycaosdb6.ini b/unittests/test_configs/pycaosdb6.ini new file mode 100644 index 0000000000000000000000000000000000000000..3826564f043c5702385a3d093cb4ebb8d4c24cd2 --- /dev/null +++ b/unittests/test_configs/pycaosdb6.ini @@ -0,0 +1,4 @@ +[Connection] +url=https://localhost:10443/ +# No username, unauthenticated connection +password_method = unauthenticated