# -*- coding: utf-8 -*-
# This file is a part of the CaosDB Project.
#
# Copyright (c) 2020 - 2024 IndiScale GmbH <info@indiscale.com>
# Copyright (c) 2022 Daniel Hornung <d.hornung@indiscale.com>
# Copyright (c) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com>
# Copyright (c) 2021 - 2024 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/>.

"""Tests for issues on gitlab.com, project linkahead-server."""

import math
import os
import tempfile
import time

import linkahead as db
import pytest

from linkahead import administration as admin
from linkahead.exceptions import (TransactionError, HTTPClientError, HTTPURITooLongError)

CURATOR_ROLE = "curator"


def setup_module():
    db.configure_connection()
    try:
        db.execute_query("FIND ENTITY WITH ID > 99").delete()
    except Exception as delete_exc:
        print(delete_exc)
    try:
        admin._delete_user("TestUser")
    except Exception as delete_exc:
        print(delete_exc)
    try:
        admin._delete_role(CURATOR_ROLE)
    except Exception as delete_exc:
        print(delete_exc)


def setup_function(function):
    """No setup required."""
    setup_module()


def teardown_function(function):
    """Deleting entities again."""
    setup_module()


# ########################### Issue tests start here #####################


def test_issue_39():
    """Query Language Bug - quotes around year

    Test for https://gitlab.com/caosdb/caosdb-server/-/issues/39
    """
    date_str = "2020-01-01"
    prop_name = "Test_Date"
    db.Property(name=prop_name, datatype=db.DATETIME).insert()
    db.RecordType(name="Test_Type").add_property(name=prop_name).insert()
    db.Record(name="Test_Record").add_parent(name="Test_Type").add_property(
        name=prop_name, value=date_str).insert()
    # Quotes around years should work in the query
    ent = db.execute_query("FIND entity WITH A Test_Date IN \"2020\"",
                           unique=True)
    assert ent.get_property(prop_name).value == date_str
    # This should raise a transaction error
    with pytest.raises(TransactionError):
        db.execute_query("FIND entity WITH A Test_Date IN \"abcd\"")


@pytest.mark.xfail(reason="to be fixed in server repo")
def test_issue_62():
    """datatype is not changed when recordtype name changes

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/62
    """
    db.RecordType(name="Test_RTA").insert()
    db.Property(name="Test_Prop", datatype="Test_RTA").insert()
    db.Record(name="Test_Record").add_parent(
        name="Test_RTA").add_property(name="Test_Prop").insert()
    # rename Test_RTA to Test_RTB
    rtb = db.execute_query("FIND RecordType Test_RTA", unique=True)
    rtb.name = "Test_RTB"
    rtb.update()
    # renaming has to be reflected in Test_Record and Test_Prop
    rec = db.execute_query("FIND Record Test_Record", unique=True)
    assert rec.parents[0].name == rtb.name
    assert rec.get_property("Test_Prop").datatype == rtb.name
    prop = db.execute_query("FIND Property Test_Prop", unique=True)
    assert prop.datatype == rtb.name  # fails; datatype not updated
    # Can't use Test_RTA as datatype anymore
    prop2 = db.Property(name="Test_Prop2", datatype="Test_RTA")
    with pytest.raises(TransactionError) as exc:
        prop2.insert()
    assert "Unknown data type." in str(exc.value)


def test_issue_85_a():
    """SQLIntegrityConstraintViolationException for special inheritance patterns.

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/85
    """
    A = db.RecordType(name="A")
    B = db.RecordType(name="B")
    C = db.RecordType(name="C")

    B.add_parent(A)

    # This order is important for the test to fail.
    C.add_parent(B)
    C.add_parent(C)
    C.add_parent(A)

    c = db.Container()
    # c.extend([C, B, A])  # worked before #86 was fixed
    # c.extend([C, A, B])  # worked before #86 was fixed
    c.extend([B, C, A])    # insert() failed before #86 was fixed
    c.insert()  # Raised java.sql.SQLIntegrityConstraintViolationException:
    #           # Duplicate entry '12345-12346-12345' for key 'PRIMARY'


def test_issue_85_b():
    """SQLIntegrityConstraintViolationException for special inheritance patterns.

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/85
    """
    A = db.RecordType(name="A")
    B = db.RecordType(name="B")
    C = db.RecordType(name="C")
    A.insert()
    B.insert()
    C.insert()
    B.add_parent(A)
    B.update()
    C.add_parent(B)
    C.update()
    C.add_parent(C)
    C.update()
    C.add_parent(A)
    C.update()  # Failed at this step


@pytest.mark.local_server
def test_issue_99():
    """Checksum updating failed with versioning enabled.
    """

    # Using files in extroot, because this allows us to update the file
    # content from the outside.
    local_dir = os.path.join(db.get_config().get("IntegrationTests",
                                                 "test_files.test_insert_files_in_dir.local"),
                             "test_issue_99")
    docker_dir = os.path.join(db.get_config().get("IntegrationTests",
                                                  "test_files.test_insert_files_in_dir.server"),
                              "test_issue_99")
    os.makedirs(local_dir, exist_ok=True)
    with tempfile.NamedTemporaryFile(dir=local_dir) as file_99:
        # Create File entity in CaosDB
        file_99.write("test 99\n".encode())
        os.fchmod(file_99.fileno(), 0o744)  # make the file world readable
        cont = db.Container()
        cont.insert(unique=False, raise_exception_on_error=False,
                    flags={"InsertFilesInDir": docker_dir})
        dbfile = cont[0]

        # Checksum should exist after a short time
        time.sleep(1)
        dbfile.retrieve()
        assert dbfile.checksum is not None


