From 32c9471ec766397b952e2c6c12fed2081c15ff70 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 18 Sep 2023 16:59:35 +0800 Subject: [PATCH 01/16] [explorer/rest]: add namespace endpoint --- explorer/rest/rest/__init__.py | 23 ++++++++++ explorer/rest/rest/db/NemDatabase.py | 50 ++++++++++++++++++++++ explorer/rest/rest/facade/NemRestFacade.py | 14 ++++++ explorer/rest/rest/model/Namespace.py | 31 ++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 explorer/rest/rest/model/Namespace.py diff --git a/explorer/rest/rest/__init__.py b/explorer/rest/rest/__init__.py index 4e90c5e86..ef2071032 100644 --- a/explorer/rest/rest/__init__.py +++ b/explorer/rest/rest/__init__.py @@ -79,6 +79,29 @@ def api_get_nem_blocks(): return jsonify(nem_api_facade.get_blocks(limit=limit, offset=offset, min_height=min_height, sort=sort)) + @app.route('/api/nem/namespace/') + def api_get_nem_namespace_by_name(name): + result = nem_api_facade.get_namespace(name) + if not result: + abort(404) + return jsonify(result) + + @app.route('/api/nem/namespaces') + def api_get_nem_namespaces(): + try: + + limit = int(request.args.get('limit', 10)) + offset = int(request.args.get('offset', 0)) + sort = request.args.get('sort', 'DESC') + + if limit < 0 or offset < 0 or sort.upper() not in ['ASC', 'DESC']: + raise ValueError() + + except ValueError: + abort(400) + + return jsonify(nem_api_facade.get_namespaces(limit=limit, offset=offset, sort=sort)) + def setup_error_handlers(app): @app.errorhandler(404) diff --git a/explorer/rest/rest/db/NemDatabase.py b/explorer/rest/rest/db/NemDatabase.py index 997a25cfa..09912130a 100644 --- a/explorer/rest/rest/db/NemDatabase.py +++ b/explorer/rest/rest/db/NemDatabase.py @@ -5,6 +5,7 @@ from symbolchain.Network import NetworkLocator from rest.model.Block import BlockView +from rest.model.Namespace import NamespaceView from .DatabaseConnection import DatabaseConnectionPool @@ -38,6 +39,16 @@ def _create_block_view(self, result): size=result[9] ) + def _create_namespace_view(self, result): + owner_public_key = PublicKey(_format_bytes(result[1])) + return NamespaceView( + root_namespace=result[0], + owner=self.network.public_key_to_address(owner_public_key), + registered_height=result[2], + expiration_height=result[3], + sub_namespaces=result[4] + ) + def get_block(self, height): """Gets block by height in database.""" @@ -67,3 +78,42 @@ def get_blocks(self, limit, offset, min_height, sort): results = cursor.fetchall() return [self._create_block_view(result) for result in results] + + def get_namespace(self, root_namespace): + """Gets namespace by name in database.""" + + with self.connection() as connection: + cursor = connection.cursor() + cursor.execute(''' + SELECT + root_namespace, + owner, + registered_height, + expiration_height, + sub_namespaces + FROM namespaces + WHERE root_namespace = %s + ''', (root_namespace,)) + result = cursor.fetchone() + + return self._create_namespace_view(result) if result else None + + def get_namespaces(self, limit, offset, sort): + """Gets namespaces pagination in database.""" + + with self.connection() as connection: + cursor = connection.cursor() + cursor.execute(f''' + SELECT + root_namespace, + owner, + registered_height, + expiration_height, + sub_namespaces + FROM namespaces + ORDER BY id {sort} + LIMIT %s OFFSET %s + ''', (limit, offset,)) + results = cursor.fetchall() + + return [self._create_namespace_view(result) for result in results] diff --git a/explorer/rest/rest/facade/NemRestFacade.py b/explorer/rest/rest/facade/NemRestFacade.py index 2afaeded2..085e26a77 100644 --- a/explorer/rest/rest/facade/NemRestFacade.py +++ b/explorer/rest/rest/facade/NemRestFacade.py @@ -22,3 +22,17 @@ def get_blocks(self, limit, offset, min_height, sort): blocks = self.nem_db.get_blocks(limit, offset, min_height, sort) return [block.to_dict() for block in blocks] + + def get_namespace(self, name): + """Gets namespace by root namespace name.""" + + namespace = self.nem_db.get_namespace(name) + + return namespace.to_dict() if namespace else None + + def get_namespaces(self, limit, offset, sort): + """Gets namespaces pagination.""" + + namespaces = self.nem_db.get_namespaces(limit, offset, sort) + + return [namespace.to_dict() for namespace in namespaces] diff --git a/explorer/rest/rest/model/Namespace.py b/explorer/rest/rest/model/Namespace.py new file mode 100644 index 000000000..931c22078 --- /dev/null +++ b/explorer/rest/rest/model/Namespace.py @@ -0,0 +1,31 @@ +class NamespaceView: + def __init__(self, root_namespace, owner, registered_height, expiration_height, sub_namespaces): + """Create Namespace view.""" + + # pylint: disable=too-many-arguments + + self.root_namespace = root_namespace + self.owner = owner + self.registered_height = registered_height + self.expiration_height = expiration_height + self.sub_namespaces = sub_namespaces + + def __eq__(self, other): + return isinstance(other, NamespaceView) and all([ + self.root_namespace == other.root_namespace, + self.owner == other.owner, + self.registered_height == other.registered_height, + self.expiration_height == other.expiration_height, + self.sub_namespaces == other.sub_namespaces + ]) + + def to_dict(self): + """Formats the namespace info as a dictionary.""" + + return { + 'rootNamespace': self.root_namespace, + 'owner': str(self.owner), + 'registeredHeight': self.registered_height, + 'expirationHeight': self.expiration_height, + 'subNamespaces': self.sub_namespaces + } From bacf8aa1044447ddcaf06f3bbf2961697591b7d7 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 6 Nov 2023 23:45:01 +0800 Subject: [PATCH 02/16] [explorer/rest]: added namespaces sample data for unit test --- explorer/rest/tests/test/DatabaseTestUtils.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/explorer/rest/tests/test/DatabaseTestUtils.py b/explorer/rest/tests/test/DatabaseTestUtils.py index bf8891623..47b656e56 100644 --- a/explorer/rest/tests/test/DatabaseTestUtils.py +++ b/explorer/rest/tests/test/DatabaseTestUtils.py @@ -22,6 +22,16 @@ 'size' ] ) +Namespace = namedtuple( + 'Namespace', + [ + 'root_namespace', + 'owner', + 'registered_height', + 'expiration_height', + 'sub_namespaces' + ] +) DatabaseConfig = namedtuple('DatabaseConfig', ['database', 'user', 'password', 'host', 'port']) # region test data @@ -56,6 +66,23 @@ BlockView(*BLOCKS[1]._replace(total_fees=201.0, signer=Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'))) ] +NAMESPACES = [ + Namespace( + 'oxford', + '8D07F90FB4BBE7715FA327C926770166A11BE2E494A970605F2E12557F66C9B9', + 1000, + 526600, + '{oxford.union,oxford.philosophy,oxford.comma,oxford.dictionary,oxford.blockchain}' + ), + Namespace( + 'dragon', + 'F9BD190DD0C364261F5C8A74870CC7F7374E631352293C62ECC437657E5DE2CD', + 2000, + 527600, + '{}' + ), +] + # endregion @@ -80,6 +107,19 @@ def initialize_database(db_config, network_name): ) ''') + cursor.execute( + ''' + CREATE TABLE IF NOT EXISTS namespaces ( + id serial PRIMARY KEY, + root_namespace varchar(16), + owner bytea NOT NULL, + registered_height bigint NOT NULL, + expiration_height bigint NOT NULL, + sub_namespaces VARCHAR(146)[] + ) + ''' + ) + # Insert data for block in BLOCKS: cursor.execute( @@ -99,6 +139,20 @@ def initialize_database(db_config, network_name): ) ) + for namespace in NAMESPACES: + cursor.execute( + ''' + INSERT INTO namespaces (root_namespace, owner, registered_height, expiration_height, sub_namespaces) + VALUES (%s, %s, %s, %s, %s) + ''', ( + namespace.root_namespace, + unhexlify(namespace.owner), + namespace.registered_height, + namespace.expiration_height, + namespace.sub_namespaces + ) + ) + connection.commit() From 87b5e340734c6c4c1392c2dff9f94677c1131ce4 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 23 Sep 2023 18:35:19 +0800 Subject: [PATCH 03/16] [explorer/rest]: enhancement on namespace endpoint, added mosaics info, registered timestamp --- explorer/rest/rest/db/NemDatabase.py | 105 +++++++++++++++++++++----- explorer/rest/rest/model/Namespace.py | 12 ++- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/explorer/rest/rest/db/NemDatabase.py b/explorer/rest/rest/db/NemDatabase.py index 09912130a..07ac01eba 100644 --- a/explorer/rest/rest/db/NemDatabase.py +++ b/explorer/rest/rest/db/NemDatabase.py @@ -41,12 +41,33 @@ def _create_block_view(self, result): def _create_namespace_view(self, result): owner_public_key = PublicKey(_format_bytes(result[1])) + + mosaics = [] + + if result[5] != []: + # Formatting mosaics info + for mosaic in result[6]: + namespace_mosaic_name = mosaic['namespace_name'].split('.') + namespace_name = '.'.join(namespace_mosaic_name[:-1]) + mosaic_name = namespace_mosaic_name[-1] + + mosaics.append({ + 'namespaceName': namespace_name, + 'mosaicName': mosaic_name, + 'totalSupply': mosaic['total_supply'], + 'divisibility': mosaic['divisibility'], + 'registeredHeight': mosaic['registered_height'], + 'registeredTimestamp': mosaic['registered_timestamp'].replace('T', ' ') + }) + return NamespaceView( root_namespace=result[0], owner=self.network.public_key_to_address(owner_public_key), registered_height=result[2], - expiration_height=result[3], - sub_namespaces=result[4] + registered_timestamp=str(result[3]), + expiration_height=result[4], + sub_namespaces=result[5], + mosaics=mosaics ) def get_block(self, height): @@ -86,13 +107,37 @@ def get_namespace(self, root_namespace): cursor = connection.cursor() cursor.execute(''' SELECT - root_namespace, - owner, - registered_height, - expiration_height, - sub_namespaces - FROM namespaces - WHERE root_namespace = %s + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp AS registered_timestamp, + n.expiration_height, + n.sub_namespaces, + CASE + WHEN COUNT(m.namespace_name) = 0 THEN '[]' + ELSE json_agg(json_build_object( + 'namespace_name', namespace_name, + 'total_supply', m.total_supply, + 'divisibility', m.divisibility, + 'registered_height', m.registered_height, + 'registered_timestamp', b2.timestamp + )) + END AS mosaics + FROM namespaces n + LEFT JOIN mosaics m + ON n.root_namespace = m.root_namespace + LEFT JOIN blocks b1 + ON n.registered_height = b1.height + LEFT JOIN blocks b2 + ON m.registered_height = b2.height + WHERE n.root_namespace = %s + GROUP BY + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp, + n.expiration_height, + n.sub_namespaces ''', (root_namespace,)) result = cursor.fetchone() @@ -104,15 +149,39 @@ def get_namespaces(self, limit, offset, sort): with self.connection() as connection: cursor = connection.cursor() cursor.execute(f''' - SELECT - root_namespace, - owner, - registered_height, - expiration_height, - sub_namespaces - FROM namespaces - ORDER BY id {sort} - LIMIT %s OFFSET %s + SELECT + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp AS registered_timestamp, + n.expiration_height, + n.sub_namespaces, + CASE + WHEN COUNT(m.namespace_name) = 0 THEN '[]' + ELSE json_agg(json_build_object( + 'namespace_name', namespace_name, + 'total_supply', m.total_supply, + 'divisibility', m.divisibility, + 'registered_height', m.registered_height, + 'registered_timestamp', b2.timestamp + )) + END AS mosaics + FROM namespaces n + LEFT JOIN mosaics m + ON n.root_namespace = m.root_namespace + LEFT JOIN blocks b1 + ON n.registered_height = b1.height + LEFT JOIN blocks b2 + ON m.registered_height = b2.height + GROUP BY + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp, + n.expiration_height, + n.sub_namespaces + ORDER BY n.registered_height {sort} + LIMIT %s OFFSET %s ''', (limit, offset,)) results = cursor.fetchall() diff --git a/explorer/rest/rest/model/Namespace.py b/explorer/rest/rest/model/Namespace.py index 931c22078..f67eda865 100644 --- a/explorer/rest/rest/model/Namespace.py +++ b/explorer/rest/rest/model/Namespace.py @@ -1,5 +1,5 @@ class NamespaceView: - def __init__(self, root_namespace, owner, registered_height, expiration_height, sub_namespaces): + def __init__(self, root_namespace, owner, registered_height, registered_timestamp, expiration_height, sub_namespaces, mosaics): """Create Namespace view.""" # pylint: disable=too-many-arguments @@ -7,16 +7,20 @@ def __init__(self, root_namespace, owner, registered_height, expiration_height, self.root_namespace = root_namespace self.owner = owner self.registered_height = registered_height + self.registered_timestamp = registered_timestamp self.expiration_height = expiration_height self.sub_namespaces = sub_namespaces + self.mosaics = mosaics def __eq__(self, other): return isinstance(other, NamespaceView) and all([ self.root_namespace == other.root_namespace, self.owner == other.owner, self.registered_height == other.registered_height, + self.registered_timestamp == other.registered_timestamp, self.expiration_height == other.expiration_height, - self.sub_namespaces == other.sub_namespaces + self.sub_namespaces == other.sub_namespaces, + self.mosaics == other.mosaics ]) def to_dict(self): @@ -26,6 +30,8 @@ def to_dict(self): 'rootNamespace': self.root_namespace, 'owner': str(self.owner), 'registeredHeight': self.registered_height, + 'registeredTimestamp': str(self.registered_timestamp), 'expirationHeight': self.expiration_height, - 'subNamespaces': self.sub_namespaces + 'subNamespaces': self.sub_namespaces, + 'mosaics': self.mosaics } From b5c8a68251ceda5125f76a8bdb089d5193e2e3b0 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 23 Sep 2023 19:07:59 +0800 Subject: [PATCH 04/16] [explorer/rest]: enhancement on namespace endpoint, support search sub namespace --- explorer/rest/rest/db/NemDatabase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/explorer/rest/rest/db/NemDatabase.py b/explorer/rest/rest/db/NemDatabase.py index 07ac01eba..84a3412ac 100644 --- a/explorer/rest/rest/db/NemDatabase.py +++ b/explorer/rest/rest/db/NemDatabase.py @@ -100,7 +100,7 @@ def get_blocks(self, limit, offset, min_height, sort): return [self._create_block_view(result) for result in results] - def get_namespace(self, root_namespace): + def get_namespace(self, namespace): """Gets namespace by name in database.""" with self.connection() as connection: @@ -130,7 +130,7 @@ def get_namespace(self, root_namespace): ON n.registered_height = b1.height LEFT JOIN blocks b2 ON m.registered_height = b2.height - WHERE n.root_namespace = %s + WHERE n.root_namespace = %s or %s = ANY(n.sub_namespaces) GROUP BY n.root_namespace, n.owner, @@ -138,7 +138,7 @@ def get_namespace(self, root_namespace): b1.timestamp, n.expiration_height, n.sub_namespaces - ''', (root_namespace,)) + ''', (namespace, namespace)) result = cursor.fetchone() return self._create_namespace_view(result) if result else None From 607784921c5d0ce33b9e9f02eceef01626d0d32d Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 23 Sep 2023 19:29:19 +0800 Subject: [PATCH 05/16] [explorer/rest]: fix wrong assign value --- explorer/rest/rest/db/NemDatabase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/rest/rest/db/NemDatabase.py b/explorer/rest/rest/db/NemDatabase.py index 84a3412ac..8d71cf10f 100644 --- a/explorer/rest/rest/db/NemDatabase.py +++ b/explorer/rest/rest/db/NemDatabase.py @@ -44,7 +44,7 @@ def _create_namespace_view(self, result): mosaics = [] - if result[5] != []: + if result[6] != []: # Formatting mosaics info for mosaic in result[6]: namespace_mosaic_name = mosaic['namespace_name'].split('.') From cfeef461636e11d6974e4d618e147eb534cce9c9 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Nov 2023 20:44:39 +0800 Subject: [PATCH 06/16] [explorer/rest]: extract common namespace SQL query into a single method --- explorer/rest/rest/db/NemDatabase.py | 141 ++++++++++++--------------- 1 file changed, 64 insertions(+), 77 deletions(-) diff --git a/explorer/rest/rest/db/NemDatabase.py b/explorer/rest/rest/db/NemDatabase.py index 8d71cf10f..6dec7bf21 100644 --- a/explorer/rest/rest/db/NemDatabase.py +++ b/explorer/rest/rest/db/NemDatabase.py @@ -25,6 +25,48 @@ def __init__(self, db_config, network_name): super().__init__(db_config) self.network = NetworkLocator.find_by_name(Network.NETWORKS, network_name) + def _generate_namespace_sql_query(self, where_condition=None): # pylint: disable=no-self-use + """Base SQL query for namespaces.""" + + where_clause = f'WHERE {where_condition}' if where_condition else '' + + return f''' + SELECT + n.id, + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp AS registered_timestamp, + n.expiration_height, + n.sub_namespaces, + CASE + WHEN COUNT(m.namespace_name) = 0 THEN '[]' + ELSE json_agg(json_build_object( + 'namespace_name', namespace_name, + 'total_supply', m.total_supply, + 'divisibility', m.divisibility, + 'registered_height', m.registered_height, + 'registered_timestamp', b2.timestamp + )) + END AS mosaics + FROM namespaces n + LEFT JOIN mosaics m + ON n.root_namespace = m.root_namespace + LEFT JOIN blocks b1 + ON n.registered_height = b1.height + LEFT JOIN blocks b2 + ON m.registered_height = b2.height + {where_clause} + GROUP BY + n.id, + n.root_namespace, + n.owner, + n.registered_height, + b1.timestamp, + n.expiration_height, + n.sub_namespaces + ''' + def _create_block_view(self, result): harvest_public_key = PublicKey(_format_bytes(result[7])) return BlockView( @@ -40,13 +82,13 @@ def _create_block_view(self, result): ) def _create_namespace_view(self, result): - owner_public_key = PublicKey(_format_bytes(result[1])) + owner_public_key = PublicKey(_format_bytes(result[2])) mosaics = [] - if result[6] != []: + if result[7] != []: # Formatting mosaics info - for mosaic in result[6]: + for mosaic in result[7]: namespace_mosaic_name = mosaic['namespace_name'].split('.') namespace_name = '.'.join(namespace_mosaic_name[:-1]) mosaic_name = namespace_mosaic_name[-1] @@ -61,12 +103,12 @@ def _create_namespace_view(self, result): }) return NamespaceView( - root_namespace=result[0], + root_namespace=result[1], owner=self.network.public_key_to_address(owner_public_key), - registered_height=result[2], - registered_timestamp=str(result[3]), - expiration_height=result[4], - sub_namespaces=result[5], + registered_height=result[3], + registered_timestamp=str(result[4]), + expiration_height=result[5], + sub_namespaces=result[6], mosaics=mosaics ) @@ -103,42 +145,14 @@ def get_blocks(self, limit, offset, min_height, sort): def get_namespace(self, namespace): """Gets namespace by name in database.""" + sql = self._generate_namespace_sql_query( + 'n.root_namespace = %s or %s = ANY(n.sub_namespaces)' + ) + params = (namespace, namespace) + with self.connection() as connection: cursor = connection.cursor() - cursor.execute(''' - SELECT - n.root_namespace, - n.owner, - n.registered_height, - b1.timestamp AS registered_timestamp, - n.expiration_height, - n.sub_namespaces, - CASE - WHEN COUNT(m.namespace_name) = 0 THEN '[]' - ELSE json_agg(json_build_object( - 'namespace_name', namespace_name, - 'total_supply', m.total_supply, - 'divisibility', m.divisibility, - 'registered_height', m.registered_height, - 'registered_timestamp', b2.timestamp - )) - END AS mosaics - FROM namespaces n - LEFT JOIN mosaics m - ON n.root_namespace = m.root_namespace - LEFT JOIN blocks b1 - ON n.registered_height = b1.height - LEFT JOIN blocks b2 - ON m.registered_height = b2.height - WHERE n.root_namespace = %s or %s = ANY(n.sub_namespaces) - GROUP BY - n.root_namespace, - n.owner, - n.registered_height, - b1.timestamp, - n.expiration_height, - n.sub_namespaces - ''', (namespace, namespace)) + cursor.execute(sql, params) result = cursor.fetchone() return self._create_namespace_view(result) if result else None @@ -146,43 +160,16 @@ def get_namespace(self, namespace): def get_namespaces(self, limit, offset, sort): """Gets namespaces pagination in database.""" + sql = self._generate_namespace_sql_query() + sql += f''' + ORDER BY n.id {sort} + LIMIT %s OFFSET %s + ''' + params = (limit, offset) + with self.connection() as connection: cursor = connection.cursor() - cursor.execute(f''' - SELECT - n.root_namespace, - n.owner, - n.registered_height, - b1.timestamp AS registered_timestamp, - n.expiration_height, - n.sub_namespaces, - CASE - WHEN COUNT(m.namespace_name) = 0 THEN '[]' - ELSE json_agg(json_build_object( - 'namespace_name', namespace_name, - 'total_supply', m.total_supply, - 'divisibility', m.divisibility, - 'registered_height', m.registered_height, - 'registered_timestamp', b2.timestamp - )) - END AS mosaics - FROM namespaces n - LEFT JOIN mosaics m - ON n.root_namespace = m.root_namespace - LEFT JOIN blocks b1 - ON n.registered_height = b1.height - LEFT JOIN blocks b2 - ON m.registered_height = b2.height - GROUP BY - n.root_namespace, - n.owner, - n.registered_height, - b1.timestamp, - n.expiration_height, - n.sub_namespaces - ORDER BY n.registered_height {sort} - LIMIT %s OFFSET %s - ''', (limit, offset,)) + cursor.execute(sql, params) results = cursor.fetchall() return [self._create_namespace_view(result) for result in results] From 7004ad5e26dd079db0d56e71c8932a09c77a6770 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Nov 2023 23:55:29 +0800 Subject: [PATCH 07/16] [explorer/rest]: added sample mosaic row in initialize_database method --- explorer/rest/tests/test/DatabaseTestUtils.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/explorer/rest/tests/test/DatabaseTestUtils.py b/explorer/rest/tests/test/DatabaseTestUtils.py index 47b656e56..43ca9f368 100644 --- a/explorer/rest/tests/test/DatabaseTestUtils.py +++ b/explorer/rest/tests/test/DatabaseTestUtils.py @@ -32,6 +32,25 @@ 'sub_namespaces' ] ) +Mosaic = namedtuple( + 'Mosaic', + [ + 'root_namespace', + 'namespace_name', + 'description', + 'creator', + 'registered_height', + 'initial_supply', + 'total_supply', + 'divisibility', + 'supply_mutable', + 'transferable', + 'levy_type', + 'levy_namespace_name', + 'levy_fee', + 'levy_recipient' + ] +) DatabaseConfig = namedtuple('DatabaseConfig', ['database', 'user', 'password', 'host', 'port']) # region test data @@ -83,6 +102,26 @@ ), ] +MOSAICS = [ + Mosaic( + 'dragon', + 'dragon.dragonfly', + 'sample information', + 'F9BD190DD0C364261F5C8A74870CC7F7374E631352293C62ECC437657E5DE2CD', + 2, + 100, + 100, + 0, + False, + True, + None, + None, + None, + None + ) +] + + # endregion @@ -120,6 +159,28 @@ def initialize_database(db_config, network_name): ''' ) + cursor.execute( + ''' + CREATE TABLE IF NOT EXISTS mosaics ( + id serial PRIMARY KEY, + root_namespace varchar(16), + namespace_name varchar(146), + description varchar(512), + creator bytea NOT NULL, + registered_height bigint NOT NULL, + initial_supply bigint DEFAULT 0, + total_supply bigint DEFAULT 0, + divisibility int NOT NULL, + supply_mutable boolean DEFAULT false, + transferable boolean DEFAULT false, + levy_type int, + levy_namespace_name varchar(146), + levy_fee bigint DEFAULT 0, + levy_recipient bytea + ) + ''' + ) + # Insert data for block in BLOCKS: cursor.execute( @@ -153,6 +214,44 @@ def initialize_database(db_config, network_name): ) ) + for mosaic in MOSAICS: + cursor.execute( + ''' + INSERT INTO mosaics ( + root_namespace, + namespace_name, + description, + creator, + registered_height, + initial_supply, + total_supply, + divisibility, + supply_mutable, + transferable, + levy_type, + levy_namespace_name, + levy_fee, + levy_recipient + ) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + ''', ( + mosaic.root_namespace, + mosaic.namespace_name, + mosaic.description, + unhexlify(mosaic.creator), + mosaic.registered_height, + mosaic.initial_supply, + mosaic.total_supply, + mosaic.divisibility, + mosaic.supply_mutable, + mosaic.transferable, + mosaic.levy_type, + mosaic.levy_namespace_name, + mosaic.levy_fee, + Address(mosaic.levy_recipient).bytes if mosaic.levy_recipient is not None else None, + ) + ) + connection.commit() From 57ee0d467ad5cc9ac07220a79132a6c8a104b50b Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Nov 2023 23:57:29 +0800 Subject: [PATCH 08/16] [explorer/rest]: added NAMESPACE_VIEWS test data in database test utils --- explorer/rest/tests/test/DatabaseTestUtils.py | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/explorer/rest/tests/test/DatabaseTestUtils.py b/explorer/rest/tests/test/DatabaseTestUtils.py index 43ca9f368..a52a4c75f 100644 --- a/explorer/rest/tests/test/DatabaseTestUtils.py +++ b/explorer/rest/tests/test/DatabaseTestUtils.py @@ -7,6 +7,7 @@ from rest.db.NemDatabase import NemDatabase from rest.model.Block import BlockView +from rest.model.Namespace import NamespaceView Block = namedtuple( 'Block', @@ -89,15 +90,15 @@ Namespace( 'oxford', '8D07F90FB4BBE7715FA327C926770166A11BE2E494A970605F2E12557F66C9B9', - 1000, - 526600, - '{oxford.union,oxford.philosophy,oxford.comma,oxford.dictionary,oxford.blockchain}' + 1, + 525601, + '{oxford.union,oxford.branch.uk}' ), Namespace( 'dragon', 'F9BD190DD0C364261F5C8A74870CC7F7374E631352293C62ECC437657E5DE2CD', - 2000, - 527600, + 2, + 525602, '{}' ), ] @@ -122,6 +123,34 @@ ] +NAMESPACE_VIEWS = [ + NamespaceView( + NAMESPACES[0].root_namespace, + Address('NANEMOABLAGR72AZ2RV3V4ZHDCXW25XQ73O7OBT5'), + NAMESPACES[0].registered_height, + '2015-03-29 00:06:25', + NAMESPACES[0].expiration_height, + ['oxford.union', 'oxford.branch.uk'], + [] + ), + NamespaceView( + NAMESPACES[1].root_namespace, + Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'), + NAMESPACES[1].registered_height, + '2015-03-29 20:34:19', + NAMESPACES[1].expiration_height, + [], + [{ + 'namespaceName': 'dragon', + 'mosaicName': 'dragonfly', + 'totalSupply': 100, + 'divisibility': 0, + 'registeredHeight': 2, + 'registeredTimestamp': '2015-03-29 20:34:19' + }] + ), +] + # endregion From 93e2920a6be195dc36651feaed426b0719b6c347 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Nov 2023 23:58:15 +0800 Subject: [PATCH 09/16] [explorer/rest]: added BLOCK_VIEWS test data in database test utils --- explorer/rest/tests/test/DatabaseTestUtils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/explorer/rest/tests/test/DatabaseTestUtils.py b/explorer/rest/tests/test/DatabaseTestUtils.py index a52a4c75f..6a7b7fd19 100644 --- a/explorer/rest/tests/test/DatabaseTestUtils.py +++ b/explorer/rest/tests/test/DatabaseTestUtils.py @@ -81,11 +81,6 @@ 752), ] -BLOCK_VIEWS = [ - BlockView(*BLOCKS[0]._replace(total_fees=102.0, signer=Address('NANEMOABLAGR72AZ2RV3V4ZHDCXW25XQ73O7OBT5'))), - BlockView(*BLOCKS[1]._replace(total_fees=201.0, signer=Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'))) -] - NAMESPACES = [ Namespace( 'oxford', @@ -122,6 +117,10 @@ ) ] +BLOCK_VIEWS = [ + BlockView(*BLOCKS[0]._replace(total_fees=102.0, signer=Address('NANEMOABLAGR72AZ2RV3V4ZHDCXW25XQ73O7OBT5'))), + BlockView(*BLOCKS[1]._replace(total_fees=201.0, signer=Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'))) +] NAMESPACE_VIEWS = [ NamespaceView( From 3f032f130672b8e1f5fe023a44c9c6bcb423681a Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Nov 2023 23:59:38 +0800 Subject: [PATCH 10/16] [explorer/rest]: added namespace model unit test --- explorer/rest/tests/model/test_Namespace.py | 97 +++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 explorer/rest/tests/model/test_Namespace.py diff --git a/explorer/rest/tests/model/test_Namespace.py b/explorer/rest/tests/model/test_Namespace.py new file mode 100644 index 000000000..8505628b1 --- /dev/null +++ b/explorer/rest/tests/model/test_Namespace.py @@ -0,0 +1,97 @@ +import unittest + +from symbolchain.nem.Network import Address + +from rest.model.Namespace import NamespaceView + + +class NamespaceTest(unittest.TestCase): + @staticmethod + def _create_default_namespace_view(override=None): + namespace_view = NamespaceView( + 'dragon', + Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'), + 10, + '2015-03-29 20:34:19', + 525610, + ['dragon.tank', 'dragon.tower.uk'], + [ + { + 'namespaceName': 'dragon', + 'mosaicName': 'dragonfly', + 'totalSupply': 100, + 'divisibility': 0, + 'registeredHeight': 300, + 'registeredTimestamp': '2015-03-29 20:34:19' + } + ] + ) + + if override: + setattr(namespace_view, override[0], override[1]) + + return namespace_view + + def test_can_create_namespace_view(self): + # Act: + namespace_view = self._create_default_namespace_view() + + # Assert: + self.assertEqual('dragon', namespace_view.root_namespace) + self.assertEqual(Address('NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3'), namespace_view.owner) + self.assertEqual(10, namespace_view.registered_height) + self.assertEqual('2015-03-29 20:34:19', namespace_view.registered_timestamp) + self.assertEqual(525610, namespace_view.expiration_height) + self.assertEqual(['dragon.tank', 'dragon.tower.uk'], namespace_view.sub_namespaces) + self.assertEqual( + [{ + 'namespaceName': 'dragon', + 'mosaicName': 'dragonfly', + 'totalSupply': 100, + 'divisibility': 0, + 'registeredHeight': 300, + 'registeredTimestamp': '2015-03-29 20:34:19' + }], + namespace_view.mosaics + ) + + def test_can_convert_to_simple_dict(self): + # Arrange: + namespace_view = self._create_default_namespace_view() + + # Act: + namespace_view_dict = namespace_view.to_dict() + + # Assert: + self.assertEqual({ + 'rootNamespace': 'dragon', + 'owner': 'NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3', + 'registeredHeight': 10, + 'registeredTimestamp': '2015-03-29 20:34:19', + 'expirationHeight': 525610, + 'subNamespaces': ['dragon.tank', 'dragon.tower.uk'], + 'mosaics': [{ + 'namespaceName': 'dragon', + 'mosaicName': 'dragonfly', + 'totalSupply': 100, + 'divisibility': 0, + 'registeredHeight': 300, + 'registeredTimestamp': '2015-03-29 20:34:19' + }] + }, namespace_view_dict) + + def test_eq_is_supported(self): + # Arrange: + namespace_view = self._create_default_namespace_view() + + # Assert: + self.assertEqual(namespace_view, self._create_default_namespace_view()) + self.assertNotEqual(namespace_view, None) + self.assertNotEqual(namespace_view, 'namespace_view') + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('root_namespace', 'namespace'))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('owner', Address('TALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3')))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('registered_height', 11))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('registered_timestamp', '2015-03-29 20:34:20'))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('expiration_height', 525611))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('sub_namespaces', ['dragon.tank']))) + self.assertNotEqual(namespace_view, self._create_default_namespace_view(('mosaics', []))) From 642191a64f7d1211a54bd79061aeb125d58a56c9 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 8 Nov 2023 01:01:52 +0800 Subject: [PATCH 11/16] [explorer/rest]: added rest facade namespace unit test --- explorer/rest/tests/db/test_NemDatabase.py | 1 + .../rest/tests/facade/test_NemRestFacade.py | 54 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/explorer/rest/tests/db/test_NemDatabase.py b/explorer/rest/tests/db/test_NemDatabase.py index 208089b4f..4e5b0569c 100644 --- a/explorer/rest/tests/db/test_NemDatabase.py +++ b/explorer/rest/tests/db/test_NemDatabase.py @@ -5,6 +5,7 @@ from ..test.DatabaseTestUtils import BLOCK_VIEWS, DatabaseTestBase BlockQueryParams = namedtuple('BlockQueryParams', ['limit', 'offset', 'min_height', 'sort']) +PaginationQueryParams = namedtuple('PaginationQueryParams', ['limit', 'offset', 'sort']) # region test data diff --git a/explorer/rest/tests/facade/test_NemRestFacade.py b/explorer/rest/tests/facade/test_NemRestFacade.py index e546004dc..dc8d42683 100644 --- a/explorer/rest/tests/facade/test_NemRestFacade.py +++ b/explorer/rest/tests/facade/test_NemRestFacade.py @@ -1,7 +1,7 @@ from rest.facade.NemRestFacade import NemRestFacade -from ..db.test_NemDatabase import BlockQueryParams -from ..test.DatabaseTestUtils import BLOCK_VIEWS, DatabaseTestBase +from ..db.test_NemDatabase import BlockQueryParams, PaginationQueryParams +from ..test.DatabaseTestUtils import BLOCK_VIEWS, NAMESPACE_VIEWS, DatabaseTestBase # region test data @@ -9,11 +9,17 @@ EXPECTED_BLOCK_2 = BLOCK_VIEWS[1].to_dict() +EXPECTED_NAMESPACE_1 = NAMESPACE_VIEWS[0].to_dict() + +EXPECTED_NAMESPACE_2 = NAMESPACE_VIEWS[1].to_dict() + # endregion class TestNemRestFacade(DatabaseTestBase): + # region block tests + def _assert_can_retrieve_block(self, height, expected_block): # Arrange: nem_rest_facade = NemRestFacade(self.db_config, self.network_name) @@ -57,3 +63,47 @@ def test_blocks_sorted_by_height_asc(self): def test_blocks_sorted_by_height_desc(self): self._assert_can_retrieve_blocks(BlockQueryParams(10, 0, 0, 'desc'), [EXPECTED_BLOCK_2, EXPECTED_BLOCK_1]) + + # endregion + + # region namespace tests + + def _assert_can_retrieve_namespace(self, name, expected_namespace): + # Arrange: + nem_rest_facade = NemRestFacade(self.db_config, self.network) + + # Act: + namespace = nem_rest_facade.get_namespace(name) + + # Assert: + self.assertEqual(expected_namespace, namespace) + + def _assert_can_retrieve_namespaces(self, query_params, expected_namespaces): + # Arrange: + nem_rest_facade = NemRestFacade(self.db_config, self.network) + + # Act: + namespaces = nem_rest_facade.get_namespaces(query_params.limit, query_params.offset, query_params.sort) + + # Assert: + self.assertEqual(expected_namespaces, namespaces) + + def test_retrieve_namespace_by_name(self): + self._assert_can_retrieve_namespace('oxford', EXPECTED_NAMESPACE_1) + + def test_returns_none_for_nonexistent_namespace(self): + self._assert_can_retrieve_namespace('non_existing_namespace', None) + + def test_namespaces_filtered_by_limit(self): + self._assert_can_retrieve_namespaces(PaginationQueryParams(1, 0, 'desc'), [EXPECTED_NAMESPACE_2]) + + def test_namespaces_filtered_by_offset(self): + self._assert_can_retrieve_namespaces(PaginationQueryParams(1, 1, 'desc'), [EXPECTED_NAMESPACE_1]) + + def test_namespaces_sorted_by_id_asc(self): + self._assert_can_retrieve_namespaces(PaginationQueryParams(10, 0, 'asc'), [EXPECTED_NAMESPACE_1, EXPECTED_NAMESPACE_2]) + + def test_namespaces_sorted_by_id_desc(self): + self._assert_can_retrieve_namespaces(PaginationQueryParams(10, 0, 'desc'), [EXPECTED_NAMESPACE_2, EXPECTED_NAMESPACE_1]) + + # endregion From d45ce85c3bf7ac7a1b304d3a5d6bad0bce8658ef Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 8 Nov 2023 01:12:08 +0800 Subject: [PATCH 12/16] [explorer/rest]: added rest namespace unit test --- explorer/rest/tests/test_rest.py | 108 ++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/explorer/rest/tests/test_rest.py b/explorer/rest/tests/test_rest.py index 5b7971dd6..68520b32a 100644 --- a/explorer/rest/tests/test_rest.py +++ b/explorer/rest/tests/test_rest.py @@ -7,7 +7,7 @@ from rest import create_app -from .test.DatabaseTestUtils import BLOCK_VIEWS, DatabaseConfig, initialize_database +from .test.DatabaseTestUtils import BLOCK_VIEWS, NAMESPACE_VIEWS, DatabaseConfig, initialize_database DATABASE_CONFIG_INI = 'db_config.ini' @@ -17,6 +17,11 @@ EXPECTED_BLOCK_VIEW_2 = BLOCK_VIEWS[1] +EXPECTED_NAMESPACE_VIEW_1 = NAMESPACE_VIEWS[0] + +EXPECTED_NAMESPACE_VIEW_2 = NAMESPACE_VIEWS[1] + + # endregion # region fixtures @@ -186,3 +191,104 @@ def test_api_nem_blocks_invalid_sort(client): # pylint: disable=redefined-outer # endregion + + +# region /namespace/ + +def _assert_get_api_nem_namespace_by_name(client, name, expected_status_code, expected_result): # pylint: disable=redefined-outer-name + # Act: + response = client.get(f'/api/nem/namespace/{name}') + + print(response.json) + + # Assert: + assert expected_status_code == response.status_code + assert expected_result == response.json + + +def test_api_nem_namespace_by_name(client): # pylint: disable=redefined-outer-name + _assert_get_api_nem_namespace_by_name(client, 'oxford', 200, EXPECTED_NAMESPACE_VIEW_1.to_dict()) + + +def test_api_nem_namespace_non_exist(client): # pylint: disable=redefined-outer-name + _assert_get_api_nem_namespace_by_name(client, 'non_exist_namespace', 404, { + 'message': 'Resource not found', + 'status': 404 + }) + +# endregion + + +# region /namespaces + +def _get_api_nem_namespaces(client, **query_params): # pylint: disable=redefined-outer-name + query_string = '&'.join(f'{key}={val}' for key, val in query_params.items()) + return client.get(f'/api/nem/namespaces?{query_string}') + + +def _assert_get_api_nem_namespaces(client, expected_code, expected_result, **query_params): # pylint: disable=redefined-outer-name + # Act: + response = _get_api_nem_namespaces(client, **query_params) + + # Assert: + assert expected_code == response.status_code + assert expected_result == response.json + + +def test_api_nem_namespaces_without_params(client): # pylint: disable=redefined-outer-name, invalid-name + # Act: + response = _get_api_nem_namespaces(client) + + # Assert: + assert 200 == response.status_code + assert [EXPECTED_NAMESPACE_VIEW_2.to_dict(), EXPECTED_NAMESPACE_VIEW_1.to_dict()] == response.json + + +def test_api_nem_namespaces_applies_limit(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_2.to_dict()], limit=1) + + +def test_api_nem_namespaces_applies_offset(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_1.to_dict()], offset=1) + + +def test_api_nem_namespaces_applies_sorted_by_id_desc(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_2.to_dict(), EXPECTED_NAMESPACE_VIEW_1.to_dict()], sort='desc') + + +def test_api_nem_namespaces_applies_sorted_by_id_asc(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_1.to_dict(), EXPECTED_NAMESPACE_VIEW_2.to_dict()], sort='asc') + + +def test_api_nem_namespaces_with_all_params(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_2.to_dict()], limit=1, offset=1, sort='asc') + + +def _assert_get_api_nem_namespaces_fail(client, **query_params): # pylint: disable=redefined-outer-name + # Act: + response = _get_api_nem_namespaces(client, **query_params) + + # Assert: + assert 400 == response.status_code + assert { + 'message': 'Bad request', + 'status': 400 + } == response.json + + +def test_api_nem_namespaces_invalid_limit(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces_fail(client, limit=-1) + _assert_get_api_nem_namespaces_fail(client, limit='invalid') + + +def test_api_nem_namespaces_invalid_offset(client): # pylint: disable=redefined-outer-name, invalid-name + _assert_get_api_nem_namespaces_fail(client, offset=-1) + _assert_get_api_nem_namespaces_fail(client, offset='invalid') + + +def test_api_nem_namespaces_invalid_sort(client): # pylint: disable=redefined-outer-name + _assert_get_api_nem_namespaces_fail(client, sort=-1) + _assert_get_api_nem_namespaces_fail(client, sort='invalid') + + +# endregion From 3b687e4009952bdc6f4c8ce170420ebee59904fb Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 8 Nov 2023 01:12:20 +0800 Subject: [PATCH 13/16] [explorer/rest]: added database namespace unit test --- explorer/rest/tests/db/test_NemDatabase.py | 58 +++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/explorer/rest/tests/db/test_NemDatabase.py b/explorer/rest/tests/db/test_NemDatabase.py index 4e5b0569c..8887e9a01 100644 --- a/explorer/rest/tests/db/test_NemDatabase.py +++ b/explorer/rest/tests/db/test_NemDatabase.py @@ -2,7 +2,7 @@ from rest.db.NemDatabase import NemDatabase -from ..test.DatabaseTestUtils import BLOCK_VIEWS, DatabaseTestBase +from ..test.DatabaseTestUtils import BLOCK_VIEWS, NAMESPACE_VIEWS, DatabaseTestBase BlockQueryParams = namedtuple('BlockQueryParams', ['limit', 'offset', 'min_height', 'sort']) PaginationQueryParams = namedtuple('PaginationQueryParams', ['limit', 'offset', 'sort']) @@ -13,11 +13,17 @@ EXPECTED_BLOCK_VIEW_2 = BLOCK_VIEWS[1] +EXPECTED_NAMESPACE_VIEW_1 = NAMESPACE_VIEWS[0] + +EXPECTED_NAMESPACE_VIEW_2 = NAMESPACE_VIEWS[1] + # endregion class NemDatabaseTest(DatabaseTestBase): + # region block tests + def _assert_can_query_block_by_height(self, height, expected_block): # Arrange: nem_db = NemDatabase(self.db_config, self.network_name) @@ -67,3 +73,53 @@ def test_can_query_blocks_sorted_by_height_asc(self): def test_can_query_blocks_sorted_by_height_desc(self): self._assert_can_query_blocks_with_filter(BlockQueryParams(10, 0, 0, 'desc'), [EXPECTED_BLOCK_VIEW_2, EXPECTED_BLOCK_VIEW_1]) + + # endregion + + # region namespace tests + + def _assert_can_query_namespace_by_name(self, name, expected_namespace): + # Arrange: + nem_db = NemDatabase(self.db_config, self.network) + + # Act: + namespace_view = nem_db.get_namespace(name) + + # Assert: + self.assertEqual(expected_namespace, namespace_view) + + def _assert_can_query_namespaces_with_filter(self, query_params, expected_namespaces): + # Arrange: + nem_db = NemDatabase(self.db_config, self.network) + + # Act: + namespaces_view = nem_db.get_namespaces(query_params.limit, query_params.offset, query_params.sort) + + # Assert: + self.assertEqual(expected_namespaces, namespaces_view) + + def test_can_query_namespace_by_name(self): + self._assert_can_query_namespace_by_name('oxford', EXPECTED_NAMESPACE_VIEW_1) + + def test_cannot_query_nonexistent_namespace(self): + self._assert_can_query_namespace_by_name('non_exist', None) + + def test_can_query_namespaces_filtered_limit(self): + self._assert_can_query_namespaces_with_filter(PaginationQueryParams(1, 0, 'desc'), [EXPECTED_NAMESPACE_VIEW_2]) + + def test_can_query_namespaces_filtered_offset_0(self): + self._assert_can_query_namespaces_with_filter(PaginationQueryParams(1, 0, 'desc'), [EXPECTED_NAMESPACE_VIEW_2]) + + def test_can_query_namespaces_filtered_offset_1(self): + self._assert_can_query_namespaces_with_filter(PaginationQueryParams(1, 1, 'desc'), [EXPECTED_NAMESPACE_VIEW_1]) + + def test_can_query_namespaces_sorted_by_id_asc(self): + self._assert_can_query_namespaces_with_filter(PaginationQueryParams(10, 0, 'asc'), [EXPECTED_NAMESPACE_VIEW_1, EXPECTED_NAMESPACE_VIEW_2]) + + def test_can_query_namespaces_sorted_by_id_desc(self): + self._assert_can_query_namespaces_with_filter( + PaginationQueryParams(10, 0, 'desc'), + [EXPECTED_NAMESPACE_VIEW_2, EXPECTED_NAMESPACE_VIEW_1] + ) + + # endregion From 4f8e0cd7223ae5af7395196c095a5a0d3b76d127 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Fri, 10 Nov 2023 21:40:46 +0800 Subject: [PATCH 14/16] [explorer/rest]: patch rename network to network_name --- explorer/rest/tests/db/test_NemDatabase.py | 4 ++-- explorer/rest/tests/facade/test_NemRestFacade.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/explorer/rest/tests/db/test_NemDatabase.py b/explorer/rest/tests/db/test_NemDatabase.py index 8887e9a01..e313d949e 100644 --- a/explorer/rest/tests/db/test_NemDatabase.py +++ b/explorer/rest/tests/db/test_NemDatabase.py @@ -80,7 +80,7 @@ def test_can_query_blocks_sorted_by_height_desc(self): def _assert_can_query_namespace_by_name(self, name, expected_namespace): # Arrange: - nem_db = NemDatabase(self.db_config, self.network) + nem_db = NemDatabase(self.db_config, self.network_name) # Act: namespace_view = nem_db.get_namespace(name) @@ -90,7 +90,7 @@ def _assert_can_query_namespace_by_name(self, name, expected_namespace): def _assert_can_query_namespaces_with_filter(self, query_params, expected_namespaces): # Arrange: - nem_db = NemDatabase(self.db_config, self.network) + nem_db = NemDatabase(self.db_config, self.network_name) # Act: namespaces_view = nem_db.get_namespaces(query_params.limit, query_params.offset, query_params.sort) diff --git a/explorer/rest/tests/facade/test_NemRestFacade.py b/explorer/rest/tests/facade/test_NemRestFacade.py index dc8d42683..014f35dd3 100644 --- a/explorer/rest/tests/facade/test_NemRestFacade.py +++ b/explorer/rest/tests/facade/test_NemRestFacade.py @@ -70,7 +70,7 @@ def test_blocks_sorted_by_height_desc(self): def _assert_can_retrieve_namespace(self, name, expected_namespace): # Arrange: - nem_rest_facade = NemRestFacade(self.db_config, self.network) + nem_rest_facade = NemRestFacade(self.db_config, self.network_name) # Act: namespace = nem_rest_facade.get_namespace(name) @@ -80,7 +80,7 @@ def _assert_can_retrieve_namespace(self, name, expected_namespace): def _assert_can_retrieve_namespaces(self, query_params, expected_namespaces): # Arrange: - nem_rest_facade = NemRestFacade(self.db_config, self.network) + nem_rest_facade = NemRestFacade(self.db_config, self.network_name) # Act: namespaces = nem_rest_facade.get_namespaces(query_params.limit, query_params.offset, query_params.sort) From dd01dec10e257d3286324bffea4fce9770f983e1 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 11 Nov 2023 00:21:33 +0800 Subject: [PATCH 15/16] [explorer/rest]: removed print message --- explorer/rest/tests/test_rest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/explorer/rest/tests/test_rest.py b/explorer/rest/tests/test_rest.py index 68520b32a..0af8e8b36 100644 --- a/explorer/rest/tests/test_rest.py +++ b/explorer/rest/tests/test_rest.py @@ -199,8 +199,6 @@ def _assert_get_api_nem_namespace_by_name(client, name, expected_status_code, ex # Act: response = client.get(f'/api/nem/namespace/{name}') - print(response.json) - # Assert: assert expected_status_code == response.status_code assert expected_result == response.json From 38cea45db4d1a64650ebbeef0816f3fd6a6327bd Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 11 Nov 2023 00:28:04 +0800 Subject: [PATCH 16/16] [explorer/rest]: improve unit test --- explorer/rest/tests/test_rest.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/explorer/rest/tests/test_rest.py b/explorer/rest/tests/test_rest.py index 0af8e8b36..1c29ed874 100644 --- a/explorer/rest/tests/test_rest.py +++ b/explorer/rest/tests/test_rest.py @@ -200,7 +200,7 @@ def _assert_get_api_nem_namespace_by_name(client, name, expected_status_code, ex response = client.get(f'/api/nem/namespace/{name}') # Assert: - assert expected_status_code == response.status_code + _assert_status_code_and_headers(response, expected_status_code) assert expected_result == response.json @@ -224,12 +224,12 @@ def _get_api_nem_namespaces(client, **query_params): # pylint: disable=redefine return client.get(f'/api/nem/namespaces?{query_string}') -def _assert_get_api_nem_namespaces(client, expected_code, expected_result, **query_params): # pylint: disable=redefined-outer-name +def _assert_get_api_nem_namespaces(client, expected_status_code, expected_result, **query_params): # pylint: disable=redefined-outer-name # Act: response = _get_api_nem_namespaces(client, **query_params) # Assert: - assert expected_code == response.status_code + _assert_status_code_and_headers(response, expected_status_code) assert expected_result == response.json @@ -238,7 +238,7 @@ def test_api_nem_namespaces_without_params(client): # pylint: disable=redefined response = _get_api_nem_namespaces(client) # Assert: - assert 200 == response.status_code + _assert_status_code_and_headers(response, 200) assert [EXPECTED_NAMESPACE_VIEW_2.to_dict(), EXPECTED_NAMESPACE_VIEW_1.to_dict()] == response.json @@ -262,31 +262,31 @@ def test_api_nem_namespaces_with_all_params(client): # pylint: disable=redefine _assert_get_api_nem_namespaces(client, 200, [EXPECTED_NAMESPACE_VIEW_2.to_dict()], limit=1, offset=1, sort='asc') -def _assert_get_api_nem_namespaces_fail(client, **query_params): # pylint: disable=redefined-outer-name +def _assert_get_api_nem_namespaces_fail(client, expected_status_code, **query_params): # pylint: disable=redefined-outer-name # Act: response = _get_api_nem_namespaces(client, **query_params) # Assert: - assert 400 == response.status_code + _assert_status_code_and_headers(response, expected_status_code) assert { 'message': 'Bad request', - 'status': 400 + 'status': expected_status_code } == response.json def test_api_nem_namespaces_invalid_limit(client): # pylint: disable=redefined-outer-name, invalid-name - _assert_get_api_nem_namespaces_fail(client, limit=-1) - _assert_get_api_nem_namespaces_fail(client, limit='invalid') + _assert_get_api_nem_namespaces_fail(client, 400, limit=-1) + _assert_get_api_nem_namespaces_fail(client, 400, limit='invalid') def test_api_nem_namespaces_invalid_offset(client): # pylint: disable=redefined-outer-name, invalid-name - _assert_get_api_nem_namespaces_fail(client, offset=-1) - _assert_get_api_nem_namespaces_fail(client, offset='invalid') + _assert_get_api_nem_namespaces_fail(client, 400, offset=-1) + _assert_get_api_nem_namespaces_fail(client, 400, offset='invalid') def test_api_nem_namespaces_invalid_sort(client): # pylint: disable=redefined-outer-name - _assert_get_api_nem_namespaces_fail(client, sort=-1) - _assert_get_api_nem_namespaces_fail(client, sort='invalid') + _assert_get_api_nem_namespaces_fail(client, 400, sort=-1) + _assert_get_api_nem_namespaces_fail(client, 400, sort='invalid') # endregion