diff --git a/swh/graphql/resolvers/snapshot_branch.py b/swh/graphql/resolvers/snapshot_branch.py --- a/swh/graphql/resolvers/snapshot_branch.py +++ b/swh/graphql/resolvers/snapshot_branch.py @@ -3,9 +3,9 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from collections import namedtuple +from typing import Optional -from swh.graphql.errors import ObjectNotFoundError +from swh.graphql.errors import DataError from swh.graphql.utils import utils from swh.storage.interface import PagedResult @@ -18,17 +18,19 @@ # target field for this node is a UNION type # It is resolved in the top level (resolvers.resolvers.py) - def _get_node_from_data(self, node_data: tuple): - # node_data is a tuple as returned by _get_paged_result in - # SnapshotBranchConnection and _get_node_data in AliasSnapshotBranchNode - # overriding to support this special data structure - branch_name, branch_obj = node_data - node = { - "name": branch_name, - "type": branch_obj.target_type.value, - "target_hash": branch_obj.target, - } - return namedtuple("NodeObj", node.keys())(*node.values()) + def _get_node_from_data(self, node_data: Optional[tuple]): + # node_data is received as a tuple from the archive + # (unlike an object or a dict in other cases) + # overriding this method to make a dict from this special data structure (tuple) + structured_node_data = None + if node_data is not None: + branch_name, branch_obj = node_data + structured_node_data = { + "name": branch_name, + "type": branch_obj.target_type.value, + "target_hash": branch_obj.target, + } + return super()._get_node_from_data(structured_node_data) def is_type_of(self): return "Branch" @@ -56,11 +58,15 @@ return parent.swhid parent = parent.obj # Reached the root query node. This will never happen with the current entrypoints - raise ObjectNotFoundError("There is no snapshot associated with the branch") + # raise a DataError in this case + raise DataError( + f"Missing snapshot for the branch {self.target_hash}, last known parent is {parent}" + ) class AliasSnapshotBranchNode(BaseSnapshotBranchNode): + _can_be_null = True obj: BaseSnapshotBranchNode def _get_node_data(self): @@ -71,10 +77,9 @@ snapshot_swhid.object_id, first=1, name_include=target_branch ) if target_branch not in alias_branch["branches"]: - raise ObjectNotFoundError( - f"Branch name with {target_branch.decode()} is not available" - ) - # this will be serialized in _get_node_from_data method in the base class + # FIXME, alias branch is missing, log this event + return None + # this will be converted to a dict in _get_node_from_data method in the base class return (target_branch, alias_branch["branches"][target_branch]) @@ -106,7 +111,7 @@ # FIX in swh-storage to return PagedResult # STORAGE-TODO - # this will be serialized in _get_node_from_data method in the node class + # this will be converted to a dict in _get_node_from_data method in the base class return PagedResult( results=result["branches"].items(), next_page_token=end_cusrsor ) diff --git a/swh/graphql/tests/data.py b/swh/graphql/tests/data.py --- a/swh/graphql/tests/data.py +++ b/swh/graphql/tests/data.py @@ -13,6 +13,9 @@ Release, Revision, RevisionType, + Snapshot, + SnapshotBranch, + TargetType, ) from swh.model.tests import swh_model_data @@ -172,7 +175,20 @@ ] +def get_snapshots_with_missing_alias(): + return [ + Snapshot( + branches={ + b"target/missing-alias": SnapshotBranch( + target_type=TargetType.ALIAS, target=b"target/revision" + ), + }, + ), + ] + + GRAPHQL_EXTRA_TEST_OBJECTS = { + "snapshot": get_snapshots_with_missing_alias(), "release": get_releases_with_target(), "revision": get_revisions_with_parents() + get_revisions_with_none_date(), "directory": get_directories_with_nested_path() diff --git a/swh/graphql/tests/functional/test_branch_connection.py b/swh/graphql/tests/functional/test_branch_connection.py --- a/swh/graphql/tests/functional/test_branch_connection.py +++ b/swh/graphql/tests/functional/test_branch_connection.py @@ -6,6 +6,7 @@ import pytest from . import utils +from ..data import get_snapshots_with_missing_alias def get_branches(client, **kwargs) -> tuple: @@ -81,6 +82,20 @@ } +@pytest.mark.parametrize("snapshot", get_snapshots_with_missing_alias()) +def test_get_branches_with_missing_alias(client, snapshot): + data, errors = get_branches( + client, swhid=str(snapshot.swhid()), first=1, types=["alias"] + ) + node = data["snapshot"]["branches"]["nodes"][0] + assert errors is None + assert node == { + "name": {"text": "target/missing-alias"}, + "target": None, + "targetType": "alias", + } + + @pytest.mark.parametrize( "filter_type, count, target_type, swhid_pattern", [