def test_issue_110():
    """query ignores ID: FIND MusicalInstrument which is referenced by Analysis with ID=124 """
    cont = db.Container()
    A = db.RecordType(name="TypeA")
    B = db.RecordType(name="TypeB")
    prop = db.Property(name="prop_ba", datatype=db.REFERENCE)

    # Referenced Records
    a1 = db.Record().add_parent(A)
    a2 = db.Record().add_parent(A)

    # Referencing Records
    b1 = db.Record().add_parent(B).add_property(prop, value=a1)
    b2 = db.Record().add_parent(B).add_property(prop, value=a2)

    cont.extend([A, B, prop, a1, a2, b1, b2])
    cont.insert()

    id_b1 = b1.id
    query = "FIND TypeA WHICH IS REFERENCED BY TypeB WITH ID={}".format(id_b1)
    print(query)
    result = db.execute_query(query)
    print(result)
    assert len(result) == 1
    print(result[0])
    print(a1)
    assert result[0].id == a1.id


def test_issue_120():
    """Editing entities that were created with a no longer existing user leads
    to a server error.

    The server should throw an error when CHECK_ENTITY_ACL_ROLES_MODE=MUST,
    otherwise a warning.
    """
    # insert an entity
    entity = db.RecordType("TestRT").insert(flags={"ACL": None})

    db.administration.set_server_property("CHECK_ENTITY_ACL_ROLES_MODE",
                                          "SHOULD")
    # update with non-existing user, realm and role
    entity.deny(
        realm="CaosDB",
        username="NON_EXISTING_USER",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User role does not exist."

    entity.deny(
        realm="NON_EXISTING_REALM",
        username="NON_EXISTING_USER",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User role does not exist."

    entity.deny(
        role="ALSO_NON_EXISTING_ROLE",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User role does not exist."


def test_issue_134():
    """multiple white space characters after `FROM`"""
    db.execute_query("SELECT pname FROM  ename")


def test_issue_131():
    """white space before unit with strange character"""
    rt = db.RecordType(name="TestType").insert()
    prop = db.Property(name="TestProp", datatype=db.INTEGER, unit="€").insert()
    rec = db.Record(name="TestRecord").add_property(
        name=prop.name, value=101, unit="€")
    rec.add_parent(rt)
    rec.insert()
    result_ids = [ent.id for ent in db.execute_query(
        "FIND Entity WITH {} > 100 €".format(prop.name))]

    assert rec.id in result_ids

    result_ids = [ent.id for ent in db.execute_query(
        "FIND Entity WITH {} > 100.5 €".format(prop.name))]

    assert rec.id in result_ids


def test_issue_154_no_versioning():
    """ FIND MusicalInstrument WITH Manufacturer = "Antonio Stradivari" and
    FIND MusicalInstrument WITH Manufacturer != "Antonio Stradivari" """
    rt_man = db.RecordType("Manufacturer")
    rt_inst = db.RecordType("MusicalInstrument").add_property(rt_man)

    rec_man = db.Record("Antonio Stradivari").add_parent("Manufacturer")
    rec_man2 = db.Record("The other guy").add_parent("Manufacturer")
    rec_inst = db.Record("Violin").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man)
    rec_inst2 = db.Record("Guitar").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man2)
    rec_inst3 = db.Record("Broken Record").add_parent("MusicalInstrument")

    c = db.Container().extend([rt_man, rt_inst, rec_man, rec_inst, rec_man2,
                               rec_inst2, rec_inst3]).insert()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND ENTITY MusicalInstrument")) == 4
    assert len(db.execute_query("FIND RECORD MusicalInstrument")) == 3
    assert len(db.execute_query(
        "FIND ENTITY MusicalInstrument WITH Manufacturer")) == 3
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer")) == 2
    assert rec_inst.id == db.execute_query(
        "FIND ENTITY MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'",
        unique=True).id
    assert len(db.execute_query(
        "FIND ENTITY MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 3
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ENTITY MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1


def test_issue_154_with_versioning():
    """ FIND MusicalInstrument WITH Manufacturer = "Antonio Stradivari" and
    FIND MusicalInstrument WITH Manufacturer != "Antonio Stradivari" """
    rt_man = db.RecordType("Manufacturer")
    rt_inst = db.RecordType("MusicalInstrument").add_property(rt_man)

    rec_man = db.Record("Antonio Stradivari").add_parent("Manufacturer")
    rec_man2 = db.Record("The other guy").add_parent("Manufacturer")
    rec_inst = db.Record("Violin").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man)
    rec_inst2 = db.Record("Guitar").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man2)
    rec_inst3 = db.Record("Broken Record").add_parent("MusicalInstrument")

    db.Container().extend([rt_man, rt_inst, rec_man,
                           rec_inst, rec_man2, rec_inst2, rec_inst3]).insert()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND ANY VERSION OF ENTITY MusicalInstrument")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer")) == 2
    assert rec_inst.id == db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'",
        unique=True).id
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1

    # now, some updates
    rt_man.description = "Updated Description"
    rt_inst.description = "Updated Description"

    rec_man.description = "Updated Description"
    rec_man2.description = "Updated Description"
    rec_inst.description = "Updated Description"
    rec_inst2.description = "Updated Description"
    rec_inst3.description = "Updated Description"
    db.Container().extend([rt_man, rt_inst, rec_man,
                           rec_inst, rec_man2, rec_inst2, rec_inst3]).update()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND ANY VERSION OF ENTITY MusicalInstrument")) == 8
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF ENTITY MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 2


