diff --git a/swh/graphql/resolvers/directory_entry.py b/swh/graphql/resolvers/directory_entry.py index cddff3d..777f8c1 100644 --- a/swh/graphql/resolvers/directory_entry.py +++ b/swh/graphql/resolvers/directory_entry.py @@ -1,63 +1,63 @@ # Copyright (C) 2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from swh.graphql.utils import utils from swh.storage.interface import PagedResult from .base_connection import BaseConnection from .base_node import BaseNode class BaseDirectoryEntryNode(BaseNode): @property def target_hash(self): # for DirectoryNode return self._node.target @property def targetType(self): # To support the schema naming convention mapping = {"file": "content", "dir": "directory", "rev": "revision"} return mapping.get(self._node.type) class DirectoryEntryNode(BaseDirectoryEntryNode): """ Node resolver for a directory entry requested with a directory SWHID and a relative path """ def _get_node_data(self): # STORAGE-TODO, archive is returning a dict # return DirectoryEntry object instead return self.archive.get_directory_entry_by_path( - directory_id=self.kwargs.get("directorySwhid").object_id, + directory_id=self.kwargs.get("directorySWHID").object_id, path=self.kwargs.get("path"), ) class DirectoryEntryConnection(BaseConnection): """ Connection resolver for entries in a directory """ from .directory import BaseDirectoryNode obj: BaseDirectoryNode _node_class = BaseDirectoryEntryNode def _get_paged_result(self) -> PagedResult: # FIXME, using dummy(local) pagination, move pagination to backend # To remove localpagination, just drop the paginated call # STORAGE-TODO entries = self.archive.get_directory_entries(self.obj.swhid.object_id).results name_include = self.kwargs.get("nameInclude") if name_include is not None: # STORAGE-TODO, move this filter to swh-storage entries = [ x for x in entries if name_include.casefold() in x.name.decode().casefold() ] return utils.paginated(entries, self._get_first_arg(), self._get_after_arg()) diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py index 80087df..b8cbb11 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,314 +1,314 @@ # Copyright (C) 2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information """ High level resolvers """ # Any schema attribute can be resolved by any of the following ways # and in the following priority order # - In this module using a decorator (eg: @visit_status.field("snapshot")) # Every object (type) is expected to resolve this way as they can accept arguments # eg: origin.visits takes arguments to paginate # - As a property in the Node object (eg: resolvers.visit.BaseVisitNode.id) # Every scalar is expected to resolve this way # - As an attribute/item in the object/dict returned by a backend (eg: Origin.url) from typing import Optional, Union from ariadne import ObjectType, UnionType from graphql.type import GraphQLResolveInfo from swh.graphql import resolvers as rs from swh.graphql.utils import utils from .resolver_factory import ConnectionObjectFactory, NodeObjectFactory query: ObjectType = ObjectType("Query") origin: ObjectType = ObjectType("Origin") visit: ObjectType = ObjectType("Visit") visit_status: ObjectType = ObjectType("VisitStatus") snapshot: ObjectType = ObjectType("Snapshot") snapshot_branch: ObjectType = ObjectType("Branch") revision: ObjectType = ObjectType("Revision") release: ObjectType = ObjectType("Release") directory: ObjectType = ObjectType("Directory") directory_entry: ObjectType = ObjectType("DirectoryEntry") search_result: ObjectType = ObjectType("SearchResult") binary_string: ObjectType = ObjectType("BinaryString") branch_target: UnionType = UnionType("BranchTarget") release_target: UnionType = UnionType("ReleaseTarget") directory_entry_target: UnionType = UnionType("DirectoryEntryTarget") search_result_target: UnionType = UnionType("SearchResultTarget") # Node resolvers # A node resolver will return either an instance of a BaseNode subclass or None @query.field("origin") def origin_resolver(obj: None, info: GraphQLResolveInfo, **kw) -> rs.origin.OriginNode: return NodeObjectFactory.create("origin", obj, info, **kw) @origin.field("latestVisit") def latest_visit_resolver( obj: rs.origin.BaseOriginNode, info: GraphQLResolveInfo, **kw ) -> Optional[rs.visit.LatestVisitNode]: return NodeObjectFactory.create("latest-visit", obj, info, **kw) @query.field("visit") def visit_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.visit.OriginVisitNode: return NodeObjectFactory.create("visit", obj, info, **kw) @visit.field("latestStatus") def latest_visit_status_resolver( obj: rs.visit.BaseVisitNode, info: GraphQLResolveInfo, **kw ) -> Optional[rs.visit_status.LatestVisitStatusNode]: return NodeObjectFactory.create("latest-status", obj, info, **kw) @query.field("snapshot") def snapshot_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.snapshot.SnapshotNode: return NodeObjectFactory.create("snapshot", obj, info, **kw) @visit_status.field("snapshot") def visit_snapshot_resolver( obj: rs.visit_status.BaseVisitStatusNode, info: GraphQLResolveInfo, **kw ) -> Optional[rs.snapshot.VisitSnapshotNode]: return NodeObjectFactory.create("visit-snapshot", obj, info, **kw) @snapshot_branch.field("target") def snapshot_branch_target_resolver( obj: rs.snapshot_branch.BaseSnapshotBranchNode, info: GraphQLResolveInfo, **kw ) -> Union[ rs.revision.BaseRevisionNode, rs.release.BaseReleaseNode, rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, rs.snapshot.BaseSnapshotNode, rs.snapshot_branch.BaseSnapshotBranchNode, ]: """ Snapshot branch target can be a revision, release, directory, content, snapshot or a branch itself (alias type) """ return NodeObjectFactory.create(f"branch-{obj.targetType}", obj, info, **kw) @query.field("revision") def revision_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.revision.RevisionNode: return NodeObjectFactory.create("revision", obj, info, **kw) @revision.field("directory") def revision_directory_resolver( obj: rs.revision.BaseRevisionNode, info: GraphQLResolveInfo, **kw ) -> Optional[rs.directory.RevisionDirectoryNode]: return NodeObjectFactory.create("revision-directory", obj, info, **kw) @query.field("release") def release_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.release.ReleaseNode: return NodeObjectFactory.create("release", obj, info, **kw) @release.field("target") def release_target_resolver( obj: rs.release.BaseReleaseNode, info: GraphQLResolveInfo, **kw ) -> Union[ rs.revision.BaseRevisionNode, rs.release.BaseReleaseNode, rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, ]: """ Release target can be a release, revision, directory or a content """ return NodeObjectFactory.create(f"release-{obj.targetType}", obj, info, **kw) @query.field("directory") def directory_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.directory.DirectoryNode: return NodeObjectFactory.create("directory", obj, info, **kw) @query.field("directoryEntry") def directory_entry_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.directory_entry.DirectoryEntryNode: return NodeObjectFactory.create("directory-entry", obj, info, **kw) @directory_entry.field("target") def directory_entry_target_resolver( obj: rs.directory_entry.BaseDirectoryEntryNode, info: GraphQLResolveInfo, **kw ) -> Union[ rs.revision.BaseRevisionNode, rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, ]: """ DirectoryEntry target can be a directory, content or a revision """ return NodeObjectFactory.create(f"dir-entry-{obj.targetType}", obj, info, **kw) @query.field("content") def content_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.content.ContentNode: return NodeObjectFactory.create("content", obj, info, **kw) @search_result.field("target") def search_result_target_resolver( obj: rs.search.SearchResultNode, info: GraphQLResolveInfo, **kw ) -> Union[ rs.origin.BaseOriginNode, rs.snapshot.BaseSnapshotNode, rs.revision.BaseRevisionNode, rs.release.BaseReleaseNode, rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, ]: """ SearchResult target can be an origin, snapshot, revision, release directory or a content """ return NodeObjectFactory.create(f"search-result-{obj.targetType}", obj, info, **kw) @query.field("contentByHash") def content_by_hash_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.content.ContentNode: return NodeObjectFactory.create("content-by-hash", obj, info, **kw) # Connection resolvers # A connection resolver should return an instance of BaseConnection @query.field("origins") def origins_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.origin.OriginConnection: return ConnectionObjectFactory.create("origins", obj, info, **kw) @origin.field("visits") def visits_resolver( obj: rs.origin.BaseOriginNode, info: GraphQLResolveInfo, **kw ) -> rs.visit.OriginVisitConnection: return ConnectionObjectFactory.create("origin-visits", obj, info, **kw) @origin.field("snapshots") def origin_snapshots_resolver( obj: rs.origin.BaseOriginNode, info: GraphQLResolveInfo, **kw ) -> rs.snapshot.OriginSnapshotConnection: return ConnectionObjectFactory.create("origin-snapshots", obj, info, **kw) @visit.field("statuses") def visitstatus_resolver( obj: rs.visit.BaseVisitNode, info: GraphQLResolveInfo, **kw ) -> rs.visit_status.VisitStatusConnection: return ConnectionObjectFactory.create("visit-status", obj, info, **kw) @snapshot.field("branches") def snapshot_branches_resolver( obj: rs.snapshot.BaseSnapshotNode, info: GraphQLResolveInfo, **kw ) -> rs.snapshot_branch.SnapshotBranchConnection: return ConnectionObjectFactory.create("snapshot-branches", obj, info, **kw) @revision.field("parents") def revision_parents_resolver( obj: rs.revision.BaseRevisionNode, info: GraphQLResolveInfo, **kw ) -> rs.revision.ParentRevisionConnection: return ConnectionObjectFactory.create("revision-parents", obj, info, **kw) @revision.field("revisionLog") def revision_log_resolver( obj: rs.revision.BaseRevisionNode, info: GraphQLResolveInfo, **kw ) -> rs.revision.LogRevisionConnection: return ConnectionObjectFactory.create("revision-log", obj, info, **kw) @directory.field("entries") def directory_entries_resolver( obj: rs.directory.BaseDirectoryNode, info: GraphQLResolveInfo, **kw ) -> rs.directory_entry.DirectoryEntryConnection: return ConnectionObjectFactory.create("directory-entries", obj, info, **kw) -@query.field("resolveSwhid") +@query.field("resolveSWHID") def search_swhid_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.search.ResolveSwhidConnection: return ConnectionObjectFactory.create("resolve-swhid", obj, info, **kw) @query.field("search") def search_resolver( obj: None, info: GraphQLResolveInfo, **kw ) -> rs.search.SearchConnection: return ConnectionObjectFactory.create("search", obj, info, **kw) # Other resolvers @release_target.type_resolver @directory_entry_target.type_resolver @branch_target.type_resolver @search_result_target.type_resolver def union_resolver( obj: Union[ rs.origin.BaseOriginNode, rs.revision.BaseRevisionNode, rs.release.BaseReleaseNode, rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, rs.snapshot.BaseSnapshotNode, rs.snapshot_branch.BaseSnapshotBranchNode, ], *_, ) -> str: """ Generic resolver for all the union types """ return obj.is_type_of() # BinaryString resolvers @binary_string.field("text") def binary_string_text_resolver(obj: bytes, *args, **kw) -> str: return obj.decode(utils.ENCODING, "replace") @binary_string.field("base64") def binary_string_base64_resolver(obj: bytes, *args, **kw) -> str: return utils.get_b64_string(obj) diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql index 1d08c65..f1d8b7b 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,1127 +1,1127 @@ """ SoftWare Heritage persistent Identifier """ scalar SWHID """ ISO-8601 encoded date string """ scalar DateTime """ Content identifier in the form hash-type:hash-value """ scalar ContentHash """ Object with an id """ interface Node { """ Id of the object. This is for caching purpose and should not be used outside the GraphQL API """ id: ID! } """ SWH merkle node object with a SWHID """ interface MerkleNode { """ SWHID of the object """ swhid: SWHID! } """ Information about pagination """ type PageInfo { """ Cursor to request the next page in the connection """ endCursor: String """ Are there more pages in the connection? """ hasNextPage: Boolean! } """ Binary string with multiple encodings """ type BinaryString { """ Utf-8 encoded value, any non Unicode character will be replaced """ text: String! """ base64 encoded value """ base64: String! } """ Connection to origins """ type OriginConnection { """ List of origin edges """ edges: [OriginEdge] """ List of origin objects """ nodes: [Origin] """ Information for pagination """ pageInfo: PageInfo! """ Total number of origin objects in the connection """ totalCount: Int } """ Edge in origin connection """ type OriginEdge { """ Cursor to request the next page after the item """ cursor: String! """ Origin object """ node: Origin } """ A software origin object """ type Origin implements Node { """ Unique identifier """ id: ID! """ Origin URL """ url: String! """ Connection to all the visit objects for the origin """ visits( """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after this cursor """ after: String ): VisitConnection """ Latest visit object for the origin """ latestVisit( """ Return the latest visit with the given visit type """ visitType: String """ Return the latest visit with any of the given statuses """ allowedStatuses: [VisitStatusState] """ If True, the latest visit with a snapshot will be returned """ requireSnapshot: Boolean ): Visit """ Connection to all the snapshots for the origin """ snapshots( """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after this cursor """ after: String ): SnapshotConnection } """ Connection to origin visits """ type VisitConnection { """ List of visit edges """ edges: [VisitEdge] """ List of visit objects """ nodes: [Visit] """ Information for pagination """ pageInfo: PageInfo! """ Total number of visit objects in the connection """ totalCount: Int } """ Edge in origin visit connection """ type VisitEdge { """ Cursor to request the next page after the item """ cursor: String! """ Visit object """ node: Visit } """ Possible visit status states """ enum VisitStatusState { created ongoing partial full not_found failed } """ An origin visit object """ type Visit implements Node { """ Unique identifier """ id: ID! """ Visit number for the origin """ visitId: Int """ Visit date ISO-8601 encoded """ date: DateTime! """ Type of the origin visited. Eg: git/hg/svn/tar/deb """ type: String """ Connection to all the status objects for the visit """ statuses( """ Returns the first _n_ elements from the list """ first: Int """ Returns the page after this cursor """ after: String ): VisitStatusConnection """ Latest status object for the Visit """ latestStatus( """ Filter by status state """ allowedStatuses: [VisitStatusState] """ Filter by the availability of a snapshot in the status """ requireSnapshot: Boolean ): VisitStatus } """ Connection to visit status """ type VisitStatusConnection { """ List of visit status edges """ edges: [VisitStatusEdge] """ List of visit status objects """ nodes: [VisitStatus] """ Information for pagination """ pageInfo: PageInfo! """ Total number of visit status objects in the connection """ totalCount: Int } """ Edge in visit status connection """ type VisitStatusEdge { """ Cursor to request the next page after the item """ cursor: String! """ Visit status object """ node: VisitStatus } """ A visit status object """ type VisitStatus { """ Status string of the visit (either full, partial or ongoing) """ status: VisitStatusState! """ ISO-8601 encoded date string """ date: DateTime! """ Snapshot object """ snapshot: Snapshot """ Type of the origin visited. Eg: git/hg/svn/tar/deb """ type: String } """ Connection to snapshots """ type SnapshotConnection { """ List of snapshot edges """ edges: [SnapshotEdge] """ List of snapshot objects """ nodes: [Snapshot] """ Information for pagination """ pageInfo: PageInfo! """ Total number of snapshot objects in the connection """ totalCount: Int } """ Edge in snapshot connection """ type SnapshotEdge { """ Cursor to request the next page after the item """ cursor: String! """ Snapshot object """ node: Snapshot } """ A snapshot object """ type Snapshot implements MerkleNode & Node { """ Unique identifier """ id: ID! """ SWHID of the snapshot object """ swhid: SWHID! """ Connection to all the snapshot branches """ branches( """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after this cursor """ after: String """ Filter by branch target types """ types: [BranchTargetType] """ Return branches whose name contains the given substring """ nameInclude: String """ Do not return branches whose name contains the given prefix """ nameExcludePrefix: String ): BranchConnection } """ Connection to snapshot branches """ type BranchConnection { """ List of branch edges """ edges: [BranchConnectionEdge] """ List of branch objects """ nodes: [Branch] """ Information for pagination """ pageInfo: PageInfo! """ Total number of branch objects in the connection """ totalCount: Int } """ Edge in snapshot branch connection """ type BranchConnectionEdge { """ Cursor to request the next page after the item """ cursor: String! """ Branch object """ node: Branch } """ A user object """ type Person { """ User's email address """ email: BinaryString """ User's name """ name: BinaryString """ User's full name """ fullname: BinaryString } """ Possible branch target objects """ union BranchTarget = Revision | Release | Branch | Content | Directory | Snapshot """ Possible Branch target types """ enum BranchTargetType { revision release alias content directory snapshot } """ A snapshot branch object """ type Branch { """ Branch name """ name: BinaryString """ Type of Branch target """ targetType: BranchTargetType """ Branch target object """ target: BranchTarget } """ Connection to revisions """ type RevisionConnection { """ List of revision edges """ edges: [RevisionEdge] """ List of revision objects """ nodes: [Revision] """ Information for pagination """ pageInfo: PageInfo! """ Total number of revision objects in the connection """ totalCount: Int } """ Edge in revision connection """ type RevisionEdge { """ Cursor to request the next page after the item """ cursor: String! """ Revision object """ node: Revision } """ A revision object """ type Revision implements MerkleNode & Node { """ Unique identifier """ id: ID! """ SWHID of the revision object """ swhid: SWHID! """ Message associated to the revision """ message: BinaryString """ Revision author """ author: Person """ Revision committer """ committer: Person """ Revision date ISO-8601 encoded """ date: DateTime """ Type of the revision, eg: git/hg """ type: String """ The unique directory object that revision points to """ directory: Directory """ Connection to all the parents of the revision """ parents( """ Returns the first _n_ elements from the list """ first: Int """ Returns the page after this cursor """ after: String ): RevisionConnection """ Connection to all the revisions heading to this one aka the commit log """ revisionLog( """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after the cursor """ after: String ): RevisionConnection } """ Possible release target objects """ union ReleaseTarget = Release | Revision | Directory | Content """ Possible release target types """ enum ReleaseTargetType { release revision content directory } """ A release object """ type Release implements MerkleNode & Node { """ Unique identifier """ id: ID! """ SWHID of the release object """ swhid: SWHID! """ The name of the release """ name: BinaryString """ The message associated to the release """ message: BinaryString """ Release author """ author: Person """ Release date ISO-8601 encoded """ date: DateTime """ Type of release target """ targetType: ReleaseTargetType """ Release target object """ target: ReleaseTarget } """ Connection to directory entries """ type DirectoryEntryConnection { """ List of directory entry edges """ edges: [DirectoryEntryEdge] """ List of directory entry objects """ nodes: [DirectoryEntry] """ Information for pagination """ pageInfo: PageInfo! """ Total number of directory entry objects in the connection """ totalCount: Int } """ Edge in directory entry connection """ type DirectoryEntryEdge { """ Cursor to request the next page after the item """ cursor: String! """ Directory entry object """ node: DirectoryEntry } """ Possible directory entry target objects """ union DirectoryEntryTarget = Directory | Content | Revision """ Possible directory entry types """ enum DirectoryEntryTargetType { directory content revision } """ A directory entry object """ type DirectoryEntry { """ The directory entry name """ name: BinaryString """ Directory entry object type; can be file, dir or rev """ targetType: DirectoryEntryTargetType """ Directory entry target object """ target: DirectoryEntryTarget } """ A directory object """ type Directory implements MerkleNode & Node { """ Unique identifier """ id: ID! """ SWHID of the directory object """ swhid: SWHID! """ Connection to the directory entries """ entries( """ Returns the first _n_ elements from the list """ first: Int """ Returns the page after this cursor """ after: String """ Filter by entry name """ nameInclude: String ): DirectoryEntryConnection } """ An object with different content checksums """ type ContentChecksum { blake2s256: String sha1: String sha1_git: String sha256: String } """ Object with different content data representations """ type ContentData { """ URL to download the file data """ url: String } type ContentFileType { """ Detected content encoding """ encoding: String """ Detected MIME type of the content """ mimetype: String } type ContentLanguage { """ Detected programming language if any """ lang: String } type ContentLicense { """ Array of strings containing the detected license names """ licenses: [String] } """ A content object """ type Content implements MerkleNode & Node { """ Unique identifier """ id: ID! """ SWHID of the content object """ swhid: SWHID! """ Checksums for the content """ checksum: ContentChecksum """ Length of the content in bytes """ length: Int """ Content status, visible or hidden """ status: String """ File content """ data: ContentData """ Information about the content MIME type """ fileType: ContentFileType """ Information about the programming language used in the content """ language: ContentLanguage """ Information about the license of the content """ license: ContentLicense } """ Connection to SearchResults """ type SearchResultConnection { """ List of SearchResult edges """ edges: [SearchResultEdge] """ List of SearchResult objects """ nodes: [SearchResult] """ Information for pagination """ pageInfo: PageInfo! """ Total number of result objects in the connection """ totalCount: Int } """ Edge in SearchResult connection """ type SearchResultEdge { """ Cursor to request the next page after the item """ cursor: String! """ SearchResult object """ node: SearchResult } union SearchResultTarget = Origin | Revision | Release | Content | Directory | Snapshot enum SearchResultTargetType { origin revision release content directory snapshot } """ A SearchResult object """ type SearchResult { """ Result target type """ targetType: SearchResultTargetType """ Result target object """ target: SearchResultTarget } """ The query root of the GraphQL interface. """ type Query { """ Get an origin with its url """ origin( """ URL of the Origin """ url: String! ): Origin """ Get a Connection to all the origins """ origins( """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after the cursor """ after: String """ Filter origins with a URL pattern """ urlPattern: String ): OriginConnection """ Get the visit object with an origin URL and a visit id """ visit( """ URL of the origin """ originUrl: String! """ Visit id to get """ visitId: Int! ): Visit """ Get the snapshot with a SWHID """ snapshot( """ SWHID of the snapshot object """ swhid: SWHID! ): Snapshot """ Get the revision with a SWHID """ revision( """ SWHID of the revision object """ swhid: SWHID! ): Revision """ Get the release with a SWHID """ release( """ SWHID of the release object """ swhid: SWHID! ): Release """ Get the directory with a SWHID """ directory( """ SWHID of the directory object """ swhid: SWHID! ): Directory """ Get a directory entry with directory SWHID and a path """ directoryEntry( """ SWHID of the directory object """ - directorySwhid: SWHID! + directorySWHID: SWHID! """ Relative path to the requested object """ path: String! ): DirectoryEntry """ Get the content with a SWHID """ content( """ SWHID of the content object """ swhid: SWHID! ): Content """ Get the content by one or more hashes Use multiple hashes for an accurate result """ contentByHash( """ List of hashType:hashValue strings """ checksums: [ContentHash]! ): Content """ Resolve the given SWHID to an object """ - resolveSwhid( + resolveSWHID( """ SWHID to look for """ swhid: SWHID! ): SearchResultConnection """ Search in SWH """ search( """ String to search for """ query: String! """ Returns the first _n_ elements from the list """ first: Int! """ Returns the page after the cursor """ after: String ): SearchResultConnection } diff --git a/swh/graphql/tests/functional/test_directory_entry.py b/swh/graphql/tests/functional/test_directory_entry.py index 7498282..fced60e 100644 --- a/swh/graphql/tests/functional/test_directory_entry.py +++ b/swh/graphql/tests/functional/test_directory_entry.py @@ -1,188 +1,188 @@ # Copyright (C) 2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import pytest from swh.graphql import server from swh.model.swhids import CoreSWHID, ObjectType from . import utils from ..data import ( get_directories, get_directories_with_nested_path, get_directories_with_special_name_entries, ) def get_target_type(target_type): mapping = {"file": "content", "dir": "directory", "rev": "revision"} return mapping.get(target_type) def test_get_directory_entry_missing_path(client): directory = get_directories()[0] path = "missing" query_str = """ query getDirEntry($swhid: SWHID!, $path: String!) { - directoryEntry(directorySwhid: $swhid, path: $path) { + directoryEntry(directorySWHID: $swhid, path: $path) { name { text } targetType target { ...on Content { swhid } } } } """ utils.assert_missing_object( client, query_str, "directoryEntry", swhid=str(directory.swhid()), path=path, ) @pytest.mark.parametrize( "directory", get_directories() + get_directories_with_nested_path() ) def test_get_directory_entry(client, directory): storage = server.get_storage() query_str = """ query getDirEntry($swhid: SWHID!, $path: String!) { - directoryEntry(directorySwhid: $swhid, path: $path) { + directoryEntry(directorySWHID: $swhid, path: $path) { name { text } targetType target { ...on Content { swhid } ...on Directory { swhid } ...on Revision { swhid } } } } """ for entry in storage.directory_ls(directory.id, recursive=True): data, _ = utils.get_query_response( client, query_str, swhid=str(directory.swhid()), path=entry["name"].decode(), ) swhid = None if entry["type"] == "file" and entry["sha1_git"] is not None: swhid = CoreSWHID( object_type=ObjectType.CONTENT, object_id=entry["sha1_git"] ) elif entry["type"] == "dir" and entry["target"] is not None: swhid = CoreSWHID( object_type=ObjectType.DIRECTORY, object_id=entry["target"] ) elif entry["type"] == "rev" and entry["target"] is not None: swhid = CoreSWHID( object_type=ObjectType.REVISION, object_id=entry["target"] ) assert data["directoryEntry"] == { "name": {"text": entry["name"].decode()}, "target": {"swhid": str(swhid)} if swhid else None, "targetType": get_target_type(entry["type"]), } @pytest.mark.parametrize("directory", get_directories()) def test_get_directory_entry_connection(client, directory): query_str = """ query getDirectory($swhid: SWHID!) { directory(swhid: $swhid) { swhid entries { nodes { targetType name { text } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(directory.swhid())) directory_entries = data["directory"]["entries"]["nodes"] assert len(directory_entries) == len(directory.entries) output = [ {"name": {"text": de.name.decode()}, "targetType": get_target_type(de.type)} for de in directory.entries ] for each_entry in output: assert each_entry in directory_entries @pytest.mark.parametrize("directory", get_directories()) def test_directory_entry_connection_filter_by_name(client, directory): storage = server.get_storage() for dir_entry in storage.directory_ls(directory.id): name_include = dir_entry["name"][:-1].decode() query_str = """ query getDirectory($swhid: SWHID!, $nameInclude: String) { directory(swhid: $swhid) { swhid entries(nameInclude: $nameInclude) { nodes { targetType name { text } } } } } """ data, _ = utils.get_query_response( client, query_str, swhid=str(directory.swhid()), nameInclude=name_include, ) for entry in data["directory"]["entries"]["nodes"]: assert name_include in entry["name"]["text"] assert entry["targetType"] == get_target_type(dir_entry["type"]) def test_directory_entry_connection_filter_by_name_special_chars(client): directory = get_directories_with_special_name_entries()[0] query_str = """ query getDirectory($swhid: SWHID!, $nameInclude: String) { directory(swhid: $swhid) { entries(nameInclude: $nameInclude) { nodes { targetType name { text } } } } } """ data, _ = utils.get_query_response( client, query_str, swhid=str(directory.swhid()), nameInclude="ssSSé", ) assert data["directory"]["entries"]["nodes"][0] == { "name": {"text": "ßßétEÉt"}, "targetType": "content", } diff --git a/swh/graphql/tests/functional/test_swhid_resolve.py b/swh/graphql/tests/functional/test_swhid_resolve.py index 5a6efde..5ca39aa 100644 --- a/swh/graphql/tests/functional/test_swhid_resolve.py +++ b/swh/graphql/tests/functional/test_swhid_resolve.py @@ -1,221 +1,221 @@ # Copyright (C) 2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import pytest from . import utils from ..data import ( get_contents, get_directories, get_releases, get_revisions, get_snapshots, ) def test_invalid_swhid(client): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType } } } """ errors = utils.get_error_response(client, query_str, swhid="swh:1:dir:invalid") # API will throw an error in case of an invalid SWHID assert len(errors) == 1 assert "Input error: Invalid SWHID" in errors[0]["message"] @pytest.mark.parametrize( "swhid", [ "swh:1:rel:0949d7a8c96347dba09be8d79085b8207f345412", "swh:1:rev:0949d7a8c96347dba09be8d79085b8207f345412", "swh:1:dir:0949d7a8c96347dba09be8d79085b8207f345412", "swh:1:cnt:0949d7a8c96347dba09be8d79085b8207f345412", "swh:1:snp:0949d7a8c96347dba09be8d79085b8207f345412", ], ) def test_missing_swhid(client, swhid): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType } } } """ data, _ = utils.get_query_response(client, query_str, swhid=swhid) # API will return an empty list in case of a valid, non existing SWHID - assert data == {"resolveSwhid": {"nodes": []}} + assert data == {"resolveSWHID": {"nodes": []}} @pytest.mark.parametrize("snapshot", get_snapshots()) def test_snapshot_swhid_resolve(client, snapshot): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType target { __typename ... on Snapshot { swhid } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(snapshot.swhid())) assert data == { - "resolveSwhid": { + "resolveSWHID": { "nodes": [ { "target": { "__typename": "Snapshot", "swhid": str(snapshot.swhid()), }, "targetType": "snapshot", } ] } } @pytest.mark.parametrize("revision", get_revisions()) def test_revision_swhid_resolve(client, revision): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType target { __typename ... on Revision { swhid } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(revision.swhid())) assert data == { - "resolveSwhid": { + "resolveSWHID": { "nodes": [ { "target": { "__typename": "Revision", "swhid": str(revision.swhid()), }, "targetType": "revision", } ] } } @pytest.mark.parametrize("release", get_releases()) def test_release_swhid_resolve(client, release): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType target { __typename ... on Release { swhid } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(release.swhid())) assert data == { - "resolveSwhid": { + "resolveSWHID": { "nodes": [ { "target": { "__typename": "Release", "swhid": str(release.swhid()), }, "targetType": "release", } ] } } @pytest.mark.parametrize("directory", get_directories()) def test_directory_swhid_resolve(client, directory): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType target { __typename ... on Directory { swhid } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(directory.swhid())) assert data == { - "resolveSwhid": { + "resolveSWHID": { "nodes": [ { "target": { "__typename": "Directory", "swhid": str(directory.swhid()), }, "targetType": "directory", } ] } } @pytest.mark.parametrize("content", get_contents()) def test_content_swhid_resolve(client, content): query_str = """ query resolve($swhid: SWHID!) { - resolveSwhid(swhid: $swhid) { + resolveSWHID(swhid: $swhid) { nodes { targetType target { __typename ... on Content { swhid } } } } } """ data, _ = utils.get_query_response(client, query_str, swhid=str(content.swhid())) assert data == { - "resolveSwhid": { + "resolveSWHID": { "nodes": [ { "target": { "__typename": "Content", "swhid": str(content.swhid()), }, "targetType": "content", } ] } }