async def _resolve_login( self, name: str) -> typing.List[github_types.GitHubLogin]: if not name: return [] elif not isinstance(name, str): return [github_types.GitHubLogin(name)] elif name[0] != "@": return [github_types.GitHubLogin(name)] if "/" in name: organization, _, team_slug = name.partition("/") if not team_slug or "/" in team_slug: # Not a team slug return [github_types.GitHubLogin(name)] organization = github_types.GitHubLogin(organization[1:]) if organization != self.pull["base"]["repo"]["owner"]["login"]: # TODO(sileht): We don't have the permissions, maybe we should report this return [github_types.GitHubLogin(name)] team_slug = github_types.GitHubTeamSlug(team_slug) else: team_slug = github_types.GitHubTeamSlug(name[1:]) try: return await self.repository.installation.get_team_members( team_slug) except http.HTTPClientSideError as e: self.log.warning( "fail to get the organization, team or members", team=name, status_code=e.status_code, detail=e.message, ) return [github_types.GitHubLogin(name)]
def from_string(cls, value: str) -> "_GitHubTeam": if not value: raise voluptuous.Invalid("A GitHub team cannot be an empty string") # Remove leading @ if any: # This format is accepted in conditions so we're happy to accept it here too. if value[0] == "@": org, sep, team = value[1:].partition("/") else: org, sep, team = value.partition("/") if sep == "" and team == "": # Just a slug team = org final_org = None else: final_org = _check_GitHubLogin_format(org, "organization") if not team: raise voluptuous.Invalid("A GitHub team cannot be an empty string") if ("/" in team or team[0] == "-" or team[-1] == "-" or not team.isascii() or not team.replace("-", "").replace("_", "").isalnum()): raise voluptuous.Invalid("GitHub team contains invalid characters") return cls(github_types.GitHubTeamSlug(team), final_org, value)
async def _resolve_login( ctxt: context.Context, name: str ) -> typing.List[github_types.GitHubLogin]: if not name: return [] elif not isinstance(name, str): return [github_types.GitHubLogin(name)] elif name[0] != "@": return [github_types.GitHubLogin(name)] if "/" in name: organization, _, team_slug = name.partition("/") if not team_slug or "/" in team_slug: raise LiveResolutionFailure(f"Team `{name}` is invalid") organization = github_types.GitHubLogin(organization[1:]) expected_organization = ctxt.pull["base"]["repo"]["owner"]["login"] if organization != expected_organization: raise LiveResolutionFailure( f"Team `{name}` is not part of the organization `{expected_organization}`" ) team_slug = github_types.GitHubTeamSlug(team_slug) else: team_slug = github_types.GitHubTeamSlug(name[1:]) try: return await ctxt.repository.installation.get_team_members(team_slug) except http.HTTPNotFound: raise LiveResolutionFailure(f"Team `{name}` does not exist") except http.HTTPClientSideError as e: ctxt.log.warning( "fail to get the organization, team or members", team=name, status_code=e.status_code, detail=e.message, ) raise LiveResolutionFailure( f"Failed retrieve team `{name}`, details: {e.message}" )
async def test_team_permission_cache(redis_cache: utils.RedisCache) -> None: class FakeClient(github.AsyncGithubInstallationClient): called: int def __init__(self, owner, repo): super().__init__(auth=None) self.owner = owner self.repo = repo self.called = 0 async def get(self, url, *args, **kwargs): self.called += 1 if (url == f"/orgs/{self.owner}/teams/team-ok/repos/{self.owner}/{self.repo}" ): return {} elif (url == f"/orgs/{self.owner}/teams/team-nok/repos/{self.owner}/{self.repo}" ): raise http.HTTPNotFound(message="Not found", request=mock.ANY, response=mock.ANY) elif (url == f"/orgs/{self.owner}/teams/team-also-nok/repos/{self.owner}/{self.repo}" ): raise http.HTTPNotFound(message="Not found", request=mock.ANY, response=mock.ANY) raise ValueError(f"Unknown test URL `{url}`") gh_owner = github_types.GitHubAccount({ "id": github_types.GitHubAccountIdType(123), "login": github_types.GitHubLogin("jd"), "type": "User", "avatar_url": "", }) gh_repo = github_types.GitHubRepository({ "id": github_types.GitHubRepositoryIdType(0), "owner": gh_owner, "full_name": "", "archived": False, "url": "", "default_branch": github_types.GitHubRefType(""), "name": github_types.GitHubRepositoryName("test"), "private": False, }) team_slug1 = github_types.GitHubTeamSlug("team-ok") team_slug2 = github_types.GitHubTeamSlug("team-nok") team_slug3 = github_types.GitHubTeamSlug("team-also-nok") sub = subscription.Subscription(redis_cache, 0, False, "", frozenset()) client = FakeClient(gh_owner["login"], gh_repo["name"]) installation = context.Installation(gh_owner["id"], gh_owner["login"], sub, client, redis_cache) repository = context.Repository(installation, gh_repo["name"], gh_repo["id"]) assert client.called == 0 assert await repository.team_has_read_permission(team_slug1) assert client.called == 1 assert await repository.team_has_read_permission(team_slug1) assert client.called == 1 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 2 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 2 assert not await repository.team_has_read_permission(team_slug3) assert client.called == 3 gh_repo = github_types.GitHubRepository({ "id": github_types.GitHubRepositoryIdType(1), "owner": gh_owner, "full_name": "", "archived": False, "url": "", "default_branch": github_types.GitHubRefType(""), "name": github_types.GitHubRepositoryName("test2"), "private": False, }) client = FakeClient(gh_owner["login"], gh_repo["name"]) installation = context.Installation(gh_owner["id"], gh_owner["login"], sub, client, redis_cache) repository = context.Repository(installation, gh_repo["name"], gh_repo["id"]) assert client.called == 0 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 1 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 1 assert await repository.team_has_read_permission(team_slug1) assert client.called == 2 await context.Repository.clear_team_permission_cache_for_repo( redis_cache, gh_owner, gh_repo) assert await repository.team_has_read_permission(team_slug1) assert client.called == 3 assert not await repository.team_has_read_permission(team_slug3) assert client.called == 4 await context.Repository.clear_team_permission_cache_for_org( redis_cache, gh_owner) assert not await repository.team_has_read_permission(team_slug3) assert client.called == 5 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 6 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 6 await context.Repository.clear_team_permission_cache_for_team( redis_cache, gh_owner, team_slug2) assert not await repository.team_has_read_permission(team_slug2) assert client.called == 7
async def test_team_members_cache(redis_cache: utils.RedisCache) -> None: class FakeClient(github.AsyncGithubInstallationClient): called: int def __init__(self, owner): super().__init__(auth=None) self.owner = owner self.called = 0 async def items(self, url, *args, **kwargs): self.called += 1 if url == f"/orgs/{self.owner}/teams/team1/members": yield {"login": "******"} yield {"login": "******"} elif url == f"/orgs/{self.owner}/teams/team2/members": yield {"login": "******"} yield {"login": "******"} elif url == f"/orgs/{self.owner}/teams/team3/members": return else: raise ValueError( f"Unknown test URL `{url}` for repo {self.repo}") gh_owner = github_types.GitHubAccount({ "id": github_types.GitHubAccountIdType(123), "login": github_types.GitHubLogin("jd"), "type": "User", "avatar_url": "", }) team_slug1 = github_types.GitHubTeamSlug("team1") team_slug2 = github_types.GitHubTeamSlug("team2") team_slug3 = github_types.GitHubTeamSlug("team3") sub = subscription.Subscription(redis_cache, 0, False, "", frozenset()) client = FakeClient(gh_owner["login"]) installation = context.Installation(gh_owner["id"], gh_owner["login"], sub, client, redis_cache) assert client.called == 0 assert (await installation.get_team_members(team_slug1)) == [ "member1", "member2" ] assert client.called == 1 assert (await installation.get_team_members(team_slug1)) == [ "member1", "member2" ] assert client.called == 1 assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 2 assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 2 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 3 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 3 await installation.clear_team_members_cache_for_team( redis_cache, gh_owner, github_types.GitHubTeamSlug(team_slug2)) assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 4 assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 4 await installation.clear_team_members_cache_for_org(redis_cache, gh_owner) assert (await installation.get_team_members(team_slug1)) == [ "member1", "member2" ] assert client.called == 5 assert (await installation.get_team_members(team_slug1)) == [ "member1", "member2" ] assert client.called == 5 assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 6 assert (await installation.get_team_members(team_slug2)) == [ "member3", "member4" ] assert client.called == 6 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 7 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 7
async def test_team_permission_cache(redis_cache: utils.RedisCache) -> None: class FakeClient(github.AsyncGithubInstallationClient): called: int def __init__(self, owner: str, repo: str) -> None: super().__init__(auth=None) # type: ignore[arg-type] self.owner = owner self.repo = repo self.called = 0 async def get(self, url: str, *args: typing.Any, **kwargs: typing.Any) -> typing.Any: # type: ignore[override] self.called += 1 if ( url == f"/orgs/{self.owner}/teams/team-ok/repos/{self.owner}/{self.repo}" ): return {} elif ( url == f"/orgs/{self.owner}/teams/team-nok/repos/{self.owner}/{self.repo}" ): raise http.HTTPNotFound( message="Not found", request=mock.ANY, response=mock.ANY ) elif ( url == f"/orgs/{self.owner}/teams/team-also-nok/repos/{self.owner}/{self.repo}" ): raise http.HTTPNotFound( message="Not found", request=mock.ANY, response=mock.ANY ) raise ValueError(f"Unknown test URL `{url}`") gh_owner = github_types.GitHubAccount( { "id": github_types.GitHubAccountIdType(123), "login": github_types.GitHubLogin("jd"), "type": "User", "avatar_url": "", } ) gh_repo = github_types.GitHubRepository( { "id": github_types.GitHubRepositoryIdType(0), "owner": gh_owner, "full_name": "", "archived": False, "url": "", "html_url": "", "default_branch": github_types.GitHubRefType(""), "name": github_types.GitHubRepositoryName("test"), "private": False, } ) installation_json = github_types.GitHubInstallation( { "id": github_types.GitHubInstallationIdType(12345), "target_type": gh_owner["type"], "permissions": {}, "account": gh_owner, } ) team_slug1 = github_types.GitHubTeamSlug("team-ok") team_slug2 = github_types.GitHubTeamSlug("team-nok") team_slug3 = github_types.GitHubTeamSlug("team-also-nok") sub = subscription.Subscription( redis_cache, 0, "", frozenset([subscription.Features.PUBLIC_REPOSITORY]) ) client = FakeClient(gh_owner["login"], gh_repo["name"]) installation = context.Installation( installation_json, sub, client, redis_cache, mock.Mock() ) repository = context.Repository(installation, gh_repo) assert client.called == 0 assert await repository.team_has_read_permission(team_slug1) assert client.called == 1 assert await repository.team_has_read_permission(team_slug1) assert client.called == 1 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 2 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 2 assert not await repository.team_has_read_permission(team_slug3) assert client.called == 3 gh_repo = github_types.GitHubRepository( { "id": github_types.GitHubRepositoryIdType(1), "owner": gh_owner, "full_name": "", "archived": False, "url": "", "html_url": "", "default_branch": github_types.GitHubRefType(""), "name": github_types.GitHubRepositoryName("test2"), "private": False, } ) client = FakeClient(gh_owner["login"], gh_repo["name"]) installation = context.Installation( installation_json, sub, client, redis_cache, mock.Mock() ) repository = context.Repository(installation, gh_repo) assert client.called == 0 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 1 # From local cache assert not await repository.team_has_read_permission(team_slug2) assert client.called == 1 # From redis repository._caches.team_has_read_permission.clear() assert not await repository.team_has_read_permission(team_slug2) assert client.called == 1 assert await repository.team_has_read_permission(team_slug1) assert client.called == 2 await context.Repository.clear_team_permission_cache_for_repo( redis_cache, gh_owner, gh_repo ) repository._caches.team_has_read_permission.clear() assert await repository.team_has_read_permission(team_slug1) assert client.called == 3 assert not await repository.team_has_read_permission(team_slug3) assert client.called == 4 await context.Repository.clear_team_permission_cache_for_org(redis_cache, gh_owner) repository._caches.team_has_read_permission.clear() assert not await repository.team_has_read_permission(team_slug3) assert client.called == 5 assert not await repository.team_has_read_permission(team_slug2) assert client.called == 6 # From local cache assert not await repository.team_has_read_permission(team_slug2) assert client.called == 6 # From redis repository._caches.team_has_read_permission.clear() assert not await repository.team_has_read_permission(team_slug2) assert client.called == 6 repository._caches.team_has_read_permission.clear() await context.Repository.clear_team_permission_cache_for_team( redis_cache, gh_owner, team_slug2 ) repository._caches.team_has_read_permission.clear() assert not await repository.team_has_read_permission(team_slug2) assert client.called == 7
async def test_team_members_cache(redis_cache: utils.RedisCache) -> None: class FakeClient(github.AsyncGithubInstallationClient): called: int def __init__(self, owner: str) -> None: super().__init__(auth=None) # type: ignore[arg-type] self.owner = owner self.called = 0 async def items(self, url, *args, **kwargs): self.called += 1 if url == f"/orgs/{self.owner}/teams/team1/members": yield {"login": "******"} yield {"login": "******"} elif url == f"/orgs/{self.owner}/teams/team2/members": yield {"login": "******"} yield {"login": "******"} elif url == f"/orgs/{self.owner}/teams/team3/members": return else: raise ValueError(f"Unknown test URL `{url}` for repo {self.repo}") gh_owner = github_types.GitHubAccount( { "id": github_types.GitHubAccountIdType(123), "login": github_types.GitHubLogin("jd"), "type": "User", "avatar_url": "", } ) installation_json = github_types.GitHubInstallation( { "id": github_types.GitHubInstallationIdType(12345), "target_type": gh_owner["type"], "permissions": {}, "account": gh_owner, } ) team_slug1 = github_types.GitHubTeamSlug("team1") team_slug2 = github_types.GitHubTeamSlug("team2") team_slug3 = github_types.GitHubTeamSlug("team3") sub = subscription.Subscription( redis_cache, 0, "", frozenset([subscription.Features.PUBLIC_REPOSITORY]) ) client = FakeClient(gh_owner["login"]) installation = context.Installation( installation_json, sub, client, redis_cache, mock.Mock() ) assert client.called == 0 assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"] assert client.called == 1 assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"] assert client.called == 1 assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 2 # From local cache assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 2 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 2 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 3 # From local cache assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 3 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 3 await installation.clear_team_members_cache_for_team( redis_cache, gh_owner, github_types.GitHubTeamSlug(team_slug2) ) installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 4 # From local cache assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 4 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 4 await installation.clear_team_members_cache_for_org(redis_cache, gh_owner) installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"] assert client.called == 5 # From local cache assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"] assert client.called == 5 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"] assert client.called == 5 assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 6 # From local cache assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 6 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"] assert client.called == 6 assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 7 # From local cache assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 7 # From redis installation._caches.team_members.clear() assert (await installation.get_team_members(team_slug3)) == [] assert client.called == 7
async def build_fake_context( number: github_types.GitHubPullRequestNumber, *, repository: context.Repository, **kwargs: typing.Dict[str, typing.Any], ) -> context.Context: pull_request_author = github_types.GitHubAccount( { "id": github_types.GitHubAccountIdType(123), "type": "User", "login": github_types.GitHubLogin("contributor"), "avatar_url": "", } ) pull: github_types.GitHubPullRequest = { "node_id": "42", "locked": False, "assignees": [], "requested_reviewers": [ { "id": github_types.GitHubAccountIdType(123), "type": "User", "login": github_types.GitHubLogin("jd"), "avatar_url": "", }, { "id": github_types.GitHubAccountIdType(456), "type": "User", "login": github_types.GitHubLogin("sileht"), "avatar_url": "", }, ], "requested_teams": [ {"slug": github_types.GitHubTeamSlug("foobar")}, {"slug": github_types.GitHubTeamSlug("foobaz")}, ], "milestone": None, "title": "awesome", "body": "", "created_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"), "closed_at": None, "updated_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"), "id": github_types.GitHubPullRequestId(123), "maintainer_can_modify": True, "user": pull_request_author, "labels": [], "rebaseable": True, "draft": False, "merge_commit_sha": None, "number": number, "commits": 1, "mergeable_state": "clean", "mergeable": True, "state": "open", "changed_files": 1, "head": { "sha": github_types.SHAType("the-head-sha"), "label": f"{pull_request_author['login']}:feature-branch", "ref": github_types.GitHubRefType("feature-branch"), "repo": { "id": github_types.GitHubRepositoryIdType(123), "default_branch": github_types.GitHubRefType("main"), "name": github_types.GitHubRepositoryName("mergify-engine"), "full_name": "contributor/mergify-engine", "archived": False, "private": False, "owner": pull_request_author, "url": "https://api.github.com/repos/contributor/mergify-engine", "html_url": "https://github.com/contributor/mergify-engine", }, "user": pull_request_author, }, "merged": False, "merged_by": None, "merged_at": None, "html_url": "https://...", "base": { "label": "mergify_engine:main", "ref": github_types.GitHubRefType("main"), "repo": repository.repo, "sha": github_types.SHAType("the-base-sha"), "user": repository.repo["owner"], }, } pull.update(kwargs) # type: ignore return await context.Context.create(repository, pull)