def test_issue_127():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/127"""
    p = db.Property(
        name="TestDoubleProperty",
        datatype=db.LIST(
            db.DOUBLE)).insert()
    rt = db.RecordType(name="TestRecordType").add_property(name="TestDoubleProperty",
                                                           value=["nan"]).insert()

    test1 = db.execute_query("FIND ENTITY TestRecordType", unique=True)
    assert math.isnan(test1.get_property("TestDoubleProperty").value[0])

    test2 = db.execute_query(
        "FIND ENTITY TestRecordType WITH TestDoubleProperty = NaN", unique=True)
    assert math.isnan(test1.get_property("TestDoubleProperty").value[0])


def test_issue_170():
    """update scalar data type to list data type"""
    p = db.Property(name="TestProp1", datatype=db.LIST(db.INTEGER))
    p.value = [1, 2]
    p.insert()

    p2 = db.execute_query("FIND ENTITY TestProp1", unique=True)
    assert p2.datatype == db.LIST(db.INTEGER)
    assert p2.value == [1, 2]

    p.description = "TestDescription"
    p.update()  # this failed

    p2 = db.execute_query("FIND ENTITY TestProp1", unique=True)
    assert p2.datatype == db.LIST(db.INTEGER)
    assert p2.value == [1, 2]
    assert p2.description == "TestDescription"

    p = db.Property(name="TestProp2", datatype=db.DOUBLE)
    p.insert()

    p.datatype = db.LIST(db.INTEGER)
    p.update()  # this worked because no value yet
    p2 = db.execute_query("FIND ENTITY TestProp2", unique=True)
    assert p2.datatype == db.LIST(db.INTEGER)
    p.value = [1, 2]

    p.update()  # this failed
    p2 = db.execute_query("FIND ENTITY TestProp2", unique=True)
    assert p2.datatype == db.LIST(db.INTEGER)
    assert p2.value == [1, 2]

    p = db.Property(name="TestProp3", datatype=db.DOUBLE)
    p.insert()

    p.datatype = db.LIST(db.INTEGER)
    p.value = [1, 2]
    p.update()  # this failed

    p2 = db.execute_query("FIND ENTITY TestProp3", unique=True)
    assert p2.datatype == db.LIST(db.INTEGER)
    assert p2.value == [1, 2]


def test_issue_181():
    """https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/181"""
    rt = db.RecordType("TestRT").insert()
    assert len(db.execute_query("FIND RECORDTYPE TestRT")) == 1
    assert len(db.execute_query(
        "FIND RECORDTYPE TestRT WHICH HAS BEEN UPDATED TODAY")) == 0
    rt.description = "New description"
    rt.update()
    assert len(db.execute_query(
        "FIND RECORDTYPE TestRT WHICH HAS BEEN UPDATED TODAY")) == 1


def test_issue_183():
    """No reasonable error when using bad datetime format.
    https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/183
    """

    # Date YYYY-MM-ddThh:mm:ss
    assert db.Property(name="TestDateTime", datatype=db.DATETIME,
                       value="2015-05-05T20:15:00").insert().id is not None
    with pytest.raises(db.TransactionError) as cm:
        # Date YYYY-MM-ddThh:mm
        db.Property(name="TestDateTime2", datatype=db.DATETIME,
                    value="2015-05-05T20:15").insert()
    assert cm.value.errors[0].msg == ("Cannot parse value to datetime format "
                                      "(yyyy-mm-dd'T'hh:mm:ss[.fffffffff][TimeZone]).")


def test_issue_130():
    """Test select queries where names contain spaces

    https://gitlab.com/caosdb/caosdb-server/-/issues/130

    However, this bug was actually about quotation marks
    """
    db.RecordType(name="TestRT_A").insert()
    r1 = db.Record("ReferencedRecord").add_parent("TestRT_A").insert()
    p1 = db.Property(name="TestWrapper", datatype="TestRT_A").insert()
    p2 = db.Property(
        name="TestWrapper With Spaces",
        datatype="TestRT_A").insert()
    db.RecordType(name="TestRT_B"
                  ).add_property(name="TestWrapper"
                                 ).add_property("TestWrapper With Spaces"
                                                ).insert()
    db.Record().add_parent("TestRT_B"
                           ).add_property("TestWrapper", value=r1
                                          ).add_property("TestWrapper With Spaces",
                                                         value=r1
                                                         ).insert()

    query = "SELECT TestWrapper FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper"))
    assert row == [(r1.id,)]

    query = "SELECT TestWrapper FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper", "id"))
    assert row == [(p1.id,)]

    query = "SELECT 'TestWrapper' FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper", "id"))
    assert row == [(p1.id,)]

    query = "SELECT TestWrapper FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper", "name"))
    assert row == [("TestWrapper",)]

    query = "SELECT TestWrapper.name FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper", "name"))
    assert row == [("ReferencedRecord",)]

    query = "SELECT 'TestWrapper.name' FROM RECORD TestRT_B"
    rec = db.execute_query(query, unique=True)
    assert len(rec.properties) == 0

    query = "SELECT 'TestWrapper'.name FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(("TestWrapper", "name"))
    assert row == [("ReferencedRecord",)]

    query = "SELECT TestWrapper With Spaces FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces"))
    assert row == [(r1.id,)]

    query = "SELECT TestWrapper With Spaces FROM RECORD TestRT_B"
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces", "id"))
    assert row == [(p2.id,)]

    query = 'SELECT TestWrapper With Spaces FROM RECORD TestRT_B'
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces", "name"))
    assert row == [("TestWrapper With Spaces",)]

    query = 'SELECT "TestWrapper With Spaces" FROM RECORD TestRT_B'
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces", "name"))
    assert row == [("TestWrapper With Spaces",)]

    query = 'SELECT TestWrapper With Spaces.name FROM RECORD TestRT_B'
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces", "name"))
    assert row == [("ReferencedRecord",)]

    query = 'SELECT "TestWrapper With Spaces".name FROM RECORD TestRT_B'
    row = db.execute_query(query).get_property_values(
        ("TestWrapper With Spaces", "name"))
    assert row == [("ReferencedRecord",)]


def test_issue_132():
    """Query: Parenthesis around subproperties.

    https://gitlab.com/caosdb/caosdb-server/-/issues/132
    """
    db.RecordType("TestRT").insert()
    db.RecordType("TestRT_Foo").insert()
    db.Property("TestP_Bar", datatype=db.TEXT).insert()
    db.Property("TestP_Baz", datatype=db.TEXT).insert()

    rt1 = db.Record().add_parent("TestRT_Foo").add_property(
        "TestP_Bar", "val1").add_property(
        "TestP_Baz", "the other baz").insert()
    rt2 = db.Record().add_parent("TestRT").add_property(
        "TestP_Baz", "val2").add_property(
        "TestRT_Foo", rt1).insert()

    query = "FIND RECORD TestRT_Foo"
    assert db.execute_query(query, unique=True).id == rt1.id

    query = "FIND RECORD TestRT"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH TestRT_Foo"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH TestRT_Foo.TestP_Bar"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH TestRT_Foo.TestP_Bar = val1"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH (TestRT_Foo.TestP_Bar = val1)"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH ( TestRT_Foo.TestP_Bar = val1 )"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH (TestRT_Foo.TestP_Bar = val1) AND TestP_Baz = val2"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH (TestRT_Foo WITH (TestP_Bar = val1 AND TestP_Baz = 'the other baz')) AND TestP_Baz = val2"
    assert db.execute_query(query, unique=True).id == rt2.id

    query = "FIND RECORD TestRT WITH TestRT_Foo WITH (TestP_Bar = val1 AND TestP_Baz = 'the other baz') AND TestP_Baz = val2"
    assert db.execute_query(query, unique=True).id == rt2.id

    # 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


def test_134_1():
    """CQL: Subproperties are not recognized in list of references.

    https://gitlab.com/caosdb/caosdb-server/-/issues/134
    """
    p_lng = db.Property(name="longitude", datatype=db.DOUBLE).insert()
    p_lat = db.Property(name="latitude", datatype=db.DOUBLE).insert()
    rt_ev = db.RecordType(name="Event").add_property(
        p_lng).add_property(p_lat).insert()
    p_ev = db.Property(name="events", datatype=db.LIST(rt_ev)).insert()
    rt_ds = db.RecordType(name="DataSet").add_property(p_ev).insert()

    r_ev_1 = db.Record().add_parent("Event").add_property("longitude",
                                                          0.1).add_property("latitude",
                                                                            0.1).insert()
    r_ev_2 = db.Record().add_parent("Event").add_property("longitude",
                                                          0.2).add_property("latitude",
                                                                            0.2).insert()

    r_ds = db.Record().add_parent("DataSet").add_property(
        "events", value=[r_ev_1, r_ev_2]).insert()

    result = db.execute_query("SELECT events.latitude FROM RECORD DataSet",
                              unique=True)
    print(result)
    assert len(result.get_property("events").value) == 2
    assert result.get_property("events").value[0].get_property(
        "latitude").value == 0.1
    assert result.get_property("events").value[1].get_property(
        "latitude").value == 0.2


def test_134_2():
    """CQL: Subproperties are not recognized in list of references.

    https://gitlab.com/caosdb/caosdb-server/-/issues/134
    """
    p_lng = db.Property(name="longitude", datatype=db.DOUBLE).insert()
    p_lat = db.Property(name="latitude", datatype=db.DOUBLE).insert()
    rt_ev = db.RecordType(name="Event").add_property(
        p_lng).add_property(p_lat).insert()
    rt_ds = db.RecordType(name="DataSet").add_property(name="Event",
                                                       datatype=db.LIST(rt_ev)).insert()

    r_ev_1 = db.Record().add_parent("Event").add_property("longitude",
                                                          0.1).add_property("latitude",
                                                                            0.1).insert()
    r_ev_2 = db.Record().add_parent("Event").add_property("longitude",
                                                          0.2).add_property("latitude",
                                                                            0.2).insert()

    r_ds = db.Record().add_parent("DataSet").add_property(
        "Event", datatype=db.LIST(rt_ev), value=[r_ev_1, r_ev_2]).insert()

    result = db.execute_query("SELECT Event.latitude FROM RECORD DataSet",
                              unique=True)
    assert len(result.get_property("Event").value) == 2
    assert result.get_property("Event").value[0].get_property(
        "latitude").value == 0.1
    assert result.get_property("Event").value[1].get_property(
        "latitude").value == 0.2


def test_136():
    """Faulty creation of a multi-property when updating a non-list property
    with a list value.

    https://gitlab.com/caosdb/caosdb-server/-/issues/136

    """
    # @author Florian Spreckelsen
    # @date 2022-05-23

    # Insert data model:
    rt = db.RecordType(name="TestBug")
    p = db.Property(name="TestBugProperty", datatype=db.INTEGER)
    db.Container().extend([rt, p]).insert()

    # Insert test record:
    r = db.Record(name="TestRecord")
    r.add_parent(rt)
    r.add_property(p, value=18)
    r.insert()

    # Update the record:
    test_r = db.Record(id=r.id).retrieve()
    test_r.add_parent(rt)
    test_r.add_property(id=p.id, value=[18, 12])
    with pytest.raises(db.TransactionError) as err:
        test_r.update()

    te = err.value
    assert te.has_error(db.UnqualifiedPropertiesError)
    assert "This data type does not accept collections of values (e.g. Lists)" in str(te)


@pytest.mark.xfail(reason="Fix https://gitlab.com/caosdb/caosdb-pylib/-/issues/81")
def test_136_b():
    """Faulty creation of a multi-property when updating a non-list property
    with a list value.

    https://gitlab.com/caosdb/caosdb-server/-/issues/136

    """
    # @author Florian Spreckelsen
    # @date 2022-05-23

    # Insert data model:
    rt = db.RecordType(name="TestBug")
    p = db.Property(name="TestBugProperty", datatype=db.TEXT)
    db.Container().extend([rt, p]).insert()

    # Insert test record:
    r = db.Record(name="TestRecord")
    r.add_parent(rt)
    r.add_property(p, value="val1")
    r.insert()

    # Update the record:
    test_r = db.Record(id=r.id).retrieve()
    test_r.add_parent(rt)
    test_r.add_property(id=p.id, value=["val1", "val2"])
    with pytest.raises(db.TransactionError) as err:
        test_r.update()

    te = err.value
    assert te.has_error(db.UnqualifiedPropertiesError)
    assert "This data type does not accept collections of values (e.g. Lists)" in str(te)


def test_141():
    """Roles with `Grant(*)P` permissions still can't update other people's
    entities."""
    admin._insert_role(name=CURATOR_ROLE, description="Desc")

    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Grant", permission="*", priority=True)
    d = admin.PermissionRule(action="Deny", permission="*", priority=True)
    if g in perms:
        perms.remove(g)
    if d in perms:
        perms.remove(d)
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)
    perms = admin._get_permissions(CURATOR_ROLE)
    print(perms)

    rt = db.RecordType(name="TestRT", description="Desc1").insert()

    admin._insert_user(name="TestUser", password="Password1!", status="ACTIVE")
    admin._set_roles(username="TestUser", roles=[CURATOR_ROLE])

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")
    assert db.Info().user_info.name == "TestUser"
    assert db.Info().user_info.roles == [CURATOR_ROLE]

    rt.description = "Desc2"
    rt.update()
    assert rt.description == "Desc2"

    # switch back to admin user
    db.configure_connection()
    assert db.execute_query("FIND ENTITY TestRT", unique=True).description == "Desc2"


def test_145():
    """Searching for large numbers results in wrong results if integer values
    are used.

    https://gitlab.com/caosdb/caosdb-server/-/issues/145
    """
    db.Property("TestProp", datatype=db.TEXT).insert()
    db.Property("TestPropInt", datatype=db.INTEGER).add_parent(
        "TestProp").insert()
    db.Property("TestPropDouble", datatype=db.DOUBLE).add_parent(
        "TestProp").insert()

    db.RecordType("TestRT").insert()
    rec1 = db.Record("TestRec1").add_parent("TestRT").add_property(
        "TestPropInt", 1_000_000_000).insert()
    assert rec1.get_property("TestPropInt").value == 1_000_000_000
    assert isinstance(rec1.get_property("TestPropInt").value, int)
    rec2 = db.Record("TestRec2").add_parent("TestRT").add_property(
        "TestPropDouble", 20_000_000_000).insert()
    assert rec2.get_property("TestPropDouble").value == 20_000_000_000
    assert isinstance(rec2.get_property("TestPropDouble").value, float)

    assert db.execute_query(
        "FIND TestRT WITH TestProp = 1000000000", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT WITH TestProp = 1000000000.0", unique=True).id == rec1.id

    assert db.execute_query(
        "FIND TestRT WITH TestProp > 1000000000", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT WITH TestProp > 1000000000.0", unique=True).id == rec2.id

    assert db.execute_query(
        "FIND TestRT WITH TestProp = 20000000000", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT WITH TestProp = 20000000000.0", unique=True).id == rec2.id

    assert db.execute_query(
        "FIND TestRT WITH TestProp < 20000000000", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT WITH TestProp < 20000000000.0", unique=True).id == rec1.id

    assert db.execute_query(
        "FIND TestRT WITH TestPropInt < 10000000000000000000000000000000000000000000000000000000000",
        unique=True).id == rec1.id


@pytest.mark.xfail(reason="Fix https://gitlab.com/caosdb/caosdb-server/-/issues/147")
def test_147():
    """Searching for integer numbers results in wrong results if floats are used.

    https://gitlab.com/caosdb/caosdb-server/-/issues/147
    """
    db.Property("TestProp", datatype=db.TEXT).insert()
    db.Property("TestPropInt", datatype=db.INTEGER).add_parent(
        "TestProp").insert()

    db.RecordType("TestRT1").insert()
    db.RecordType("TestRT2").insert()
    rec1 = db.Record("TestRec1").add_parent("TestRT1").add_property(
        "TestPropInt", 1).insert()
    assert rec1.get_property("TestPropInt").value == 1
    assert isinstance(rec1.get_property("TestPropInt").value, int)
    rec2 = db.Record("TestRec2").add_parent("TestRT2").add_property(
        "TestPropInt", -2).insert()

    # Find the records
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp < 1.9", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp < 1.1", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp = 1.0", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp > 0.9", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp > 0.1", unique=True).id == rec1.id

    assert db.execute_query(
        "FIND TestRT1 WITH TestProp <= 1.9", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp <= 1.1", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp >= 0.9", unique=True).id == rec1.id
    assert db.execute_query(
        "FIND TestRT1 WITH TestProp >= 0.1", unique=True).id == rec1.id

    assert db.execute_query(
        "FIND TestRT2 WITH TestProp < -1.1", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp < -1.9", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp = -2.0", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp > -2.1", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp > 2.9", unique=True).id == rec2.id

    assert db.execute_query(
        "FIND TestRT2 WITH TestProp <= -1.1", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp <= -1.9", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp >= -2.1", unique=True).id == rec2.id
    assert db.execute_query(
        "FIND TestRT2 WITH TestProp >= 2.9", unique=True).id == rec2.id

    # Don't find the records
    assert len(db.execute_query("FIND TestRT1 WITH TestProp < 0.9")) == 0
    assert len(db.execute_query("FIND TestRT1 WITH TestProp <= 0.9")) == 0
    assert len(db.execute_query("FIND TestRT1 WITH TestProp > 1.1")) == 0
    assert len(db.execute_query("FIND TestRT1 WITH TestProp >= 1.1")) == 0

    assert len(db.execute_query("FIND TestRT2 WITH TestProp > -1.9")) == 0
    assert len(db.execute_query("FIND TestRT2 WITH TestProp >= -1.9")) == 0
    assert len(db.execute_query("FIND TestRT2 WITH TestProp < -2.1")) == 0
    assert len(db.execute_query("FIND TestRT2 WITH TestProp <= -2.1")) == 0

    # Smaller numbers, but querying across number types.
    rec3 = db.Record("TestRec3").add_parent(
        "TestRT").add_property("TestPropInt", 1).insert()
    assert db.execute_query(
        "FIND TestRT WITH TestPropInt < 2", unique=True).id == rec3.id
    assert db.execute_query(
        "FIND TestRT WITH TestPropInt < 2.5", unique=True).id == rec3.id


def test_140():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/140"""
    admin._insert_role(name=CURATOR_ROLE, description="Desc")

    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Grant", permission="TRANSACTION:*")
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)
    admin._insert_user(name="TestUser", password="Password1!", status="ACTIVE")
    admin._set_roles(username="TestUser", roles=[CURATOR_ROLE])

    core_model_deny_permissions = [
        "DELETE",
        "UPDATE:*",
        "EDIT:ACL"
    ]
    core_model_grant_permissions = [
        "RETRIEVE:*",
        "USE:*",
        "UPDATE:PROPERTY:ADD"
    ]

    prop = db.Property(name="TestProp", datatype=db.TEXT).insert()
    rt = db.RecordType(name="TestRT").insert(flags={"ACL": None})

    for d in core_model_deny_permissions:
        # First deny s.th. later the "UPDATE:PROPERTY:ADD" permission can be granted explicitely
        rt.deny(role=CURATOR_ROLE, permission=d)
    rt.update_acl()

    # retrieve again to be sure
    rt.retrieve(flags={"ACL": None})
    for g in core_model_grant_permissions:
        rt.grant(role=CURATOR_ROLE, permission=g)
    rt.update_acl()

    print(rt.acl)

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")
    assert db.Info().user_info.name == "TestUser"
    assert db.Info().user_info.roles == [CURATOR_ROLE]

    rt.add_property(prop)
    rt.get_property("TestProp").value = "some value"

    # this should succeed because the curator has UPDATE:PROPERTY:ADD
    rt.update()

    assert rt.get_property("TestProp").value == "some value"
    rt.get_property("TestProp").value = "some other value"
    with pytest.raises(TransactionError) as cm:
        # this should fail because the curator doesn't have
        # UPDATE:PROPERTY:REMOVE
        rt.update()
    assert cm.value.errors[0].msg == "You are not allowed to do this."


