diff --git a/hyperon_das_atomdb/adapters/ram_only.py b/hyperon_das_atomdb/adapters/ram_only.py index ccd69ea8..2e044e3f 100644 --- a/hyperon_das_atomdb/adapters/ram_only.py +++ b/hyperon_das_atomdb/adapters/ram_only.py @@ -557,8 +557,8 @@ def get_matched_links( link_type_hash = ( WILDCARD if link_type == WILDCARD else ExpressionHasher.named_type_hash(link_type) ) - - if link_type in UNORDERED_LINK_TYPES: + # NOTE unreachable + if link_type in UNORDERED_LINK_TYPES: # pragma: no cover logger().error( "Failed to get matched links: Queries with unordered links are not implemented. " f"link_type: {link_type}" @@ -598,7 +598,9 @@ def get_matched_type(self, link_type: str, **kwargs) -> MatchedTypesResultT: return kwargs.get("cursor"), self._filter_non_toplevel(templates_matched) return kwargs.get("cursor"), templates_matched - def get_atoms_by_field(self, query: list[OrderedDict[str, str]]) -> list[str]: + def get_atoms_by_field( + self, query: list[OrderedDict[str, str]] + ) -> list[str]: # pragma: no cover raise NotImplementedError() def get_atoms_by_index( @@ -607,7 +609,7 @@ def get_atoms_by_index( query: list[OrderedDict[str, str]], cursor: int = 0, chunk_size: int = 500, - ) -> tuple[int, list[AtomT]]: + ) -> tuple[int, list[AtomT]]: # pragma: no cover raise NotImplementedError() def get_atoms_by_text_field( @@ -615,10 +617,12 @@ def get_atoms_by_text_field( text_value: str, field: str | None = None, text_index_id: str | None = None, - ) -> list[str]: + ) -> list[str]: # pragma: no cover raise NotImplementedError() - def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> list[str]: + def get_node_by_name_starting_with( + self, node_type: str, startswith: str + ) -> list[str]: # pragma: no cover raise NotImplementedError() def _get_atom(self, handle: str) -> AtomT | None: @@ -672,7 +676,8 @@ def add_node(self, node_params: NodeParamsT) -> NodeT | None: def add_link(self, link_params: LinkParamsT, toplevel: bool = True) -> LinkT | None: result = self._build_link(link_params, toplevel) - if result is None: + # NOTE unreachable + if result is None: # pragma: no cover return None handle, link, _ = result self.db.link[handle] = link @@ -681,7 +686,7 @@ def add_link(self, link_params: LinkParamsT, toplevel: bool = True) -> LinkT | N def reindex( self, pattern_index_templates: dict[str, list[dict[str, Any]]] | None = None - ) -> None: + ) -> None: # pragma: no cover raise NotImplementedError() def delete_atom(self, handle: str, **kwargs) -> None: @@ -710,7 +715,7 @@ def create_field_index( named_type: str | None = None, composite_type: list[Any] | None = None, index_type: FieldIndexType | None = None, - ) -> str: + ) -> str: # pragma: no cover raise NotImplementedError() def bulk_insert(self, documents: list[AtomT]) -> None: @@ -732,5 +737,5 @@ def retrieve_all_atoms(self) -> list[AtomT]: logger().error(f"Error retrieving all atoms: {str(e)}") raise e - def commit(self, **kwargs) -> None: + def commit(self, **kwargs) -> None: # pragma: no cover raise NotImplementedError() diff --git a/hyperon_das_atomdb/adapters/redis_mongo_db.py b/hyperon_das_atomdb/adapters/redis_mongo_db.py index e1a75d42..a58099e6 100644 --- a/hyperon_das_atomdb/adapters/redis_mongo_db.py +++ b/hyperon_das_atomdb/adapters/redis_mongo_db.py @@ -89,60 +89,6 @@ class MongoIndexType(str, Enum): TEXT = "text" -class NodeDocuments: - """Class for managing node documents in MongoDB.""" - - def __init__(self, collection: Collection) -> None: - """ - Initialize the NodeDocuments class with a MongoDB collection. - - Args: - collection (Collection): The MongoDB collection to manage node documents. - """ - self.mongo_collection = collection - self.cached_nodes: dict[str, Any] = {} - self.count = 0 - - def add(self) -> None: - """Increment the count of node documents managed by this instance.""" - self.count += 1 - - def get(self, handle: str, default_value: Any = None) -> Any: - """ - Retrieve a node document from the MongoDB collection using the given handle. - - Args: - handle (str): The unique identifier for the node document. - default_value (Any): The value to return if the node document is not found. - Defaults to None. - - Returns: - The node document if found, otherwise the default value. - """ - mongo_filter = {FieldNames.ID_HASH: handle} - node = self.mongo_collection.find_one(mongo_filter) - return node if node else default_value - - def size(self) -> int: - """ - Return the count of node documents managed by this instance. - - Returns: - int: The count of node documents. - """ - return self.count - - def values(self) -> Iterable[dict[str, Any]]: - """ - Yield all node documents from the MongoDB collection. - - Returns: - generator: A generator yielding each document in the MongoDB collection. - """ - for document in self.mongo_collection.find(): - yield document - - class _HashableDocument: """Class for making documents hashable.""" @@ -763,6 +709,7 @@ def get_matched_links( link_type_hash = WILDCARD if link_type == WILDCARD else self._get_atom_type_hash(link_type) + # NOTE unreachable if link_type in UNORDERED_LINK_TYPES: target_handles = sorted(target_handles) @@ -861,6 +808,7 @@ def commit(self, **kwargs) -> None: {id_tag: document[id_tag]}, document, upsert=True ) self._update_atom_indexes([document]) + except Exception as e: logger().error(f"Failed to commit buffer - Details: {str(e)}") raise e diff --git a/hyperon_das_atomdb/database.py b/hyperon_das_atomdb/database.py index 42ee3187..d1269d40 100644 --- a/hyperon_das_atomdb/database.py +++ b/hyperon_das_atomdb/database.py @@ -257,14 +257,10 @@ def _build_link( raise ValueError("The target must be a dictionary") if "targets" not in target: atom = self.add_node(target) - if atom is None: - return None atom_hash = atom["composite_type_hash"] composite_type.append(atom_hash) else: atom = self.add_link(target, toplevel=False) - if atom is None: - return None atom_hash = atom["composite_type_hash"] composite_type.append(atom["composite_type"]) composite_type_hash.append(atom_hash) diff --git a/hyperon_das_atomdb/utils/expression_hasher.py b/hyperon_das_atomdb/utils/expression_hasher.py index e4720f40..c6ed3537 100644 --- a/hyperon_das_atomdb/utils/expression_hasher.py +++ b/hyperon_das_atomdb/utils/expression_hasher.py @@ -112,6 +112,7 @@ def composite_hash(hash_base: str | list[str]) -> str: return ExpressionHasher._compute_hash( ExpressionHasher.compound_separator.join(hash_base) ) + # TODO unreachable else: raise ValueError( "Invalid base to compute composite hash: " f"{type(hash_base)}: {hash_base}" diff --git a/tests/unit/adapters/test_ram_only_extra.py b/tests/unit/adapters/test_ram_only_extra.py new file mode 100644 index 00000000..eb766620 --- /dev/null +++ b/tests/unit/adapters/test_ram_only_extra.py @@ -0,0 +1,26 @@ +from hyperon_das_atomdb.adapters.ram_only import InMemoryDB +from tests.unit.fixtures import in_memory_db # noqa: F401 +from tests.unit.test_database import _check_handle + + +class TestRamOnlyExtra: + def test__build_atom_type_key_hash(self, in_memory_db): # noqa: F811 + db: InMemoryDB = in_memory_db + hash = db._build_atom_type_key_hash("A") + assert _check_handle(hash) + assert hash == "2c832bdcd9d74bf961205676d861540a" + + def test__delete_atom_type(self, in_memory_db): # noqa: F811 + db: InMemoryDB = in_memory_db + node = db.add_node({"name": "A", "type": "A"}) + assert len(db.all_named_types) == 1 + assert node["named_type"] in db.all_named_types + db._delete_atom_type("A") + assert len(db.all_named_types) == 0 + assert node["named_type"] not in db.all_named_types + + def test__update_atom_indexes(self, in_memory_db): # noqa: F811 + db: InMemoryDB = in_memory_db + node = db.add_node({"name": "A", "type": "A"}) + db._update_atom_indexes([node]) + assert len(db.all_named_types) == 1 diff --git a/tests/unit/adapters/test_redis_mongo_extra.py b/tests/unit/adapters/test_redis_mongo_extra.py new file mode 100644 index 00000000..d4edbb76 --- /dev/null +++ b/tests/unit/adapters/test_redis_mongo_extra.py @@ -0,0 +1,41 @@ +from unittest import mock + +import pytest + +from hyperon_das_atomdb.adapters.redis_mongo_db import MongoDBIndex, RedisMongoDB, _HashableDocument +from tests.unit.fixtures import redis_mongo_db # noqa: F401 + + +class TestRedisMongoExtra: + def test_hashable_document_str(self, redis_mongo_db): # noqa: F811 + db = redis_mongo_db + node = db._build_node({"type": "A", "name": "A"}) + hashable = _HashableDocument(node) + str_hashable = str(hashable) + assert isinstance(str_hashable, str) + assert hashable + assert str(node) == str_hashable + + @pytest.mark.parametrize( + "params", + [ + {"atom_type": "A", "fields": []}, + {"atom_type": "A", "fields": None}, + ], + ) + def test_index_create_exceptions(self, params, request): + db = request.getfixturevalue("redis_mongo_db") + mi = MongoDBIndex(db.mongo_db) + with pytest.raises(ValueError): + mi.create(**params) + + @mock.patch( + "hyperon_das_atomdb.adapters.redis_mongo_db.MongoClient", return_value=mock.MagicMock() + ) + @mock.patch("hyperon_das_atomdb.adapters.redis_mongo_db.Redis", return_value=mock.MagicMock()) + @mock.patch( + "hyperon_das_atomdb.adapters.redis_mongo_db.RedisCluster", return_value=mock.MagicMock() + ) + def test_create_db_connection_mongo(self, mock_mongo, mock_redis, mock_redis_cluster): + RedisMongoDB(mongo_tls_ca_file="/tmp/mock", redis_password="12", redis_username="A") + RedisMongoDB(redis_cluster=False) diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py new file mode 100644 index 00000000..6de72dbe --- /dev/null +++ b/tests/unit/fixtures.py @@ -0,0 +1,128 @@ +from unittest import mock + +import mongomock +import pytest + +from hyperon_das_atomdb.adapters.ram_only import InMemoryDB +from hyperon_das_atomdb.adapters.redis_mongo_db import MongoCollectionNames, RedisMongoDB + + +class MockRedis: + def __init__(self, cache=dict()): + self.cache = cache + + def get(self, key): + if key in self.cache: + return self.cache[key] + return None + + def set(self, key, value, *args, **kwargs): + if self.cache: + self.cache[key] = value + return "OK" + return None + + def hget(self, hash, key): + if hash in self.cache: + if key in self.cache[hash]: + return self.cache[hash][key] + return None + + def hset(self, hash, key, value, *args, **kwargs): + if self.cache: + self.cache[hash][key] = value + return 1 + return None + + def exists(self, key): + if key in self.cache: + return 1 + return 0 + + def cache_overwrite(self, cache=dict()): + self.cache = cache + + def sadd(self, key, *members): + if key not in self.cache: + self.cache[key] = set() + before_count = len(self.cache[key]) + self.cache[key].update(members) + after_count = len(self.cache[key]) + return after_count - before_count + + def smembers(self, key): + if key in self.cache: + return self.cache[key] + return set() + + def flushall(self): + self.cache.clear() + + def delete(self, *keys): + deleted_count = 0 + for key in keys: + if key in self.cache: + del self.cache[key] + deleted_count += 1 + return deleted_count + + def getdel(self, key): + value = self.cache.get(key) + if key in self.cache: + del self.cache[key] + return value + + def srem(self, key, *members): + if key not in self.cache: + return 0 + initial_count = len(self.cache[key]) + self.cache[key].difference_update(members) + removed_count = initial_count - len(self.cache[key]) + return removed_count + + def sscan(self, name, cursor=0, match=None, count=None): + key = name + if key not in self.cache: + return (0, []) + + elements = list(self.cache[key]) + if match: + elements = [e for e in elements if match in e] + start = cursor + end = min(start + (count if count else len(elements)), len(elements)) + new_cursor = end if end < len(elements) else 0 + + return (new_cursor, elements[start:end]) + + +@pytest.fixture +def redis_mongo_db(): + mongo_db = mongomock.MongoClient().db + redis_db = MockRedis() + with mock.patch( + "hyperon_das_atomdb.adapters.redis_mongo_db.RedisMongoDB._connection_mongo_db", + return_value=mongo_db, + ), mock.patch( + "hyperon_das_atomdb.adapters.redis_mongo_db.RedisMongoDB._connection_redis", + return_value=redis_db, + ): + db = RedisMongoDB() + db.mongo_atoms_collection = mongo_db.collection + db.mongo_types_collection = mongo_db.collection + + db.all_mongo_collections = [ + (MongoCollectionNames.ATOMS, db.mongo_atoms_collection), + (MongoCollectionNames.ATOM_TYPES, db.mongo_types_collection), + ] + db.mongo_bulk_insertion_buffer = { + MongoCollectionNames.ATOMS: tuple([db.mongo_atoms_collection, set()]), + MongoCollectionNames.ATOM_TYPES: tuple([db.mongo_types_collection, set()]), + } + + yield db + + +@pytest.fixture +def in_memory_db(): + db = InMemoryDB() + yield db diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py new file mode 100644 index 00000000..1fa66c16 --- /dev/null +++ b/tests/unit/test_database.py @@ -0,0 +1,1174 @@ +from unittest import mock +import pytest +from hyperon_das_atomdb.database import AtomDB +from .fixtures import in_memory_db, redis_mongo_db # noqa: F401 + + +def _check_handle(handle): + return all((isinstance(handle, str), len(handle) == 32, int(handle, 16))) + + +class TestDatabase: + @staticmethod + def _add_node(db: AtomDB, node_name, node_type, adapter, extra_fields=None): + node_dict = {"name": node_name, "type": node_type} + node_dict.update(extra_fields or {}) + node = db.add_node(node_dict) + if adapter == "redis_mongo_db": + db.commit() + return node + + @staticmethod + def _add_link(db: AtomDB, link_type, dict_targets, adapter, is_top_level=True): + link = db.add_link({"type": link_type, "targets": dict_targets}, toplevel=is_top_level) + if adapter != "in_memory_db": + db.commit() + return link + + @pytest.mark.parametrize( + "database,expected", + [ + ("redis_mongo_db", "1fd600f0fd8a1fab79546a4fc3612df3"), # Concept, Human + ("in_memory_db", "1fd600f0fd8a1fab79546a4fc3612df3"), # Concept, Human + ], + ) + def test_node_handle(self, database, expected, request): + db: AtomDB = request.getfixturevalue(database) + handle = db.node_handle("Concept", "Human") + incorrect_handle = db.node_handle("Concept", "human") + assert handle, incorrect_handle + assert handle != incorrect_handle + assert handle == expected + assert _check_handle(handle) + + @pytest.mark.parametrize( + "database,expected", + [ + ("redis_mongo_db", "1fd600f0fd8a1fab79546a4fc3612df3"), # Concept, Human + ("in_memory_db", "1fd600f0fd8a1fab79546a4fc3612df3"), # Concept, Human + ], + ) + def test_node_handle_exceptions(self, database, expected, request): + db: AtomDB = request.getfixturevalue(database) + # NOTE Should raise ValueError + with pytest.raises(TypeError): + db.node_handle([], []) + + @pytest.mark.parametrize( + "database,expected", + [ + ("redis_mongo_db", "a9dea78180588431ec64d6bc4872fdbc"), # Similarity + ("in_memory_db", "a9dea78180588431ec64d6bc4872fdbc"), # Similarity + ], + ) + def test_link_handle(self, database, expected, request): + db: AtomDB = request.getfixturevalue(database) + handle = db.link_handle("Similarity", []) + assert len(set([db.link_handle("Similarity", f) for f in [[], [], ""]])) == 1 + assert handle + assert _check_handle(handle) + + @pytest.mark.parametrize( + "database,expected", + [ + ("redis_mongo_db", "a9dea78180588431ec64d6bc4872fdbc"), # Similarity + ("in_memory_db", "a9dea78180588431ec64d6bc4872fdbc"), # Similarity + ], + ) + def test_link_handle_exceptions(self, database, expected, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(TypeError): + db.link_handle("Similarity", None) + # NOTE Unreachable + # TODO: unreachable code must be deleted or fixed to become reachable + # with pytest.raises(ValueError): + # db.link_handle("Similarity", set()) + + @pytest.mark.parametrize( + "database,kwlist", + [ + ("redis_mongo_db", ["targets_document", "deep_representation"]), + ("in_memory_db", ["targets_document", "deep_representation"]), + ], + ) + def test_reformat_document(self, database, kwlist, request): + db: AtomDB = request.getfixturevalue(database) + node_handle = db.add_node({"name": "A", "type": "Test"}).get("handle") + if database != "in_memory_db": + db.commit() + link = {"name": "A", "targets": [node_handle]} + for kw in kwlist: + answer = db._reformat_document(link, **{kw: True}) + assert set(answer.keys()) == {"name", "targets", "targets_document"} + assert len(answer["targets"]) == 1 + assert len(answer["targets_document"]) == 1 + assert answer["name"] == "A" + assert isinstance(answer["targets"][0], (str if kw == "targets_document" else dict)) + + @pytest.mark.parametrize( + "database,kwlist", + [ + ("redis_mongo_db", ["targets_document", "deep_representation"]), + ("in_memory_db", ["targets_document", "deep_representation"]), + ], + ) + def test_reformat_document_exceptions(self, database, kwlist, request): + db: AtomDB = request.getfixturevalue(database) + link = {"name": "A", "targets": ["test"]} + for kw in kwlist: + with pytest.raises(Exception, match="Nonexistent atom"): + db._reformat_document(link, **{kw: True}) + + @pytest.mark.parametrize( + "database,expected_fields, expected_handle", + [ + ( + "redis_mongo_db", + ["handle", "_id", "composite_type_hash", "name", "named_type"], + "180fed764dbd593f1ea45b63b13d7e69", + ), + ( + "in_memory_db", + ["handle", "_id", "composite_type_hash", "name", "named_type"], + "180fed764dbd593f1ea45b63b13d7e69", + ), + ], + ) + def test_build_node(self, database, expected_fields, expected_handle, request): + db: AtomDB = request.getfixturevalue(database) + handle, node = db._build_node({"type": "Test", "name": "test"}) + assert node + assert handle == expected_handle + assert all([k in node for k in expected_fields]) + assert isinstance(node, dict) + assert _check_handle(handle) + + # Test exception + with pytest.raises(Exception, match="The \"name\" and \"type\" fields must be sent"): + db._build_node({}) + + @pytest.mark.parametrize( + "database,expected_fields, expected_handle,is_top_level", + [ + ( + "redis_mongo_db", + [ + "handle", + "_id", + "composite_type_hash", + "named_type_hash", + "named_type", + "is_toplevel", + "targets", + ], + "180fed764dbd593f1ea45b63b13d7e69", + True, + ), + ( + "redis_mongo_db", + [ + "handle", + "_id", + "composite_type_hash", + "named_type_hash", + "named_type", + "is_toplevel", + "targets", + ], + "180fed764dbd593f1ea45b63b13d7e69", + False, + ), + ( + "in_memory_db", + [ + "handle", + "_id", + "composite_type_hash", + "named_type_hash", + "named_type", + "is_toplevel", + "targets", + ], + "180fed764dbd593f1ea45b63b13d7e69", + True, + ), + ( + "in_memory_db", + [ + "handle", + "_id", + "composite_type_hash", + "named_type_hash", + "named_type", + "is_toplevel", + "targets", + ], + "180fed764dbd593f1ea45b63b13d7e69", + False, + ), + ], + ) + def test_build_link(self, database, expected_fields, expected_handle, is_top_level, request): + db: AtomDB = request.getfixturevalue(database) + handle, link, targets = db._build_link( + {"type": "Test", "targets": [{"type": "Test", "name": "test"}]}, is_top_level + ) + assert expected_handle in targets + assert all([k in link for k in expected_fields]) + assert link["is_toplevel"] == is_top_level + assert _check_handle(handle) + assert isinstance(link, dict) + assert isinstance(targets, list) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_build_link_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(ValueError, match="The target must be a dictionary"): + db._build_link({"type": "Test", "targets": [""]}) + with pytest.raises(Exception, match="The \"type\" and \"targets\" fields must be sent"): + db._build_link({"type": "Test", "targets": None}) + with pytest.raises(Exception, match="The \"type\" and \"targets\" fields must be sent"): + db._build_link({"type": None, "targets": []}) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_node_exists(self, database, request): + db: AtomDB = request.getfixturevalue(database) + db.add_node({"name": "A", "type": "Test"}) + if database != "in_memory_db": + db.commit() + no_exists = db.node_exists("Test", "B") + exists = db.node_exists("Test", "A") + assert isinstance(no_exists, bool) + assert isinstance(exists, bool) + assert not no_exists + assert exists + + @pytest.mark.parametrize( + "database,targets", + [ + ("redis_mongo_db", ["180fed764dbd593f1ea45b63b13d7e69"]), + ("redis_mongo_db", []), + ("in_memory_db", ["180fed764dbd593f1ea45b63b13d7e69"]), + ("in_memory_db", []), + ], + ) + def test_link_exists(self, database, targets, request): + db: AtomDB = request.getfixturevalue(database) + dict_targets = [{"type": "Test", "name": "test"}] if targets else [] + link = {"type": "Test", "targets": dict_targets} + db.add_link(link) + if database != "in_memory_db": + db.commit() + no_exists = db.link_exists("Tes", []) + exists = db.link_exists("Test", targets) + + assert isinstance(no_exists, bool) + assert isinstance(exists, bool) + assert not no_exists + assert exists + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_handle(self, database, request): + db: AtomDB = request.getfixturevalue(database) + expected_node = self._add_node(db, "A", "Test", database) + node = db.get_node_handle("Test", "A") + assert node == expected_node["handle"] + assert _check_handle(node) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_handle_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_node_handle("Test", "A") + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_name(self, database, request): + db: AtomDB = request.getfixturevalue(database) + expected_node = self._add_node(db, "A", "Test", database) + name = db.get_node_name(expected_node["handle"]) + # NOTE all adapters must return the same type + assert isinstance(name, str) + assert name == expected_node["name"] + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_name_exceptions(self, database, request): + if database == "redis_mongo_db": + # TODO: fix this + pytest.skip( + "ERROR in_memory returns a AtomDoesNotExist exception, redis_mongo returns ValueError. " + "See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + # in memory returns a AtomDoesNotExist exception, redis_mongo returns ValueError + # TODO: should this be fixed/synced? I mean, make both raise the same exception? + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_node_name("error") + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_type(self, database, request): + db: AtomDB = request.getfixturevalue(database) + expected_node = self._add_node(db, "A", "Test", database) + node_type = db.get_node_type(expected_node["handle"]) + assert isinstance(node_type, str) + assert node_type == expected_node["named_type"] + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_type_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_node_type("test") + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_by_name(self, database, request): + db: AtomDB = request.getfixturevalue(database) + expected_nodes = [self._add_node(db, n, "Test", database) for n in {"A", "Aa", "Ac"}] + not_expected_nodes = [self._add_node(db, n, "Test", database) for n in {"B", "Ba", "Bc"}] + nodes = db.get_node_by_name("Test", "A") + not_nodes = db.get_node_by_name("Test", "C") + assert not_nodes == [] + assert isinstance(nodes, list) + assert len(nodes) == 3 + assert all(_check_handle(node) for node in nodes) + assert all(n["handle"] in nodes for n in expected_nodes) + assert not any(n["handle"] in nodes for n in not_expected_nodes) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atoms_by_field(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + expected_node = self._add_node(db, "Ac", "Test", database) + expected_link = self._add_link(db, "Ac", [expected_node], database) + nodes = db.get_atoms_by_field([{"field": "name", "value": "Ac"}]) + links = db.get_atoms_by_field([{"field": "named_type", "value": "Ac"}]) + assert isinstance(nodes, list) + assert isinstance(links, list) + assert all(_check_handle(node) for node in nodes) + assert all(_check_handle(link) for link in links) + assert nodes[0] == expected_node["handle"] + assert links[0] == expected_link["handle"] + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atoms_by_index(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "A", "Test", database, {"value": 3}) + index_id = db.create_field_index(atom_type="node", fields=["value"], named_type="Test") + cursor, atoms = db.get_atoms_by_index(index_id, [{"field": "value", "value": 3}]) + assert isinstance(cursor, int) + assert isinstance(atoms, list) + assert cursor == 0 + assert len(atoms) == 1 + assert all(isinstance(a, dict) for a in atoms) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atoms_by_index_exceptions(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception): + db.get_atoms_by_index("", []) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atoms_by_text_field_regex(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "A", "Test", database, {"value": "Test sentence"}) + self._add_link(db, "Test", [], database) + index_id = db.create_field_index( + atom_type="node", fields=["value"], named_type="Test", index_type="token_inverted_list" + ) + atoms = db.get_atoms_by_text_field("Test", "value", text_index_id=index_id) + assert isinstance(atoms, list) + assert all(_check_handle(a) for a in atoms) + assert all(isinstance(a, str) for a in atoms) + assert len(atoms) == 1 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atoms_by_text_field_text(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "A", "Test", database, {"value": "Test sentence"}) + index_id = db.create_field_index( + atom_type="node", fields=["value"], named_type="Test", index_type="token_inverted_list" + ) + with mock.patch( + "mongomock.filtering._Filterer.apply", return_value=["815212e3d7ac246e70c1744d14a8c402"] + ): + atoms = db.get_atoms_by_text_field("Test", text_index_id=index_id) + assert isinstance(atoms, list) + assert all(_check_handle(a) for a in atoms) + assert all(isinstance(a, str) for a in atoms) + assert len(atoms) == 1 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_node_by_name_starting_with(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + node_b = self._add_node(db, "Abb", "Test", database) + self._add_node(db, "Bbb", "Test", database) + nodes = db.get_node_by_name_starting_with("Test", "A") + assert isinstance(nodes, list) + assert all(_check_handle(n) for n in nodes) + assert all(isinstance(n, str) for n in nodes) + assert all(handle in nodes for handle in [node_a["handle"], node_b["handle"]]) + assert len(nodes) == 2 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_all_nodes(self, database, request): + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "Aaa", "Test", database) + self._add_node(db, "Abb", "Test", database) + self._add_node(db, "Bbb", "Test", database) + nodes = db.get_all_nodes("Test") + assert isinstance(nodes, list) + assert all(_check_handle(n) for n in nodes) + assert all(isinstance(n, str) for n in nodes) + assert len(nodes) == 3 + + @pytest.mark.parametrize( + "database,params,links_len,cursor_value", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {"link_type": "Ac"}, 3, 0), + ("redis_mongo_db", {"link_type": "Ac", "names": True, "chunk_size": 1}, 3, 0), + ("redis_mongo_db", {"link_type": "Ac", "names": True, "cursor": 0}, 3, 0), + ("redis_mongo_db", {"link_type": "Z", "names": True, "cursor": 1}, 0, 0), + ("redis_mongo_db", {"link_type": "Ac", "chunk_size": 1, "cursor": 0}, 1, 1), + ("in_memory_db", {"link_type": "Ac"}, 3, None), + # NOTE should return the same value for the cursor + ("in_memory_db", {"link_type": "Ac", "names": True, "cursor": 0}, 3, 0), + ("in_memory_db", {"link_type": "Ac", "names": True, "cursor": 0}, 3, 0), + ("in_memory_db", {"link_type": "Z", "names": True, "cursor": 1}, 0, 1), + ("in_memory_db", {"link_type": "Ac", "chunk_size": 1, "cursor": 0}, 3, 0), + ], + ) + def test_get_all_links(self, database, params, links_len, cursor_value, request): + db: AtomDB = request.getfixturevalue(database) + self._add_link(db, "Ac", [{"name": "A", "type": "A"}], database) + self._add_link(db, "Ac", [{"name": "B", "type": "B"}], database) + self._add_link(db, "Ac", [{"name": "C", "type": "C"}], database) + cursor, links = db.get_all_links(**params) + assert cursor == cursor_value + assert isinstance(links, list) + assert all(_check_handle(link) for link in links) + assert all(isinstance(link, str) for link in links) + assert len(links) == links_len + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_link_handle(self, database, request): + db: AtomDB = request.getfixturevalue(database) + link = self._add_link(db, "Ac", [{"name": "A", "type": "A"}], database) + handle = db.get_link_handle(link["type"], link["targets"]) + assert _check_handle(handle) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_link_handle_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_link_handle("A", []) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_link_type(self, database, request): + db: AtomDB = request.getfixturevalue(database) + link_a = self._add_link(db, "Ac", [{"name": "A", "type": "A"}], database) + self._add_link(db, "Bc", [{"name": "A", "type": "A"}], database) + link_type = db.get_link_type(link_a["handle"]) + assert link_type + assert isinstance(link_type, str) + assert link_type == link_a["type"] + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_link_type_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_link_type("") + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_link_targets(self, database, request): + db: AtomDB = request.getfixturevalue(database) + link_a = self._add_link(db, "Ac", [{"name": "A", "type": "A"}], database) + targets = db.get_link_targets(link_a["handle"]) + assert isinstance(targets, list) + assert len(targets) == 1 + assert all(_check_handle(t) for t in targets) + assert all(isinstance(t, str) for t in targets) + assert targets == link_a["targets"] + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_is_ordered(self, database, request): + db: AtomDB = request.getfixturevalue(database) + link_a = self._add_link(db, "Ac", [{"name": "A", "type": "A"}], database) + # NOTE just retrieves the link ... + assert db.is_ordered(link_a["handle"]) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_is_ordered_no_handle(self, database, request): + if database == "redis_mongo_db": + # TODO: fix this + pytest.skip( + "ERROR redis_mongo_db is raising ValueError exception, should be AtomDoesNotExist. " + "See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.is_ordered("handle") + + @pytest.mark.parametrize( + "database,params,links_len,cursor_value", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}, 3, None), + ("redis_mongo_db", {"handles_only": True}, 3, None), + ("redis_mongo_db", {"no_target_format": True}, 3, None), + ("redis_mongo_db", {"cursor": 0, "chunk_size": 1}, 1, 1), + # NOTE should return None on all cases + ("in_memory_db", {}, 3, None), + ("in_memory_db", {"handles_only": True}, 3, None), + ("in_memory_db", {"no_target_format": True}, 3, None), + ("in_memory_db", {"cursor": 0, "chunk_size": 1}, 3, 0), + ], + ) + def test_get_incoming_links(self, database, params, links_len, cursor_value, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + self._add_link(db, "Aa", [node_a], database) + self._add_link(db, "Ab", [node_a], database) + self._add_link(db, "Ac", [node_a], database) + cursor, links = db.get_incoming_links(node_a["handle"], **params) + assert cursor == cursor_value + assert len(links) == links_len + assert all( + [ + _check_handle(link if params.get("handles_only") else link["handle"]) + for link in links + ] + ) + + @pytest.mark.parametrize( + "database,params,links_len,cursor_value", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}, 1, None), + ("redis_mongo_db", {"toplevel_only": True}, 1, None), + # ("redis_mongo_db", {"link_type": "NoTopLevel" , "toplevel_only": True}, 0, None), # doesn"t work + # Note returning different values + # ("in_memory_db", {"link_type": "*", "toplevel_only": True}, 3, None), + # ("redis_mongo_db", {"link_type": "*", "toplevel_only": True}, 0, None), + # ("redis_mongo_db", {"link_type": "*"}, 3, None), # should return 3 + ("redis_mongo_db", {"target_handles": "*"}, 1, None), + ("redis_mongo_db", {"handles_only": True}, 1, None), + ("redis_mongo_db", {"no_target_format": True}, 1, None), + ("redis_mongo_db", {"cursor": 0, "chunk_size": 1}, 1, None), + ("in_memory_db", {}, 1, None), + ("in_memory_db", {"toplevel_only": True}, 1, None), + # ("in_memory_db", {"link_type": "NoTopLevel", "toplevel_only": True}, 0, None), # doesn"t work + ("in_memory_db", {"link_type": "*"}, 3, None), + ("in_memory_db", {"toplevel_only": True}, 1, None), + ("in_memory_db", {"target_handles": "*"}, 1, None), + ("in_memory_db", {"handles_only": True}, 1, None), + ("in_memory_db", {"no_target_format": True}, 1, None), + ("in_memory_db", {"cursor": 0, "chunk_size": 1}, 1, 0), + ], + ) + def test_get_matched_links(self, database, params, links_len, cursor_value, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Aa", [node_a], database) + _ = self._add_link(db, "NoTopLevel", [node_a], database, is_top_level=False) + _ = self._add_link(db, "Ac", [node_a], database) + params["link_type"] = link_a["type"] if not params.get("link_type") else params["link_type"] + params["target_handles"] = ( + link_a["targets"] if not params.get("target_handles") else params["target_handles"] + ) + cursor, links = db.get_matched_links(**params) + assert cursor == cursor_value + assert len(links) == links_len + if all(isinstance(link, tuple) for link in links): + for link in links: + while link: + assert _check_handle(link[0]) + link = link[1] if len(link) > 1 else None + else: + assert all([_check_handle(link) for link in links]) + + @pytest.mark.parametrize( + "database,params,links_len,cursor_value", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {"link_type": "Z", "target_handles": []}, 0, None), + # ("redis_mongo_db", {"link_type": "*", "target_handles": ["*", "*"], "toplevel_only": True}, 0, None), + # ("in_memory_db", {"link_type": "*", "target_handles": ["*", "*"]}, 0, None), + # ("in_memory_db", {"link_type": "*", "target_handles": ["*", "*"], "toplevel_only": True}, 0, None), + ], + ) + def test_get_matched_no_links(self, database, params, links_len, cursor_value, request): + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "Aaa", "Test", database) + cursor, links = db.get_matched_links(**params) + assert cursor == cursor_value + assert len(links) == links_len + + @pytest.mark.parametrize( + "database,params,links_len,cursor_value,is_top_level", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}, 1, None, True), + ("redis_mongo_db", {}, 1, None, False), + ("redis_mongo_db", {"toplevel_only": True}, 0, None, False), + ("redis_mongo_db", {"cursor": 0, "chunk_size": 1}, 1, 0, False), + ("in_memory_db", {}, 1, None, True), + ("in_memory_db", {}, 1, None, False), + ("in_memory_db", {"toplevel_only": True}, 0, None, False), + # NOTE should return None or same as redis_mongo + ("in_memory_db", {"cursor": 0, "chunk_size": 1}, 1, 0, False), + ], + ) + def test_get_matched_type_template( + self, database, params, links_len, cursor_value, is_top_level, request + ): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + node_b = self._add_node(db, "Bbb", "Test", database) + link_a = self._add_link(db, "Aa", [node_a, node_b], database, is_top_level=is_top_level) + cursor, links = db.get_matched_type_template(["Aa", "Test", "Test"], **params) + assert cursor == cursor_value + assert len(links) == links_len + if len(links) > 0: + for link in links: + assert _check_handle(link[0]) + assert link[0] == link_a["handle"] + assert all(t in link[1] for t in link_a["targets"]) + assert all(_check_handle(t) for t in link[1]) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_matched_type(self, database, request): + if database == "redis_mongo_db": + # TODO: fix this + pytest.skip( + "ERROR redis_mongo_db is returning more values. " + "See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + link_a = self._add_link(db, "Aa", [], database) + self._add_link(db, "Ab", [], database) + cursor, links = db.get_matched_type(link_a["type"]) + assert cursor is None + assert len(links) == 1 + if len(links) > 0: + for link in links: + assert _check_handle(link[0]) + assert link[0] == link_a["handle"] + assert all(t in link[1] for t in link_a["targets"]) + assert all(_check_handle(t) for t in link[1]) + + @pytest.mark.parametrize( + "database,params,top_level,n_links,n_nodes", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}, True, 1, 1), + ("redis_mongo_db", {"no_target_format": True}, False, 1, 1), + # ("redis_mongo_db", {"targets_document": True}, False, 1, 1),# breaks when is a node + ("redis_mongo_db", {"deep_representation": True}, False, 1, 1), + ("in_memory_db", {}, True, 1, 1), + ("in_memory_db", {"no_target_format": True}, False, 0, 1), + # ("in_memory_db", {"targets_document": True}, False, 0, 1),# breaks when is a node + ("in_memory_db", {"deep_representation": True}, False, 0, 1), + ], + ) + def test_get_atom_node(self, database, params, top_level, n_links, n_nodes, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + atom_n = db.get_atom(node_a["handle"], **params) + assert atom_n + assert atom_n["handle"] == node_a["handle"] + assert _check_handle(atom_n["handle"]) + + @pytest.mark.parametrize( + "database,params,top_level,n_links,n_nodes", + [ + ("redis_mongo_db", {}, True, 1, 1), + ("redis_mongo_db", {"no_target_format": True}, False, 1, 1), + ("redis_mongo_db", {"targets_document": True}, False, 1, 1), + ("redis_mongo_db", {"deep_representation": True}, False, 1, 1), + ("in_memory_db", {}, True, 1, 1), + ("in_memory_db", {"no_target_format": True}, False, 0, 1), + ("in_memory_db", {"targets_document": True}, False, 0, 1), + ("in_memory_db", {"deep_representation": True}, False, 0, 1), + ], + ) + def test_get_atom_link(self, database, params, top_level, n_links, n_nodes, request): + db: AtomDB = request.getfixturevalue(database) + link_a = self._add_link(db, "Aa", [], database, is_top_level=top_level) + atom_l = db.get_atom(link_a["handle"], **params) + assert atom_l + assert atom_l["handle"] == link_a["handle"] + assert _check_handle(atom_l["handle"]) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test__get_atom(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Aa", [], database) + node = db._get_atom(node_a["handle"]) + link = db._get_atom(link_a["handle"]) + assert node, link + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test__get_atom_none(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node = db._get_atom("handle") + link = db._get_atom("handle") + assert node is None + assert link is None + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atom_type(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [], database) + atom_type_node = db.get_atom_type(node_a["handle"]) + atom_type_link = db.get_atom_type(link_a["handle"]) + assert isinstance(atom_type_node, str) + assert isinstance(atom_type_link, str) + assert atom_type_node == atom_type_link + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atom_type_none(self, database, request): + db: AtomDB = request.getfixturevalue(database) + atom_type_node = db.get_atom_type("handle") + atom_type_link = db.get_atom_type("handle") + assert atom_type_node is None + assert atom_type_link is None + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atom_as_dict(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [], database) + atom_node = db.get_atom_as_dict(node_a["handle"]) + atom_link = db.get_atom_as_dict(link_a["handle"]) + assert isinstance(atom_node, dict) + assert isinstance(atom_link, dict) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atom_as_dict_none(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR in_memory raises exception, they should return the same result/exception. " + "See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + atom_node = db.get_atom_as_dict("handle") + atom_link = db.get_atom_as_dict("handle") + assert atom_node == {} + assert atom_link == {} + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_get_atom_as_dict_exceptions(self, database, request): + if database == "redis_mongo_db": + # TODO: fix this + pytest.skip( + "ERROR redis_mongo_db doesn't raises exception, they should return the same result/exception. " + "See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_atom_as_dict("handle") + with pytest.raises(Exception, match="Nonexistent atom"): + db.get_atom_as_dict("handle") + + @pytest.mark.parametrize( + "database,params", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}), + ("redis_mongo_db", {"precise": True}), + ("redis_mongo_db", {"precise": False}), + # NOTE should return the same value if the arg precise is set + ("in_memory_db", {}), + ("in_memory_db", {"precise": True}), + ("in_memory_db", {"precise": False}), + ], + ) + def test_count_atoms(self, database, params, request): + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "Aaa", "Test", database) + self._add_link(db, "Test", [], database) + atoms_count = db.count_atoms(params) + assert atoms_count + assert isinstance(atoms_count, dict) + assert isinstance(atoms_count["atom_count"], int) + assert atoms_count["atom_count"] == 2 + if params.get("precise", False): + assert isinstance(atoms_count["node_count"], int) + assert isinstance(atoms_count["link_count"], int) + assert atoms_count["node_count"] == 1 + assert atoms_count["link_count"] == 1 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_clear_database(self, database, request): + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "Aaa", "Test", database) + self._add_link(db, "Test", [], database) + assert db.count_atoms()["atom_count"] == 2 + db.clear_database() + assert db.count_atoms()["atom_count"] == 0 + + @pytest.mark.parametrize( + "database,node", + [ + ("redis_mongo_db", {"name": "A", "type": "A"}), + ("in_memory_db", {"name": "A", "type": "A"}), + ], + ) + def test_add_node(self, database, node, request): + db: AtomDB = request.getfixturevalue(database) + if database == "redis_mongo_db": + db.mongo_bulk_insertion_limit = 1 + node = db.add_node(node) + count = db.count_atoms() + assert node + assert count["atom_count"] == 1 + assert isinstance(node, dict) + + @pytest.mark.parametrize( + "database,node", + [ + ("redis_mongo_db", {"name": "AAAA", "type": "A"}), + ], + ) + def test_add_node_discard(self, database, node, request): + db: AtomDB = request.getfixturevalue(database) + db.mongo_bulk_insertion_limit = 1 + db.max_mongo_db_document_size = 1 + node = db.add_node(node) + count = db.count_atoms() + assert node is None + assert count["atom_count"] == 0 + + @pytest.mark.parametrize( + "database,node", + [ # TODO: differences here must be fixed if possible + ("redis_mongo_db", {}), + # NOTE it"s not breaking, should break? + # ("redis_mongo_db", {"name": "A", "type": "A", "handle": ""}), + # ("redis_mongo_db", {"name": "A", "type": "A", "_id": ""}), + # ("redis_mongo_db", {"name": "A", "type": "A", "composite_type_hash": ""}), + # ("redis_mongo_db", {"name": "A", "type": "A", "named_type": ""}), + ("in_memory_db", {}), + # NOTE it"s not breaking, should break? + # ("in_memory_db", {"name": "A", "type": "A", "handle": ""}), + # ("in_memory_db", {"name": "A", "type": "A", "_id": ""}), + # ("in_memory_db", {"name": "A", "type": "A", "composite_type_hash": ""}), + # ("in_memory_db", {"name": "A", "type": "A", "named_type": ""}), + ], + ) + def test_add_node_exceptions(self, database, node, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception): + db.add_node(node) + + @pytest.mark.parametrize( + "database,params,expected_count,top_level", + [ + ("redis_mongo_db", {"type": "A", "targets": [{"name": "A", "type": "A"}]}, 2, True), + ("redis_mongo_db", {"type": "A", "targets": []}, 1, True), + ("in_memory_db", {"type": "A", "targets": [{"name": "A", "type": "A"}]}, 2, True), + ("in_memory_db", {"type": "A", "targets": []}, 1, True), + ], + ) + def test_add_link(self, database, params, expected_count, top_level, request): + db: AtomDB = request.getfixturevalue(database) + if database == "redis_mongo_db": + db.mongo_bulk_insertion_limit = 1 + link = db.add_link(params, top_level) + count = db.count_atoms() + assert link + assert count["atom_count"] == expected_count + assert isinstance(link, dict) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_reindex(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + self._add_node(db, "Aaa", "Test", database) + self._add_link(db, "Test", [], database) + db.reindex() + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_delete_atom(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Atom not in incoming_set. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [], database) + count = db.count_atoms({"precise": True}) + assert count["atom_count"] == 2 + assert count["node_count"] == 1 + assert count["link_count"] == 1 + db.delete_atom(node_a["handle"]) + count = db.count_atoms({"precise": True}) + assert count["atom_count"] == 1 + assert count["node_count"] == 0 + assert count["link_count"] == 1 + db.delete_atom(link_a["handle"]) + count = db.count_atoms({"precise": True}) + assert count["atom_count"] == 0 + assert count["node_count"] == 0 + assert count["link_count"] == 0 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_delete_atom_exceptions(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Atom not in incoming_set. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception): + db.delete_atom("handle") + + @pytest.mark.parametrize( + "database,params", + [ + ("redis_mongo_db", {"atom_type": "A", "fields": ["value"]}), + ("redis_mongo_db", {"atom_type": "A", "fields": ["value"], "named_type": "A"}), + ( + "redis_mongo_db", + {"atom_type": "A", "fields": ["value"], "composite_type": ["value", "side"]}, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "named_type": "A", + "index_type": "binary_tree", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value", "side"], + "composite_type": ["value", "side"], + "index_type": "binary_tree", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value", "side"], + "composite_type": ["value", "side"], + "index_type": "token_inverted_list", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "named_type": "A", + "index_type": "token_inverted_list", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "named_type": "A", + "index_type": "binary_tree", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "composite_type": ["value", "side"], + "index_type": "token_inverted_list", + }, + ), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "composite_type": ["value", "side"], + "index_type": "binary_tree", + }, + ), + ("in_memory_db", {}), + ], + ) + def test_create_field_index(self, database, params, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented on in_memory_db. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + index_id = db.create_field_index(**params) + assert index_id + assert isinstance(index_id, str) + # check if creating a duplicated index breaks + index_id2 = db.create_field_index(**params) + assert isinstance(index_id2, str) + assert index_id2 == index_id + + @pytest.mark.parametrize( + "database,params", + [ + ("redis_mongo_db", {"atom_type": "A", "fields": []}), + ( + "redis_mongo_db", + { + "atom_type": "A", + "fields": ["value"], + "named_type": "A", + "composite_type": ["value", "side"], + }, + ), + ("in_memory_db", {}), + ], + ) + def test_create_field_index_value_error(self, database, params, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented on in_memory_db. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(ValueError): + db.create_field_index(**params) + + # TODO: fix this or delete + # @pytest.mark.parametrize("database,params", [ + # ("redis_mongo_db", {"atom_type": "A", "fields": ["side"], "index_type": "wrong_type"}), + # ("in_memory_db", {}) + # ]) + # def test_create_field_index_mongo_error(self, database, params, request): + # if database == "in_memory_db": + # # TODO: fix this + # pytest.skip("ERROR Not implemented on in_memory_db. See https://github.com/singnet/das-atom-db/issues/210") + # + # db: AtomDB = request.getfixturevalue(database) + # # with pytest.raises(ValueError): + # db.create_field_index(**params) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_bulk_insert(self, database, request): + if database == "redis_mongo_db": + # TODO: fix this + pytest.skip( + "ERROR redis_mongo_db is not updating targets. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [{"name": "A", "type": "A"}], database) + node_a["name"] = "B" + link_a["targets"] = [node_a["handle"]] + db.bulk_insert([node_a, link_a]) + count = db.count_atoms({"precise": True}) + node = db.get_atom(node_a["handle"]) + link = db.get_atom(link_a["handle"]) + assert count["atom_count"] == 3 + assert count["node_count"] == 2 + assert count["link_count"] == 1 + assert node["name"] == "B" + assert link["targets"] == [node_a["handle"]] + + # Note no exception is raised if error + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_bulk_insert_exceptions(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node_a = db._build_node({"name": "A", "type": "A"}) + link_a = db._build_link({"targets": [], "type": "A"}) + with pytest.raises(Exception): + db.bulk_insert([node_a, link_a]) + # TODO: fix this + pytest.skip( + "ERROR should raise an exception. See https://github.com/singnet/das-atom-db/issues/210" + ) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_retrieve_all_atoms(self, database, request): + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [{"name": "A", "type": "A"}], database) + node_b = db.get_atom(db.get_node_handle(node_type="A", node_name="A")) + atoms = db.retrieve_all_atoms() + assert isinstance(atoms, list) + assert len(atoms) == 3 + assert all(a in atoms for a in [node_a, link_a, node_b]) + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_commit(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented on in_memory_db. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + db.add_node({"name": "A", "type": "Test"}) + db.add_link({"type": "Test", "targets": []}) + db.commit() + count = db.count_atoms({"precise": True}) + assert count["atom_count"] == 2 + assert count["node_count"] == 1 + assert count["link_count"] == 1 + + @pytest.mark.parametrize("database", ["redis_mongo_db", "in_memory_db"]) + def test_commit_buffer(self, database, request): + if database == "in_memory_db": + # TODO: fix this + pytest.skip( + "ERROR Not implemented on in_memory_db. See https://github.com/singnet/das-atom-db/issues/210" + ) + db: AtomDB = request.getfixturevalue(database) + node_a = self._add_node(db, "Aaa", "Test", database) + link_a = self._add_link(db, "Test", [{"name": "A", "type": "A"}], database) + node_a["name"] = "B" + link_a["targets"] = [node_a["handle"]] + db.commit(buffer=[node_a, link_a]) + count = db.count_atoms({"precise": True}) + assert count["atom_count"] == 3 + assert count["node_count"] == 2 + assert count["link_count"] == 1 + + @pytest.mark.parametrize("database", ["redis_mongo_db"]) + def test_commit_buffer_exception(self, database, request): + db: AtomDB = request.getfixturevalue(database) + with pytest.raises(Exception): + db.commit(buffer=[{"name": "A", "type": "A"}]) + + with pytest.raises(Exception, match="Failed to commit Atom Types"): + db.mongo_bulk_insertion_buffer = {"atom_types": ("a", "a")} + db.commit()