def test_142():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/142"""
    valid_names = [
        "with.dot",
        "with-hyphen",
        "with_underbar",
        "with0number",
        "withAcapital",
        ".withleadingdot",
        "Bwithleadingcapital",
        "1withleadingnumber",
        "_withleadingunderbar",
        "withtrailingcapitalC",
        "withtrailingnumber2",
        "withtrailingunderbar_",
        "withtrailinghyphen-",
        "withtrailingdot.",
        "4",
        "_",
        "D",
        "d",
        ".",
    ]
    invalid_names = [
        "-",
        "-leadinghyphen",
        "",
        "%",
        "/",
        "[asdf]",
        "?",
        '"',
    ]
    for name in valid_names:
        admin._insert_user(
            name=name,
            password="Password1!",
            status="ACTIVE",
            email=None,
            entity=None)
        admin._delete_user(name=name)
    for name in invalid_names:
        with pytest.raises(HTTPClientError) as cm:
            admin._insert_user(
                name=name,
                password="Password1!",
                status="ACTIVE",
                email=None,
                entity=None)
            admin._delete_user(name=name)
        assert cm.value.status == 400
        assert cm.value.msg.startswith(
            "The user name does not comply with the current policies for user names")


@pytest.mark.xfail(reason="Fix https://gitlab.com/caosdb/caosdb-server/-/issues/177")
def test_177():
    db.RecordType("TestRT").insert()
    db.RecordType("TestRT").insert(unique=False)
    db.Property("TestProp", datatype=db.TEXT).insert()

    db.RecordType("TestSubRT").add_property("TestProp").add_parent("TestRT").insert()


@pytest.mark.xfail(reason="Fix https://gitlab.com/caosdb/caosdb-server/-/issues/135")
def test_135():
    db.RecordType("TestRT1").insert()
    db.Property("TestProp", datatype=db.LIST("TestRT1")).insert()
    r1 = db.Record().add_parent("TestRT1").insert()
    r2 = db.Record().add_parent("TestRT1").add_property("TestProp", r1).insert()
    assert len(db.execute_query("FIND ENTITY WHICH IS REFERENCED BY A TestRT1 AS TestProp")) == 1


def test_192():
    """Testing queries with Property by name.

    See https://gitlab.com/caosdb/caosdb-server/-/issues/192

    COUNT Record WHICH HAS price -> Results: 19
    COUNT Record WHICH HAS Property price -> Results: 19
    COUNT Record WITH price -> Results: 19
    COUNT Record WITH Property price -> Results: 0
    """
    db.Property(name="testprop", datatype=db.DOUBLE).insert()
    db.RecordType(name="TestRT").add_property("testprop").insert()
    db.Record(name="Rec1").add_parent("TestRT").add_property("testprop", value=3.1).insert()

    query1 = "COUNT RECORD WHICH HAS testprop"
    query2 = "COUNT RECORD WHICH HAS A testprop"
    query3 = "COUNT RECORD WHICH HAS Property testprop"
    query4 = "COUNT RECORD WHICH HAS A Property testprop"
    query5 = "COUNT RECORD WITH testprop"
    query6 = "COUNT RECORD WITH A testprop"
    query7 = "COUNT RECORD WITH Property testprop"
    query8 = "COUNT RECORD WITH A Property testprop"

    count1 = db.execute_query(query1)
    count2 = db.execute_query(query2)
    count3 = db.execute_query(query3)
    count4 = db.execute_query(query4)
    count5 = db.execute_query(query5)
    count6 = db.execute_query(query6)
    count7 = db.execute_query(query7)
    count8 = db.execute_query(query8)

    assert count1 == 1
    assert count2 == 1
    assert count3 == 1
    assert count4 == 1
    assert count5 == 1
    assert count6 == 1
    assert count7 == 1
    assert count8 == 1


def test_196a():
    """See https://gitlab.com/caosdb/caosdb-server/-/issues/196"""
    admin._insert_role(name=CURATOR_ROLE, description="Desc")

    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Grant", permission="TRANSACTION:*")
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)
    admin._insert_user(name="TestUser", password="Password1!", status="ACTIVE")
    admin._set_roles(username="TestUser", roles=[CURATOR_ROLE])

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")
    # works
    db.RecordType(name="TestRT1").insert()
    db.Property(name="TestProp1", datatype=db.TEXT).insert()

    # Deny TRANSACTION:INSERT:PROPERTY
    db.configure_connection()
    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Deny", permission="TRANSACTION:INSERT:PROPERTY")
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")

    # it is still allowed to insert a record type...
    db.RecordType(name="TestRT2").insert()

    # fails
    with pytest.raises(TransactionError) as cm:
        # this should fail because the curator doesn't have TRANSACTION:INSERT:PROPERTY
        db.Property(name="TestProp2", datatype=db.TEXT).insert()
    assert cm.value.errors[0].msg == "You are not allowed to do this."


@pytest.mark.parametrize("deny", ["TRANSACTION:INSERT:", "TRANSACTION:INSERT:*"])
def test_196b(deny):
    """Same as test_196a but we completely deny insertion."""
    admin._insert_role(name=CURATOR_ROLE, description="Desc")

    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Grant", permission="TRANSACTION:*")
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)
    admin._insert_user(name="TestUser", password="Password1!", status="ACTIVE")
    admin._set_roles(username="TestUser", roles=[CURATOR_ROLE])

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")
    # works
    db.RecordType(name="TestRT1").insert()
    db.Property(name="TestProp1", datatype=db.TEXT).insert()

    # Deny TRANSACTION:INSERT
    db.configure_connection()
    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Deny", permission=deny)
    perms.add(g)
    admin._set_permissions(CURATOR_ROLE, permission_rules=perms)

    db.configure_connection(username="TestUser", password_method="plain",
                            password="Password1!")

    # fails (in contrast to test_196a)
    with pytest.raises(TransactionError) as cm:
        # this should fail because the curator doesn't have TRANSACTION:INSERT:RECORDTYPE
        db.RecordType(name="TestRT2").insert()
    assert cm.value.errors[0].msg == "You are not allowed to do this."

    # fails
    with pytest.raises(TransactionError) as cm:
        # this should fail because the curator doesn't have TRANSACTION:INSERT:PROPERTY
        db.Property(name="TestProp2", datatype=db.TEXT).insert()
    assert cm.value.errors[0].msg == "You are not allowed to do this."


@pytest.mark.parametrize("num", ["1e+23", "5e22", "2e-323", "2E-323", "5E22", "1E+23", "+1E+23"])
def test_143(num):
    """https://gitlab.com/caosdb/caosdb-server/-/issues/144"""
    db.Property(name="scientific_notation", datatype=db.DOUBLE).insert()
    db.RecordType(name="RT1").add_property("scientific_notation", value=num).insert()

    for query in [
        f"FIND RECORDTYPE RT1 WITH scientific_notation={num}",
        f"FIND RECORDTYPE RT1 WITH scientific_notation='{num}'",
        f"FIND RECORDTYPE RT1 WITH scientific_notation=\"{num}\"",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = {num}",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = '{num}'",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = \"{num}\""
    ]:
        db.execute_query(query, unique=True)


@pytest.mark.parametrize("num", ["1 e+23", "- 5e22", "2e -323",
                                 "2E- 323", "5 E 22", "1 E+ 23", "+ 1"])
def test_143_white_space(num):
    """https://gitlab.com/caosdb/caosdb-server/-/issues/144"""

    for query in [
        f"FIND RECORDTYPE RT1 WITH scientific_notation={num}",
        f"FIND RECORDTYPE RT1 WITH scientific_notation='{num}'",
        f"FIND RECORDTYPE RT1 WITH scientific_notation=\"{num}\"",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = {num}",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = '{num}'",
        f"FIND RECORDTYPE RT1 WITH scientific_notation = \"{num}\""
    ]:
        with pytest.raises(TransactionError) as cm:
            db.execute_query(query)
        assert cm.value.msg == (f'You typed "{num}". Empty spaces are not allowed ' +
                                f'in numbers. Did you mean "{num.replace(" ", "")}"?')


def test_144():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/144"""
    db.Property(name="scientific_notation", datatype=db.DOUBLE, value="1e23").insert()

    value = db.execute_query("FIND PROPERTY scientific_notation", unique=True).value
    assert str(value) == "1e+23"
    assert isinstance(value, float)
    assert value == 1e23
    assert value == 1e+23


def test_166():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/166"""
    db.RecordType(name="exists").insert()
    db.Property(name="exists_property", datatype=db.INTEGER).insert()

    db.RecordType(name="RT1").add_parent("exists").insert()
    db.RecordType(name="RT2").add_parent("exists").add_property("exists_property", 32453).insert()

    with pytest.raises(TransactionError) as cm:
        db.Record(name="RT3").add_parent("notexists").insert()
    assert [e.msg for e in cm.value.errors] == ["Entity has unqualified parents."]

    with pytest.raises(TransactionError) as cm:
        db.Record(name="RT4").add_parent("exists").add_property("notexists", 234243).insert()
    assert [e.msg for e in cm.value.errors] == ["Entity has unqualified properties."]

    with pytest.raises(TransactionError) as cm:
        db.Record(
            name="RT5").add_parent("notexists").add_property(
            "exists_property",
            234243).insert()
    assert [e.msg for e in cm.value.errors] == ["Entity has unqualified parents."]


@pytest.mark.xfail(reason="fix needed")
def test_195():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/195"""
    admin._insert_role(name=CURATOR_ROLE, description="Desc")

    perms = admin._get_permissions(CURATOR_ROLE)
    g = admin.PermissionRule(action="Grant", permission="INVALID_PERMISSION:*")
    perms.add(g)
    with pytest.raises(Exception):
        admin._set_permissions(CURATOR_ROLE, permission_rules=perms)


def test_216():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/216"""
    p1 = db.Property(name='p1', datatype=db.DOUBLE).insert()

    cont = db.Container()
    cont.append(db.RecordType(name="A")
                .add_property(id=p1.id, name=p1.name, datatype=db.DOUBLE,
                              unit="min",
                              importance=db.RECOMMENDED)
                .add_property(id=p1.id, name=p1.name,
                              importance=db.RECOMMENDED)
                )
    cont.append(db.RecordType(name="B")
                .add_parent(name="A", inheritance=db.SUGGESTED))
    cont.insert()

    assert db.execute_query("FIND RECORDTYPE B", unique=True).name == "B"


def test_138():
    """Problems with non-integer ids in query filters, see
    https://gitlab.com/caosdb/caosdb-server/-/issues/138

    """

    queries = [
        "FIND ENTITY WITH ID={}",
        "FIND ENTITY WITH ID=None",
        "FIND ENTITY WITH ID=\"1 non-existing id\""
    ]
    for query in queries:
        # No error, but of course also no results.
        results = db.execute_query(query)
        assert len(results) == 0


@pytest.mark.xfail(reason="Needs fix for parent name change caching, "
                   "see https://gitlab.com/caosdb/caosdb-server/-/issues/220")
def test_220():
    """Caching of children is not removed.

See https://gitlab.com/caosdb/caosdb-server/-/issues/220"""
    rectype = db.RecordType(name="OldName").insert()
    rec = db.Record(name="rec").add_parent(rectype).insert()

    query = db.Query("FIND rec")
    assert query.cached is None

    res_1 = query.execute(unique=True)
    assert query.cached is False, "First query should be uncached."
    assert res_1.id == rec.id

    res_2 = query.execute(unique=True)
    assert query.cached is True, "Second query should be cached."

    rectype.name = "NewName"
    rectype.update()

    res_3 = query.execute(unique=True)
    assert res_3.parents[0].name == rectype.name, \
        "The name of the record's parent should be up-to-date."
    assert query.cached is False, "Query after name change of parent should not be cached."


@pytest.mark.xfail(reason="Needs fix for intermediate length strings, "
                   "see https://gitlab.com/linkahead/linkahead-server/-/issues/101")
def test_101():
    """Unexpected server errors in case of intermediate length strings,
    https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/101

    """
    # This is ok
    very_long_string = "Long string"*10000
    with pytest.raises(HTTPURITooLongError):
        db.execute_query(f"FIND RECORD WITH test=\"{very_long_string}\"")
    # This is not
    long_string = "Long string"*100
    assert len(db.execute_query(f"FIND RECORD WITH test=\"{long_string}\"")) == 0


@pytest.mark.xfail(reason="Needs fix for keeping datatype, "
                   "see https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/106")
def test_indiscale_106():
    """Datatype of old properties is changed.

See https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/106
    """

    # Create TEXT Property
    p = db.Property("prop", datatype=db.TEXT)
    p.insert()

    # Create Record using this Property
    db.RecordType("RT").insert()
    r = db.Record(name="rec")
    r.add_parent("RT")
    r.add_property(id=p.id, value="This is a TEXT property")
    r.insert()

    print(db.Record(id=r.id).retrieve())
    assert db.Record(id=r.id).retrieve().get_property("prop").datatype == db.TEXT

    # Changing Property's datatype to REFERENCE
    p.datatype = db.REFERENCE
    p.update()

    # Existing Property should still be reported as TEXT
    assert db.Record(id=r.id).retrieve().get_property("prop").datatype == db.TEXT


@pytest.mark.xfail(reason="https://gitlab.com/caosdb/caosdb-server/-/issues/230")
def test_230_name_duplicates_in_list_datatypes():
    """https://gitlab.com/linkahead/linkahead-server/-/issues/230"""
    prop = db.Property(name="Test", datatype=db.TEXT).insert()
    # RT with same name, will be used as datatype
    rt = db.RecordType(name="Test").insert(unique=False)
    rec = db.Record(name="TestRec").add_parent(id=rt.id).insert()
    rec.add_property(id=rt.id, datatype=rt.id).update()  # This works since it's not a list
    rec.add_property(id=rt.id, datatype=db.LIST(rt.id)).update()  # This fails


@pytest.mark.xfail(reason="https://gitlab.com/caosdb/caosdb-server/-/issues/235")
def test_235_long_name():
    """Should give an appropriate error, not just unknown server/-/issues."""
    length = 10256
    name = "N" * length
    rt1 = db.RecordType(name=name)
    try:
        rt1.insert()
    except Exception as exc:
        assert not isinstance(exc, db.HTTPServerError)
        # TODO more specific error should be asserted

    rt2 = db.RecordType(name="Short")
    rt2.insert()
    rt2.name = name
    try:
        rt2.update()
    except Exception as exc:
        assert not isinstance(exc, db.HTTPServerError)
        # TODO more specific error should be asserted


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/248")
def test_248():
    """Querying for entities with property fails if using ID."""
    rt = db.RecordType(name="RT1").insert()
    prop = db.Property(name="prop", datatype=db.DOUBLE).insert()
    rec = db.Record().add_parent(rt).add_property(prop, value=23).insert()
    results = db.execute_query(f"FIND Entity with {prop.id}")
    assert len(results) == 1


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/253")
def test_253():
    """Value in string queries may not start with large number of digits."""
    test_strings = [
        "0123456789",
        "hello" + "0123456789" * 5 + "world",
        "0123456789" * 5 + "world",
    ]
    for string in test_strings:
        results = db.execute_query(f"FIND Entity with prop={string}")
        assert len(results) == 0


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/260")
def test_260_datetime_without_date():
    """Only time value in datetime property must not cause an unexpected server
    error but an unqulified properties error, see
    https://gitlab.com/linkahead/linkahead-server/-/issues/260.

    """
    prop = db.Property(name="TestDateTimeProp", datatype=db.DATETIME).insert()
    rt = db.RecordType(name="TestType").add_property(prop).insert()
    with pytest.raises(TransactionError) as te:
        rec = db.Record().add_parent(rt).add_property(name=prop.name, value="11:00")
        rec.insert()
    assert te.value.has_error(db.UnqualifiedPropertiesError)
    assert "Cannot parse value to datetime format" in te.value.msg


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/263")
def test_263():
    with pytest.raises(TransactionError) as exc:
        db.Record(name="1").retrieve()
    assert len(exc.errors) == 1
    assert exc.errors[0].msg == "Entity does not exist."  # TODO Or whatever the message shall be.


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/264")
def test_264_accents_and_umlauts():
    """Test whether queries respect accents and
    umlauts. https://gitlab.com/linkahead/linkahead-server/-/issues/264.

    """

    # Insertes have to be possible with unique=True since the name differ in
    # accents and umlauts.
    rt_e = db.RecordType(name="Test").insert()
    rt_ë = db.RecordType(name="Tëst").insert()
    rt_è = db.RecordType(name="Tést").insert()

    # Retrieves should be unique here, too.
    for rt in [rt_e, rt_ë, rt_è]:
        retrieved = db.get_entity_by_name(rt.name)
        assert retrieved.id == rt.id


@pytest.mark.xfail(reason="https://gitlab.com/linkahead/linkahead-server/-/issues/266 🤯")
def test_266():
    """Emojis as payload"""
    rt = db.RecordType(name="A nice 🤯 RecordType").insert()
    assert rt.id > 0
    assert "🤯" in rt.name