예제 #1
0
async def test_get_mergify_config_location_from_cache(
    redis_cache: utils.RedisCache, ) -> None:
    client = mock.AsyncMock()
    client.auth.owner = "foo"
    client.item.side_effect = [
        http.HTTPNotFound("Not Found",
                          request=mock.Mock(),
                          response=mock.Mock()),
        http.HTTPNotFound("Not Found",
                          request=mock.Mock(),
                          response=mock.Mock()),
        github_types.GitHubContentFile({
            "content":
            encodebytes("whatever".encode()).decode(),
            "type":
            "file",
            "path":
            ".github/mergify.yml",
            "sha":
            github_types.SHAType("zeazeaze"),
        }),
    ]

    installation = context.Installation(
        github_types.GitHubAccountIdType(0),
        github_types.GitHubLogin("foo"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(installation,
                                    github_types.GitHubRepositoryName("bar"))

    await repository.get_mergify_config_file()
    assert client.item.call_count == 3
    client.item.assert_has_calls([
        mock.call("/repos/foo/bar/contents/.mergify.yml"),
        mock.call("/repos/foo/bar/contents/.mergify/config.yml"),
        mock.call("/repos/foo/bar/contents/.github/mergify.yml"),
    ])

    client.item.reset_mock()
    client.item.side_effect = [
        github_types.GitHubContentFile({
            "content":
            encodebytes("whatever".encode()).decode(),
            "type":
            "file",
            "path":
            ".github/mergify.yml",
            "sha":
            github_types.SHAType("zeazeaze"),
        }),
    ]
    repository._cache = context.RepositoryCache()
    await repository.get_mergify_config_file()
    assert client.item.call_count == 1
    client.item.assert_has_calls([
        mock.call("/repos/foo/bar/contents/.github/mergify.yml"),
    ])
예제 #2
0
def _url_parser(
    url: str,
) -> typing.Tuple[github_types.GitHubLogin,
                  typing.Optional[github_types.GitHubRepositoryName],
                  typing.Optional[github_types.GitHubPullRequestNumber], ]:

    path = [
        el for el in urllib.parse.urlparse(url).path.split("/") if el != ""
    ]

    pull_number: typing.Optional[str]
    repo: typing.Optional[str]

    try:
        owner, repo, _, pull_number = path
    except ValueError:
        pull_number = None
        try:
            owner, repo = path
        except ValueError:
            if len(path) == 1:
                owner = path[0]
                repo = None
            else:
                raise ValueError

    return (
        github_types.GitHubLogin(owner),
        None if repo is None else github_types.GitHubRepositoryName(repo),
        None if pull_number is None else github_types.GitHubPullRequestNumber(
            int(pull_number)),
    )
예제 #3
0
async def test_get_mergify_config_invalid(
        invalid: str, redis_cache: utils.RedisCache) -> None:
    with pytest.raises(InvalidRules):

        async def item(*args, **kwargs):
            return github_types.GitHubContentFile({
                "content":
                encodebytes(invalid.encode()).decode(),
                "path":
                ".mergify.yml",
                "type":
                "file",
                "sha":
                "azertyu",
            })

        client = mock.Mock()
        client.item.return_value = item()
        installation = context.Installation(
            github_types.GitHubAccountIdType(0),
            github_types.GitHubLogin("foobar"),
            subscription.Subscription(redis_cache, 0, False, "", frozenset()),
            client,
            redis_cache,
        )
        repository = context.Repository(
            installation,
            github_types.GitHubRepositoryName("xyz"),
            github_types.GitHubRepositoryIdType(0),
        )

        config_file = await repository.get_mergify_config_file()
        assert config_file is not None
        get_mergify_config(config_file)
예제 #4
0
def test_seats_renamed_account_repo() -> None:
    user1 = count_seats.SeatAccount(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user1"),
    )
    user1bis = count_seats.SeatAccount(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user1bis"),
    )
    user2 = count_seats.SeatAccount(
        github_types.GitHubAccountIdType(456),
        github_types.GitHubLogin("user2"),
    )
    user2bis = count_seats.SeatAccount(
        github_types.GitHubAccountIdType(456),
        github_types.GitHubLogin("user2bis"),
    )

    users = {user1, user2, user2bis, user1bis}
    assert len(users) == 2
    assert list(users)[0].login == "user2"
    assert list(users)[1].login == "user1"

    repo1 = count_seats.SeatRepository(
        github_types.GitHubRepositoryIdType(123),
        github_types.GitHubRepositoryName("repo1"),
    )
    repo1bis = count_seats.SeatRepository(
        github_types.GitHubRepositoryIdType(123),
        github_types.GitHubRepositoryName("repo1bis"),
    )
    repo2 = count_seats.SeatRepository(
        github_types.GitHubRepositoryIdType(456),
        github_types.GitHubRepositoryName("repo2"),
    )
    repo2bis = count_seats.SeatRepository(
        github_types.GitHubRepositoryIdType(456),
        github_types.GitHubRepositoryName("repo2bis"),
    )

    repos = {repo1, repo2, repo2bis, repo1bis}
    assert repos == {repo1, repo2}
예제 #5
0
def repository(redis_cache, fake_client):
    installation = context.Installation(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        fake_client,
        redis_cache,
    )
    return context.Repository(
        installation,
        github_types.GitHubRepositoryName("name"),
        github_types.GitHubRepositoryIdType(123),
    )
예제 #6
0
def fake_repository(
    redis_links: redis_utils.RedisLinks,
    fake_subscription: subscription.Subscription,
) -> context.Repository:
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("Mergifyio"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "full_name":
        "Mergifyio/mergify-engine",
        "name":
        github_types.GitHubRepositoryName("mergify-engine"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("main"),
    })
    installation_json = github_types.GitHubInstallation({
        "id":
        github_types.GitHubInstallationIdType(12345),
        "target_type":
        gh_owner["type"],
        "permissions": {},
        "account":
        gh_owner,
    })

    fake_client = mock.Mock()
    installation = context.Installation(installation_json, fake_subscription,
                                        fake_client, redis_links)
    return context.Repository(installation, gh_repo)
예제 #7
0
def fake_repository(
    redis_cache: utils.RedisCache,
    fake_subscription: subscription.Subscription,
) -> context.Repository:
    gh_owner = github_types.GitHubAccount(
        {
            "login": github_types.GitHubLogin("Mergifyio"),
            "id": github_types.GitHubAccountIdType(0),
            "type": "User",
            "avatar_url": "",
        }
    )

    gh_repo = github_types.GitHubRepository(
        {
            "full_name": "Mergifyio/mergify-engine",
            "name": github_types.GitHubRepositoryName("mergify-engine"),
            "private": False,
            "id": github_types.GitHubRepositoryIdType(0),
            "owner": gh_owner,
            "archived": False,
            "url": "",
            "html_url": "",
            "default_branch": github_types.GitHubRefType("main"),
        }
    )
    installation_json = github_types.GitHubInstallation(
        {
            "id": github_types.GitHubInstallationIdType(12345),
            "target_type": gh_owner["type"],
            "permissions": {},
            "account": gh_owner,
        }
    )

    fake_client = redis_queue = mock.Mock()
    # NOTE(Syffe): Since redis_queue is not used in fake_repository, we simply mock it,
    # otherwise a fixture is needed for it. This might change with future use of redis_queue.
    installation = context.Installation(
        installation_json, fake_subscription, fake_client, redis_cache, redis_queue
    )
    return context.Repository(installation, gh_repo)
예제 #8
0
def repository(redis_cache, fake_client):
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "full_name":
        "user/name",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })
    installation = context.Installation(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        fake_client,
        redis_cache,
    )
    return context.Repository(installation, gh_repo)
예제 #9
0
    async def populate_with_active_users(
        self,
        redis_cache: redis_utils.RedisCache,
        owner_id: typing.Optional[github_types.GitHubAccountIdType] = None,
    ) -> None:
        async for key in get_active_users_keys(
                redis_cache, owner_id="*" if owner_id is None else owner_id):
            _, _owner_id, owner_login, repo_id, repo_name = key.split("~")
            org = SeatAccount(
                github_types.GitHubAccountIdType(int(_owner_id)),
                github_types.GitHubLogin(owner_login),
            )
            repo = SeatRepository(
                github_types.GitHubRepositoryIdType(int(repo_id)),
                github_types.GitHubRepositoryName(repo_name),
            )
            active_users = await get_active_users(redis_cache, key)

            repo_seats = self.seats[org][repo]
            if repo_seats["active_users"] is None:
                repo_seats["active_users"] = active_users
            else:
                repo_seats["active_users"] |= active_users
예제 #10
0
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
예제 #11
0
GH_REPO = github_types.GitHubRepository({
    "archived":
    False,
    "url":
    "",
    "html_url":
    "",
    "default_branch":
    github_types.GitHubRefType("main"),
    "id":
    github_types.GitHubRepositoryIdType(456),
    "full_name":
    "user/ref",
    "name":
    github_types.GitHubRepositoryName("name"),
    "private":
    False,
    "owner":
    GH_OWNER,
})
GH_PULL = github_types.GitHubPullRequest(
    {
        "title": "",
        "id": github_types.GitHubPullRequestId(0),
        "maintainer_can_modify": False,
        "rebaseable": False,
        "draft": False,
        "merge_commit_sha": None,
        "labels": [],
        "number": github_types.GitHubPullRequestNumber(6),
예제 #12
0
def a_pull_request() -> github_types.GitHubPullRequest:
    gh_owner = github_types.GitHubAccount(
        {
            "login": github_types.GitHubLogin("user"),
            "id": github_types.GitHubAccountIdType(0),
            "type": "User",
            "avatar_url": "",
        }
    )

    gh_repo = github_types.GitHubRepository(
        {
            "archived": False,
            "url": "",
            "html_url": "",
            "default_branch": github_types.GitHubRefType(""),
            "id": github_types.GitHubRepositoryIdType(456),
            "full_name": "user/repo",
            "name": github_types.GitHubRepositoryName("repo"),
            "private": False,
            "owner": gh_owner,
        }
    )

    return github_types.GitHubPullRequest(
        {
            "node_id": "42",
            "locked": False,
            "assignees": [],
            "requested_reviewers": [],
            "requested_teams": [],
            "milestone": None,
            "title": "",
            "updated_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"),
            "created_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"),
            "closed_at": None,
            "id": github_types.GitHubPullRequestId(0),
            "maintainer_can_modify": False,
            "rebaseable": False,
            "draft": False,
            "merge_commit_sha": None,
            "labels": [],
            "number": github_types.GitHubPullRequestNumber(6),
            "commits": 1,
            "merged": True,
            "state": "closed",
            "changed_files": 1,
            "html_url": "<html_url>",
            "base": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "ref": github_types.GitHubRefType("ref"),
                "repo": gh_repo,
            },
            "head": {
                "label": "",
                "sha": github_types.SHAType("old-sha-one"),
                "ref": github_types.GitHubRefType("fork"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "repo": {
                    "archived": False,
                    "url": "",
                    "html_url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "id": github_types.GitHubRepositoryIdType(123),
                    "full_name": "fork/other",
                    "name": github_types.GitHubRepositoryName("other"),
                    "private": False,
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
            },
            "user": {
                "login": github_types.GitHubLogin("user"),
                "id": github_types.GitHubAccountIdType(0),
                "type": "User",
                "avatar_url": "",
            },
            "merged_by": None,
            "merged_at": None,
            "mergeable_state": "clean",
            "mergeable": True,
            "body": None,
        }
    )
예제 #13
0
async def test_user_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 item(self, url, *args, **kwargs):
            self.called += 1
            if self.repo == "test":
                if (url ==
                        f"/repos/{self.owner}/{self.repo}/collaborators/foo/permission"
                    ):
                    return {"permission": "admin"}
                elif url.startswith(
                        f"/repos/{self.owner}/{self.repo}/collaborators/"):
                    return {"permission": "loser"}
            elif self.repo == "test2":
                if (url ==
                        f"/repos/{self.owner}/{self.repo}/collaborators/bar/permission"
                    ):
                    return {"permission": "admin"}
                elif url.startswith(
                        f"/repos/{self.owner}/{self.repo}/collaborators/"):
                    return {"permission": "loser"}
            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":
        "",
    })

    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,
    })

    user_1 = github_types.GitHubAccount({
        "id":
        github_types.GitHubAccountIdType(1),
        "login":
        github_types.GitHubLogin("foo"),
        "type":
        "User",
        "avatar_url":
        "",
    })
    user_2 = github_types.GitHubAccount({
        "id":
        github_types.GitHubAccountIdType(2),
        "login":
        github_types.GitHubLogin("bar"),
        "type":
        "User",
        "avatar_url":
        "",
    })
    user_3 = github_types.GitHubAccount({
        "id":
        github_types.GitHubAccountIdType(3),
        "login":
        github_types.GitHubLogin("baz"),
        "type":
        "User",
        "avatar_url":
        "",
    })

    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.has_write_permission(user_1)
    assert client.called == 1
    assert await repository.has_write_permission(user_1)
    assert client.called == 1
    assert not await repository.has_write_permission(user_2)
    assert client.called == 2
    assert not await repository.has_write_permission(user_2)
    assert client.called == 2
    assert not await repository.has_write_permission(user_3)
    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 await repository.has_write_permission(user_2)
    assert client.called == 1
    assert await repository.has_write_permission(user_2)
    assert client.called == 1
    assert not await repository.has_write_permission(user_1)
    assert client.called == 2
    await context.Repository.clear_user_permission_cache_for_repo(
        redis_cache, gh_owner, gh_repo)
    assert not await repository.has_write_permission(user_1)
    assert client.called == 3
    assert not await repository.has_write_permission(user_3)
    assert client.called == 4
    await context.Repository.clear_user_permission_cache_for_org(
        redis_cache, gh_owner)
    assert not await repository.has_write_permission(user_3)
    assert client.called == 5
    assert await repository.has_write_permission(user_2)
    assert client.called == 6
    assert await repository.has_write_permission(user_2)
    assert client.called == 6
    await context.Repository.clear_user_permission_cache_for_user(
        redis_cache, gh_owner, gh_repo, user_2)
    assert await repository.has_write_permission(user_2)
    assert client.called == 7
예제 #14
0
    async def _extract_pulls_from_stream(
        self, installation: context.Installation
    ) -> PullsToConsume:
        messages: typing.List[
            typing.Tuple[T_MessageID, T_MessagePayload]
        ] = await self.redis_stream.xrange(
            installation.stream_name, count=config.STREAM_MAX_BATCH
        )
        LOG.debug(
            "read stream",
            stream_name=installation.stream_name,
            messages_count=len(messages),
        )
        statsd.histogram("engine.streams.size", len(messages))
        statsd.gauge("engine.streams.max_size", config.STREAM_MAX_BATCH)

        # TODO(sileht): Put this cache in Repository context
        opened_pulls_by_repo: typing.Dict[
            github_types.GitHubRepositoryName,
            typing.List[github_types.GitHubPullRequest],
        ] = {}

        # Groups stream by pull request
        pulls: PullsToConsume = PullsToConsume(collections.OrderedDict())
        for message_id, message in messages:
            data = msgpack.unpackb(message[b"event"], raw=False)
            repo = github_types.GitHubRepositoryName(data["repo"])
            source = typing.cast(context.T_PayloadEventSource, data["source"])
            if data["pull_number"] is not None:
                key = (repo, github_types.GitHubPullRequestNumber(data["pull_number"]))
                group = pulls.setdefault(key, ([], []))
                group[0].append(message_id)
                group[1].append(source)
            else:
                logger = daiquiri.getLogger(
                    __name__,
                    gh_repo=repo,
                    gh_owner=installation.owner_login,
                    source=source,
                )
                if repo not in opened_pulls_by_repo:
                    try:
                        opened_pulls_by_repo[repo] = [
                            p
                            async for p in installation.client.items(
                                f"/repos/{installation.owner_login}/{repo}/pulls"
                            )
                        ]
                    except Exception as e:
                        if exceptions.should_be_ignored(e):
                            opened_pulls_by_repo[repo] = []
                        else:
                            raise

                converted_messages = await self._convert_event_to_messages(
                    installation,
                    repo,
                    source,
                    opened_pulls_by_repo[repo],
                )

                logger.debug("event unpacked into %s messages", len(converted_messages))
                messages.extend(converted_messages)
                deleted = await self.redis_stream.xdel(
                    installation.stream_name, message_id
                )
                if deleted != 1:
                    # FIXME(sileht): During shutdown, heroku may have already started
                    # another worker that have already take the lead of this stream_name
                    # This can create duplicate events in the streams but that should not
                    # be a big deal as the engine will not been ran by the worker that's
                    # shutdowning.
                    contents = await self.redis_stream.xrange(
                        installation.stream_name, start=message_id, end=message_id
                    )
                    if contents:
                        logger.error(
                            "message `%s` have not been deleted has expected, "
                            "(result: %s), content of current message id: %s",
                            message_id,
                            deleted,
                            contents,
                        )
        return pulls
예제 #15
0
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
예제 #16
0
async def report(
    url: str,
) -> typing.Union[context.Context, github.AsyncGithubInstallationClient, None]:
    redis_cache = utils.create_aredis_for_cache(max_idle_time=0)

    try:
        owner, repo, pull_number = _url_parser(url)
    except ValueError:
        print(f"{url} is not valid")
        return None

    try:
        client = github.aget_client(owner)
    except exceptions.MergifyNotInstalled:
        print(f"* Mergify is not installed on account {owner}")
        return None

    # Do a dumb request just to authenticate
    await client.get("/")

    if client.auth.installation is None:
        print("No installation detected")
        return None

    print(f"* INSTALLATION ID: {client.auth.installation['id']}")

    if client.auth.owner_id is None:
        raise RuntimeError("Unable to get owner_id")

    if repo is None:
        slug = None
    else:
        slug = owner + "/" + repo

    cached_sub = await subscription.Subscription.get_subscription(
        redis_cache, client.auth.owner_id)
    db_sub = await subscription.Subscription._retrieve_subscription_from_db(
        redis_cache, client.auth.owner_id)

    cached_tokens = await user_tokens.UserTokens.get(redis_cache,
                                                     client.auth.owner_id)
    db_tokens = await user_tokens.UserTokens._retrieve_from_db(
        redis_cache, client.auth.owner_id)

    print(f"* SUBSCRIBED (cache/db): {cached_sub.active} / {db_sub.active}")
    print("* Features (cache):")
    for f in db_sub.features:
        print(f"  - {f.value}")
    print("* Features (db):")
    for f in cached_sub.features:
        print(f"  - {f.value}")

    await report_dashboard_synchro(client.auth.installation["id"], cached_sub,
                                   cached_tokens, "ENGINE-CACHE", slug)
    await report_dashboard_synchro(client.auth.installation["id"], db_sub,
                                   db_tokens, "DASHBOARD", slug)

    await report_worker_status(owner)

    installation = context.Installation(client.auth.owner_id, owner,
                                        cached_sub, client, redis_cache)

    if repo is not None:
        repo_info: github_types.GitHubRepository = await client.item(
            f"/repos/{owner}/{repo}")
        repository = context.Repository(installation, repo_info["name"],
                                        repo_info["id"])

        print(
            f"* REPOSITORY IS {'PRIVATE' if repo_info['private'] else 'PUBLIC'}"
        )

        print("* CONFIGURATION:")
        mergify_config = None
        config_file = await repository.get_mergify_config_file()
        if config_file is None:
            print(".mergify.yml is missing")
        else:
            print(f"Config filename: {config_file['path']}")
            print(config_file["decoded_content"].decode())
            try:
                mergify_config = rules.get_mergify_config(config_file)
            except rules.InvalidRules as e:  # pragma: no cover
                print(f"configuration is invalid {str(e)}")
            else:
                mergify_config["pull_request_rules"].rules.extend(
                    engine.MERGIFY_BUILTIN_CONFIG["pull_request_rules"].rules)

        if pull_number is None:
            async for branch in typing.cast(
                    typing.AsyncGenerator[github_types.GitHubBranch, None],
                    client.items(f"/repos/{owner}/{repo}/branches"),
            ):
                # TODO(sileht): Add some informations on the train
                q: queue.QueueBase = naive.Queue(repository, branch["name"])
                await report_queue("QUEUES", q)

                q = merge_train.Train(repository, branch["name"])
                await q.load()
                await report_queue("TRAIN", q)

        else:
            repository = context.Repository(
                installation, github_types.GitHubRepositoryName(repo))
            ctxt = await repository.get_pull_request_context(
                github_types.GitHubPullRequestNumber(int(pull_number)))

            # FIXME queues could also be printed if no pull number given
            # TODO(sileht): display train if any
            q = await naive.Queue.from_context(ctxt)
            print(
                f"* QUEUES: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            q = await merge_train.Train.from_context(ctxt)
            print(
                f"* TRAIN: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            print("* PULL REQUEST:")
            pr_data = await ctxt.pull_request.items()
            pprint.pprint(pr_data, width=160)

            is_behind = await ctxt.is_behind
            print(f"is_behind: {is_behind}")

            print(f"mergeable_state: {ctxt.pull['mergeable_state']}")

            print("* MERGIFY LAST CHECKS:")
            for c in await ctxt.pull_engine_check_runs:
                print(
                    f"[{c['name']}]: {c['conclusion']} | {c['output'].get('title')}"
                )
                print("> " + "\n> ".join(
                    ("No Summary", ) if c["output"]["summary"] is None else
                    c["output"]["summary"].split("\n")))

            if mergify_config is not None:
                print("* MERGIFY LIVE MATCHES:")
                match = await mergify_config["pull_request_rules"
                                             ].get_pull_request_rule(ctxt)
                summary_title, summary = await actions_runner.gen_summary(
                    ctxt, match)
                print(f"[Summary]: success | {summary_title}")
                print("> " + "\n> ".join(summary.strip().split("\n")))

            return ctxt

    return client
예제 #17
0
         "id": github_types.GitHubAccountIdType(0),
         "login": github_types.GitHubLogin(""),
         "type": "User",
         "avatar_url": "",
     },
     "label": "",
     "ref": github_types.GitHubRefType(""),
     "sha": github_types.SHAType(""),
     "repo": {
         "url": "",
         "default_branch": github_types.GitHubRefType(""),
         "full_name": "",
         "archived": False,
         "id": github_types.GitHubRepositoryIdType(0),
         "private": False,
         "name": github_types.GitHubRepositoryName(""),
         "owner": {
             "login": github_types.GitHubLogin(""),
             "id": github_types.GitHubAccountIdType(0),
             "type": "User",
             "avatar_url": "",
         },
     },
 },
 "head": {
     "user": {
         "id": github_types.GitHubAccountIdType(0),
         "login": github_types.GitHubLogin(""),
         "type": "User",
         "avatar_url": "",
     },
예제 #18
0
async def test_get_mergify_config_location_from_cache(
    redis_cache: utils.RedisCache, ) -> None:
    client = mock.AsyncMock()
    client.auth.owner = "foo"
    client.item.side_effect = [
        http.HTTPNotFound("Not Found",
                          request=mock.Mock(),
                          response=mock.Mock()),
        http.HTTPNotFound("Not Found",
                          request=mock.Mock(),
                          response=mock.Mock()),
        github_types.GitHubContentFile({
            "content":
            encodebytes("whatever".encode()).decode(),
            "type":
            "file",
            "path":
            ".github/mergify.yml",
            "sha":
            github_types.SHAType("zeazeaze"),
        }),
    ]

    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("foobar"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })
    gh_repo = github_types.GitHubRepository({
        "full_name":
        "foobar/xyz",
        "name":
        github_types.GitHubRepositoryName("xyz"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })
    installation = context.Installation(
        github_types.GitHubAccountIdType(0),
        github_types.GitHubLogin("foobar"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo)

    await repository.get_mergify_config_file()
    assert client.item.call_count == 3
    client.item.assert_has_calls([
        mock.call("/repos/foobar/xyz/contents/.mergify.yml", params={}),
        mock.call("/repos/foobar/xyz/contents/.mergify/config.yml", params={}),
        mock.call("/repos/foobar/xyz/contents/.github/mergify.yml", params={}),
    ])

    client.item.reset_mock()
    client.item.side_effect = [
        github_types.GitHubContentFile({
            "content":
            encodebytes("whatever".encode()).decode(),
            "type":
            "file",
            "path":
            ".github/mergify.yml",
            "sha":
            github_types.SHAType("zeazeaze"),
        }),
    ]
    repository._cache = context.RepositoryCache()
    await repository.get_mergify_config_file()
    assert client.item.call_count == 1
    client.item.assert_has_calls([
        mock.call("/repos/foobar/xyz/contents/.github/mergify.yml", params={}),
    ])
예제 #19
0
def test_url_parser_with_pr_ok(url: str) -> None:
    assert debug._url_parser(url) == (
        github_types.GitHubLogin("mergifyio"),
        github_types.GitHubRepositoryName("mergify-engine"),
        github_types.GitHubPullRequestNumber(123),
    )
예제 #20
0
async def test_get_commits_to_cherry_pick_merge(
    commits: mock.PropertyMock,
    redis_cache: utils.RedisCache,
) -> None:
    c1 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c1f"),
        "parents": [],
        "commit": {
            "message": "foobar"
        },
    })
    c2 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c2"),
        "parents": [c1],
        "commit": {
            "message": "foobar"
        },
    })

    async def fake_commits():
        return [c1, c2]

    commits.return_value = fake_commits()

    client = mock.Mock()
    client.auth.get_access_token.return_value = "<token>"

    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })
    gh_repo = github_types.GitHubRepository({
        "full_name":
        "user/name",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })

    installation = context.Installation(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo)
    ctxt = await context.Context.create(
        repository,
        {
            "number": github_types.GitHubPullRequestNumber(6),
            "commits": 1,
            "merged": True,
            "state": "closed",
            "html_url": "<html_url>",
            "id": github_types.GitHubPullRequestId(0),
            "maintainer_can_modify": True,
            "labels": [],
            "rebaseable": True,
            "draft": True,
            "merge_commit_sha": None,
            "title": "foobar",
            "changed_files": 1,
            "base": {
                "label":
                "user:ref",
                "sha":
                github_types.SHAType("sha"),
                "ref":
                github_types.GitHubRefType("ref"),
                "user":
                gh_owner,
                "repo":
                github_types.GitHubRepository(
                    {
                        "full_name": "user/ref",
                        "name": github_types.GitHubRepositoryName("name"),
                        "private": False,
                        "id": github_types.GitHubRepositoryIdType(0),
                        "owner": gh_owner,
                        "archived": False,
                        "url": "",
                        "html_url": "",
                        "default_branch": github_types.GitHubRefType("ref"),
                    }),
            },
            "head": {
                "label":
                "user:ref",
                "sha":
                github_types.SHAType("sha"),
                "user":
                gh_owner,
                "ref":
                github_types.GitHubRefType("fork"),
                "repo":
                github_types.GitHubRepository(
                    {
                        "full_name": "fork/other",
                        "name": github_types.GitHubRepositoryName("name"),
                        "private": False,
                        "id": github_types.GitHubRepositoryIdType(0),
                        "owner": gh_owner,
                        "archived": False,
                        "url": "",
                        "html_url": "",
                        "default_branch": github_types.GitHubRefType("ref"),
                    }),
            },
            "user": gh_owner,
            "merged_at": None,
            "merged_by": None,
            "mergeable_state": "clean",
        },
    )

    base_branch = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("base_branch"),
        "parents": [],
        "commit": {
            "message": "foobar"
        },
    })
    merge_commit = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("merge_commit"),
        "parents": [base_branch, c2],
        "commit": {
            "message": "foobar"
        },
    })

    assert await duplicate_pull._get_commits_to_cherrypick(ctxt,
                                                           merge_commit) == [
                                                               c1,
                                                               c2,
                                                           ]
예제 #21
0
async def test_get_mergify_config_invalid(
        invalid: str, redis_cache: utils.RedisCache) -> None:
    with pytest.raises(InvalidRules):

        async def item(*args, **kwargs):
            return github_types.GitHubContentFile({
                "content":
                encodebytes(invalid.encode()).decode(),
                "path":
                ".mergify.yml",
                "type":
                "file",
                "sha":
                "azertyu",
            })

        client = mock.Mock()
        client.item.return_value = item()

        gh_owner = github_types.GitHubAccount({
            "login":
            github_types.GitHubLogin("foobar"),
            "id":
            github_types.GitHubAccountIdType(0),
            "type":
            "User",
            "avatar_url":
            "",
        })
        gh_repo = github_types.GitHubRepository({
            "full_name":
            "foobar/xyz",
            "name":
            github_types.GitHubRepositoryName("xyz"),
            "private":
            False,
            "id":
            github_types.GitHubRepositoryIdType(0),
            "owner":
            gh_owner,
            "archived":
            False,
            "url":
            "",
            "html_url":
            "",
            "default_branch":
            github_types.GitHubRefType("ref"),
        })
        installation = context.Installation(
            github_types.GitHubAccountIdType(0),
            github_types.GitHubLogin("foobar"),
            subscription.Subscription(redis_cache, 0, False, "", frozenset()),
            client,
            redis_cache,
        )
        repository = context.Repository(
            installation,
            gh_repo,
        )

        config_file = await repository.get_mergify_config_file()
        assert config_file is not None
        get_mergify_config(config_file)
예제 #22
0
async def test_summary_synchronization_cache(
    redis_cache: utils.RedisCache, ) -> None:
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "archived":
        False,
        "url":
        "",
        "default_branch":
        github_types.GitHubRefType(""),
        "id":
        github_types.GitHubRepositoryIdType(456),
        "full_name":
        "user/ref",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "owner":
        gh_owner,
    })

    async def items(*args, **kwargs):
        if False:
            yield
        return

    async def post_check(*args, **kwargs):
        return mock.Mock()

    client = mock.AsyncMock()
    client.auth.get_access_token.return_value = "<token>"
    client.items = items
    client.post.side_effect = post_check

    sub = subscription.Subscription(redis_cache, 0, False, "", frozenset())
    installation = context.Installation(
        gh_owner["id"],
        gh_owner["login"],
        sub,
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo["name"],
                                    gh_repo["id"])
    ctxt = await context.Context.create(
        repository,
        {
            "title": "",
            "id": github_types.GitHubPullRequestId(0),
            "maintainer_can_modify": False,
            "rebaseable": False,
            "draft": False,
            "merge_commit_sha": None,
            "labels": [],
            "number": github_types.GitHubPullRequestNumber(6),
            "commits": 1,
            "merged": True,
            "state": "closed",
            "changed_files": 1,
            "html_url": "<html_url>",
            "base": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "ref": github_types.GitHubRefType("ref"),
                "label": "",
                "repo": gh_repo,
            },
            "head": {
                "label": "",
                "sha": github_types.SHAType("old-sha-one"),
                "ref": github_types.GitHubRefType("fork"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "repo": {
                    "archived": False,
                    "url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "id": github_types.GitHubRepositoryIdType(123),
                    "full_name": "fork/other",
                    "name": github_types.GitHubRepositoryName("other"),
                    "private": False,
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
            },
            "user": {
                "login": github_types.GitHubLogin("user"),
                "id": github_types.GitHubAccountIdType(0),
                "type": "User",
                "avatar_url": "",
            },
            "merged_by": None,
            "merged_at": None,
            "mergeable_state": "clean",
        },
    )
    assert await ctxt.get_cached_last_summary_head_sha() is None
    await ctxt.set_summary_check(
        check_api.Result(check_api.Conclusion.SUCCESS, "foo", "bar"))

    assert await ctxt.get_cached_last_summary_head_sha() == "old-sha-one"
    await ctxt.clear_cached_last_summary_head_sha()

    assert await ctxt.get_cached_last_summary_head_sha() is None
예제 #23
0
async def test_get_pull_request_rule(redis_cache: utils.RedisCache) -> None:

    client = mock.Mock()

    get_reviews = [{
        "user": {
            "login": "******",
            "id": 12321,
            "type": "User"
        },
        "state": "APPROVED",
        "author_association": "MEMBER",
    }]
    get_files = [{"filename": "README.rst"}, {"filename": "setup.py"}]
    get_team_members = [{
        "login": "******",
        "id": 12321
    }, {
        "login": "******",
        "id": 2644
    }]

    get_checks: typing.List[github_types.GitHubCheckRun] = []
    get_statuses: typing.List[github_types.GitHubStatus] = [{
        "context": "continuous-integration/fake-ci",
        "state": "success",
        "description": "foobar",
        "target_url": "http://example.com",
        "avatar_url": "",
    }]

    async def client_item(url, *args, **kwargs):
        if url == "/repos/another-jd/name/collaborators/sileht/permission":
            return {"permission": "write"}
        elif url == "/repos/another-jd/name/collaborators/jd/permission":
            return {"permission": "write"}
        raise RuntimeError(f"not handled url {url}")

    client.item.side_effect = client_item

    async def client_items(url, *args, **kwargs):
        if url == "/repos/another-jd/name/pulls/1/reviews":
            for r in get_reviews:
                yield r
        elif url == "/repos/another-jd/name/pulls/1/files":
            for f in get_files:
                yield f
        elif url == "/repos/another-jd/name/commits/<sha>/check-runs":
            for c in get_checks:
                yield c
        elif url == "/repos/another-jd/name/commits/<sha>/status":
            for s in get_statuses:
                yield s
        elif url == "/orgs/another-jd/teams/my-reviewers/members":
            for tm in get_team_members:
                yield tm
        else:
            raise RuntimeError(f"not handled url {url}")

    client.items.side_effect = client_items

    installation = context.Installation(
        github_types.GitHubAccountIdType(2644),
        github_types.GitHubLogin("another-jd"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(
        installation,
        github_types.GitHubRepositoryName("name"),
        github_types.GitHubRepositoryIdType(123321),
    )
    ctxt = await context.Context.create(
        repository,
        github_types.GitHubPullRequest({
            "id":
            github_types.GitHubPullRequestId(0),
            "number":
            github_types.GitHubPullRequestNumber(1),
            "commits":
            1,
            "html_url":
            "<html_url>",
            "merge_commit_sha":
            None,
            "maintainer_can_modify":
            True,
            "rebaseable":
            True,
            "state":
            "closed",
            "merged_by":
            None,
            "merged_at":
            None,
            "merged":
            False,
            "draft":
            False,
            "mergeable_state":
            "unstable",
            "labels": [],
            "changed_files":
            1,
            "base": {
                "label": "repo",
                "ref": github_types.GitHubRefType("master"),
                "repo": {
                    "id": github_types.GitHubRepositoryIdType(123321),
                    "name": github_types.GitHubRepositoryName("name"),
                    "full_name": "another-jd/name",
                    "private": False,
                    "archived": False,
                    "url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "owner": {
                        "login": github_types.GitHubLogin("another-jd"),
                        "id": github_types.GitHubAccountIdType(2644),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
                "user": {
                    "login": github_types.GitHubLogin("another-jd"),
                    "id": github_types.GitHubAccountIdType(2644),
                    "type": "User",
                    "avatar_url": "",
                },
                "sha": github_types.SHAType("mew"),
            },
            "head": {
                "label": "foo",
                "ref": github_types.GitHubRefType("myfeature"),
                "sha": github_types.SHAType("<sha>"),
                "repo": {
                    "id": github_types.GitHubRepositoryIdType(123321),
                    "name": github_types.GitHubRepositoryName("head"),
                    "full_name": "another-jd/head",
                    "private": False,
                    "archived": False,
                    "url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "owner": {
                        "login": github_types.GitHubLogin("another-jd"),
                        "id": github_types.GitHubAccountIdType(2644),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
                "user": {
                    "login": github_types.GitHubLogin("another-jd"),
                    "id": github_types.GitHubAccountIdType(2644),
                    "type": "User",
                    "avatar_url": "",
                },
            },
            "title":
            "My awesome job",
            "user": {
                "login": github_types.GitHubLogin("another-jd"),
                "id": github_types.GitHubAccountIdType(2644),
                "type": "User",
                "avatar_url": "",
            },
        }),
    )

    # Empty conditions
    pull_request_rules = rules.PullRequestRules([
        rules.Rule(name="default",
                   conditions=rules.RuleConditions([]),
                   actions={})
    ])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]
    assert [
        rules.EvaluatedRule.from_rule(r, rules.RuleMissingConditions([]), [])
        for r in match.rules
    ] == match.matching_rules
    for rule in match.rules:
        assert rule.actions == {}

    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "hello",
        "conditions": ["base:master"],
        "actions": {}
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["hello"]
    assert [r.name for r in match.matching_rules] == ["hello"]
    assert [
        rules.EvaluatedRule.from_rule(r, rules.RuleMissingConditions([]), [])
        for r in match.rules
    ] == match.matching_rules
    for rule in match.rules:
        assert rule.actions == {}

    pull_request_rules = pull_request_rule_from_list([
        {
            "name": "hello",
            "conditions": ["base:master"],
            "actions": {}
        },
        {
            "name": "backport",
            "conditions": ["base:master"],
            "actions": {}
        },
    ])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["hello", "backport"]
    assert [r.name for r in match.matching_rules] == ["hello", "backport"]
    assert [
        rules.EvaluatedRule.from_rule(r, rules.RuleMissingConditions([]), [])
        for r in match.rules
    ] == match.matching_rules
    for rule in match.rules:
        assert rule.actions == {}

    pull_request_rules = pull_request_rule_from_list([
        {
            "name": "hello",
            "conditions": ["author:foobar"],
            "actions": {}
        },
        {
            "name": "backport",
            "conditions": ["base:master"],
            "actions": {}
        },
    ])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["hello", "backport"]
    assert [r.name for r in match.matching_rules] == ["backport"]
    for rule in match.rules:
        assert rule.actions == {}

    pull_request_rules = pull_request_rule_from_list([
        {
            "name": "hello",
            "conditions": ["author:another-jd"],
            "actions": {}
        },
        {
            "name": "backport",
            "conditions": ["base:master"],
            "actions": {}
        },
    ])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["hello", "backport"]
    assert [r.name for r in match.matching_rules] == ["hello", "backport"]
    assert [
        rules.EvaluatedRule.from_rule(r, rules.RuleMissingConditions([]), [])
        for r in match.rules
    ] == match.matching_rules
    for rule in match.rules:
        assert rule.actions == {}

    # No match
    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "merge",
        "conditions": [
            "base=xyz",
            "check-success=continuous-integration/fake-ci",
            "#approved-reviews-by>=1",
        ],
        "actions": {},
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["merge"]
    assert [r.name for r in match.matching_rules] == []

    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "merge",
        "conditions": [
            "base=master",
            "check-success=continuous-integration/fake-ci",
            "#approved-reviews-by>=1",
        ],
        "actions": {},
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["merge"]
    assert [r.name for r in match.matching_rules] == ["merge"]
    assert [
        rules.EvaluatedRule.from_rule(r, rules.RuleMissingConditions([]), [])
        for r in match.rules
    ] == match.matching_rules
    for rule in match.rules:
        assert rule.actions == {}

    pull_request_rules = pull_request_rule_from_list([
        {
            "name":
            "merge",
            "conditions": [
                "base=master",
                "check-success=continuous-integration/fake-ci",
                "#approved-reviews-by>=2",
            ],
            "actions": {},
        },
        {
            "name":
            "fast merge",
            "conditions": [
                "base=master",
                "label=fast-track",
                "check-success=continuous-integration/fake-ci",
                "#approved-reviews-by>=1",
            ],
            "actions": {},
        },
        {
            "name":
            "fast merge with alternate ci",
            "conditions": [
                "base=master",
                "label=fast-track",
                "check-success=continuous-integration/fake-ci-bis",
                "#approved-reviews-by>=1",
            ],
            "actions": {},
        },
        {
            "name":
            "fast merge from a bot",
            "conditions": [
                "base=master",
                "author=mybot",
                "check-success=continuous-integration/fake-ci",
            ],
            "actions": {},
        },
    ])
    match = await pull_request_rules.get_pull_request_rule(ctxt)

    assert [r.name for r in match.rules] == [
        "merge",
        "fast merge",
        "fast merge with alternate ci",
        "fast merge from a bot",
    ]
    assert [r.name for r in match.matching_rules] == [
        "merge",
        "fast merge",
        "fast merge with alternate ci",
    ]
    for rule in match.rules:
        assert rule.actions == {}

    assert match.matching_rules[0].name == "merge"
    assert len(match.matching_rules[0].missing_conditions) == 1
    assert (str(match.matching_rules[0].missing_conditions[0]) ==
            "#approved-reviews-by>=2")

    assert match.matching_rules[1].name == "fast merge"
    assert len(match.matching_rules[1].missing_conditions) == 1
    assert str(
        match.matching_rules[1].missing_conditions[0]) == "label=fast-track"

    assert match.matching_rules[2].name == "fast merge with alternate ci"
    assert len(match.matching_rules[2].missing_conditions) == 2
    assert str(
        match.matching_rules[2].missing_conditions[0]) == "label=fast-track"
    assert (str(match.matching_rules[2].missing_conditions[1]) ==
            "check-success=continuous-integration/fake-ci-bis")

    # Team conditions with one review missing
    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "default",
        "conditions": [
            "approved-reviews-by=@another-jd/my-reviewers",
            "#approved-reviews-by>=2",
        ],
        "actions": {},
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]

    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 1
    assert (str(match.matching_rules[0].missing_conditions[0]) ==
            "#approved-reviews-by>=2")

    get_reviews.append({
        "user": {
            "login": "******",
            "id": 2644,
            "type": "User"
        },
        "state": "APPROVED",
        "author_association": "MEMBER",
    })

    del ctxt._cache["reviews"]
    del ctxt._cache["consolidated_reviews"]

    # Team conditions with no review missing
    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "default",
        "conditions": [
            "approved-reviews-by=@another-jd/my-reviewers",
            "#approved-reviews-by>=2",
        ],
        "actions": {},
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]

    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 0

    # Forbidden labels, when no label set
    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "default",
        "conditions": ["-label~=^(status/wip|status/blocked|review/need2)$"],
        "actions": {},
    }])

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]
    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 0

    # Forbidden labels, when forbiden label set
    ctxt.pull["labels"] = [{
        "id": 0,
        "color": "#1234",
        "default": False,
        "name": "status/wip"
    }]

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]
    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 1
    assert str(match.matching_rules[0].missing_conditions[0]) == (
        "-label~=^(status/wip|status/blocked|review/need2)$")

    # Forbidden labels, when other label set
    ctxt.pull["labels"] = [{
        "id": 0,
        "color": "#1234",
        "default": False,
        "name": "allowed"
    }]

    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]
    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 0

    # Test team expander
    pull_request_rules = pull_request_rule_from_list([{
        "name":
        "default",
        "conditions": ["author~=^(user1|user2|another-jd)$"],
        "actions": {},
    }])
    match = await pull_request_rules.get_pull_request_rule(ctxt)
    assert [r.name for r in match.rules] == ["default"]
    assert [r.name for r in match.matching_rules] == ["default"]
    assert match.matching_rules[0].name == "default"
    assert len(match.matching_rules[0].missing_conditions) == 0

    # branch protection
    async def client_item_with_branch_protection_enabled(url, *args, **kwargs):
        if url == "/repos/another-jd/name/branches/master":
            return {
                "protection": {
                    "enabled": True,
                    "required_status_checks": {
                        "contexts": ["awesome-ci"]
                    },
                },
            }
        raise RuntimeError(f"not handled url {url}")

    client.item.side_effect = client_item_with_branch_protection_enabled
    pull_request_rules = pull_request_rule_from_list([{
        "name": "default",
        "conditions": [],
        "actions": {
            "merge": {},
            "comment": {
                "message": "yo"
            }
        },
    }])
    match = await pull_request_rules.get_pull_request_rule(ctxt)

    assert [r.name for r in match.rules] == ["default", "default"]
    assert list(match.matching_rules[0].actions.keys()) == ["merge"]
    assert [str(c) for c in match.matching_rules[0].conditions
            ] == ["check-success=awesome-ci"]
    assert [str(c) for c in match.matching_rules[0].missing_conditions
            ] == ["check-success=awesome-ci"]
    assert list(match.matching_rules[1].actions.keys()) == ["comment"]
    assert match.matching_rules[1].conditions == []
예제 #24
0
async def test_get_mergify_config_with_defaults(
        redis_cache: utils.RedisCache) -> None:

    config = """
defaults:
  actions:
    comment:
      bot_account: foo-bot
    rebase:
      bot_account: test-bot-account
pull_request_rules:
  - name: ahah
    conditions:
    - base=master
    actions:
      comment:
        message: I love Mergify
      rebase: {}
"""

    async def item(*args, **kwargs):
        return github_types.GitHubContentFile({
            "content":
            encodebytes(config.encode()).decode(),
            "path":
            ".mergify.yml",
            "type":
            "file",
            "sha":
            "azertyu",
        })

    client = mock.Mock()
    client.item.return_value = item()

    installation = context.Installation(
        github_types.GitHubAccountIdType(0),
        github_types.GitHubLogin("foobar"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(
        installation,
        github_types.GitHubRepositoryName("xyz"),
        github_types.GitHubRepositoryIdType(0),
    )
    config_file = await repository.get_mergify_config_file()
    assert config_file is not None

    schema = get_mergify_config(config_file)
    assert isinstance(schema, dict)

    assert len(schema["pull_request_rules"].rules) == 1

    comment = schema["pull_request_rules"].rules[0].actions["comment"].config
    assert comment == {"message": "I love Mergify", "bot_account": "foo-bot"}

    rebase = schema["pull_request_rules"].rules[0].actions["rebase"].config
    assert rebase == {"bot_account": "test-bot-account"}

    config = """
defaults:
  actions:
    comment:
      message: I love Mergify
      bot_account: AutoBot
pull_request_rules:
  - name: ahah
    conditions:
    - base=master
    actions:
      comment:
        message: I really love Mergify
"""

    client = mock.Mock()
    client.item.return_value = item()

    installation = context.Installation(
        github_types.GitHubAccountIdType(0),
        github_types.GitHubLogin("foobar"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(
        installation,
        github_types.GitHubRepositoryName("xyz"),
        github_types.GitHubRepositoryIdType(0),
    )
    config_file = await repository.get_mergify_config_file()
    assert config_file is not None

    schema = get_mergify_config(config_file)
    assert isinstance(schema, dict)

    assert len(schema["pull_request_rules"].rules) == 1

    comment = schema["pull_request_rules"].rules[0].actions["comment"].config
    assert comment == {
        "message": "I really love Mergify",
        "bot_account": "AutoBot"
    }
예제 #25
0
async def test_signals(redis_cache):
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "archived":
        False,
        "url":
        "",
        "default_branch":
        github_types.GitHubRefType(""),
        "id":
        github_types.GitHubRepositoryIdType(456),
        "full_name":
        "user/ref",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "owner":
        gh_owner,
    })

    client = mock.AsyncMock()
    client.auth.get_access_token.return_value = "<token>"

    sub = subscription.Subscription(redis_cache, 0, False, "", frozenset())
    installation = context.Installation(
        gh_owner["id"],
        gh_owner["login"],
        sub,
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo["name"],
                                    gh_repo["id"])
    ctxt = await context.Context.create(
        repository,
        {
            "title": "",
            "id": github_types.GitHubPullRequestId(0),
            "maintainer_can_modify": False,
            "rebaseable": False,
            "draft": False,
            "merge_commit_sha": None,
            "labels": [],
            "number": github_types.GitHubPullRequestNumber(6),
            "commits": 1,
            "merged": True,
            "state": "closed",
            "changed_files": 1,
            "html_url": "<html_url>",
            "base": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "ref": github_types.GitHubRefType("ref"),
                "label": "",
                "repo": gh_repo,
            },
            "head": {
                "label": "",
                "sha": github_types.SHAType("old-sha-one"),
                "ref": github_types.GitHubRefType("fork"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "repo": {
                    "archived": False,
                    "url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "id": github_types.GitHubRepositoryIdType(123),
                    "full_name": "fork/other",
                    "name": github_types.GitHubRepositoryName("other"),
                    "private": False,
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
            },
            "user": {
                "login": github_types.GitHubLogin("user"),
                "id": github_types.GitHubAccountIdType(0),
                "type": "User",
                "avatar_url": "",
            },
            "merged_by": None,
            "merged_at": None,
            "mergeable_state": "clean",
        },
    )

    assert len(signals.SIGNALS) == 0
    signals.setup()
    assert len(signals.SIGNALS) == 1

    with mock.patch(
            "mergify_engine_signals.noop.Signal.__call__") as signal_method:
        await signals.send(ctxt, "action.update")
        signal_method.assert_called_once_with(ctxt, "action.update")
예제 #26
0
    async def _prepare_repo(self) -> count_seats.Seats:
        await self.setup_repo()
        await self.create_pr()
        await self.run_engine()

        # NOTE(sileht): we add active users only on the repository used for
        # recording the fixture

        repos = (await self.client_admin.get(
            url=f"{config.GITHUB_REST_API_URL}/orgs/mergifyio-testing/repos"
        )).json()
        members = (await self.client_admin.get(
            url=f"{config.GITHUB_REST_API_URL}/orgs/mergifyio-testing/members"
        )).json()
        write_users = {
            count_seats.SeatAccount(
                github_types.GitHubAccountIdType(member["id"]),
                github_types.GitHubLogin(member["login"]),
            )
            for member in members
        }
        organization = {}
        for repo in repos:
            active_users = None
            key_repo = count_seats.SeatRepository(
                github_types.GitHubRepositoryIdType(repo["id"]),
                github_types.GitHubRepositoryName(repo["name"]),
            )
            if repo["id"] == self.repository_ctxt.repo["id"]:
                active_users = {
                    count_seats.ActiveUser(
                        github_types.GitHubAccountIdType(
                            config.TESTING_MERGIFY_TEST_1_ID),
                        github_types.GitHubLogin("mergify-test1"),
                    ),
                    count_seats.ActiveUser(
                        github_types.GitHubAccountIdType(
                            config.TESTING_MERGIFY_TEST_2_ID),
                        github_types.GitHubLogin("mergify-test2"),
                    ),
                }
            write_users_used: typing.Union[typing.Set[count_seats.SeatAccount],
                                           mock.ANY]
            if repo["name"] == "functional-testing-repo":
                write_users_used = write_users
            else:
                write_users_used = mock.ANY
            organization[key_repo] = count_seats.CollaboratorsSetsT({
                "write_users":
                write_users_used,
                "active_users":
                active_users,
            })

        collaborators = {
            count_seats.SeatAccount(
                github_types.GitHubAccountIdType(config.TESTING_ORGANIZATION_ID),
                github_types.GitHubLogin(config.TESTING_ORGANIZATION_NAME),
            ):
            organization
        }

        return count_seats.Seats(collaborators)
예제 #27
0
async def test_get_commits_to_cherry_pick_rebase(
    commits: mock.PropertyMock,
    redis_cache: utils.RedisCache,
) -> None:
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "full_name":
        "user/name",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })

    c1 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c1f"),
        "parents": [],
        "commit": {
            "message": "foobar"
        },
    })
    c2 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c2"),
        "parents": [c1],
        "commit": {
            "message": "foobar"
        },
    })
    commits.return_value = [c1, c2]

    client = mock.Mock()
    client.auth.get_access_token.return_value = "<token>"
    client.items.side_effect = fake_get_github_pulls_from_sha

    installation = context.Installation(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo)
    ctxt = await context.Context.create(
        repository,
        {
            "labels": [],
            "draft": False,
            "merge_commit_sha": github_types.SHAType(""),
            "title": "",
            "commits": 1,
            "rebaseable": False,
            "maintainer_can_modify": False,
            "id": github_types.GitHubPullRequestId(0),
            "number": github_types.GitHubPullRequestNumber(6),
            "merged": True,
            "state": "closed",
            "html_url": "<html_url>",
            "changed_files": 1,
            "base": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "ref": github_types.GitHubRefType("ref"),
                "repo": {
                    "full_name": "user/ref",
                    "name": github_types.GitHubRepositoryName("name"),
                    "private": False,
                    "id": github_types.GitHubRepositoryIdType(0),
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                    "archived": False,
                    "url": "",
                    "html_url": "",
                    "default_branch": github_types.GitHubRefType(""),
                },
            },
            "head": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "ref": github_types.GitHubRefType("fork"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "repo": {
                    "full_name": "fork/other",
                    "name": github_types.GitHubRepositoryName("other"),
                    "private": False,
                    "archived": False,
                    "url": "",
                    "html_url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "id": github_types.GitHubRepositoryIdType(0),
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
            },
            "user": {
                "login": github_types.GitHubLogin("user"),
                "id": github_types.GitHubAccountIdType(0),
                "type": "User",
                "avatar_url": "",
            },
            "merged_by": None,
            "merged_at": None,
            "mergeable_state": "clean",
        },
    )

    base_branch = github_types.GitHubBranchCommitParent(
        {"sha": github_types.SHAType("base_branch")})
    rebased_c1 = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("rebased_c1"),
        "parents": [base_branch],
        "commit": {
            "message": "hello c1"
        },
    })
    rebased_c2 = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("rebased_c2"),
        "parents": [rebased_c1],
        "commit": {
            "message": "hello c2"
        },
    })

    async def fake_get_github_commit_from_sha(url, api_version=None):
        if url.endswith("/commits/rebased_c1"):
            return rebased_c1
        if url.endswith("/commits/rebased_c2"):
            return rebased_c2
        raise RuntimeError(f"Unknown URL {url}")

    client.item.side_effect = fake_get_github_commit_from_sha

    assert await duplicate_pull._get_commits_to_cherrypick(ctxt,
                                                           rebased_c2) == [
                                                               rebased_c1,
                                                               rebased_c2,
                                                           ]
예제 #28
0
async def recorder(
    request: pytest.FixtureRequest, ) -> typing.Optional[RecorderFixture]:
    is_unittest_class = request.cls is not None

    marker = request.node.get_closest_marker("recorder")
    if not is_unittest_class and marker is None:
        return None

    if is_unittest_class:
        cassette_library_dir = os.path.join(
            CASSETTE_LIBRARY_DIR_BASE,
            request.cls.__name__,
            request.node.name,
        )
    else:
        cassette_library_dir = os.path.join(
            CASSETTE_LIBRARY_DIR_BASE,
            request.node.module.__name__.replace(
                "mergify_engine.tests.functional.", "").replace(".", "/"),
            request.node.name,
        )

    # Recording stuffs
    if RECORD:
        if os.path.exists(cassette_library_dir):
            shutil.rmtree(cassette_library_dir)
        os.makedirs(cassette_library_dir)

    recorder = vcr.VCR(
        cassette_library_dir=cassette_library_dir,
        record_mode="all" if RECORD else "none",
        match_on=["method", "uri"],
        ignore_localhost=True,
        filter_headers=[
            ("Authorization", "<TOKEN>"),
            ("X-Hub-Signature", "<SIGNATURE>"),
            ("User-Agent", None),
            ("Accept-Encoding", None),
            ("Connection", None),
        ],
        before_record_response=pyvcr_response_filter,
        before_record_request=pyvcr_request_filter,
    )

    if RECORD:
        github.CachedToken.STORAGE = {}
    else:
        # Never expire token during replay
        patcher = mock.patch.object(github_app,
                                    "get_or_create_jwt",
                                    return_value="<TOKEN>")
        patcher.start()
        request.addfinalizer(patcher.stop)
        patcher = mock.patch.object(
            github.GithubAppInstallationAuth,
            "get_access_token",
            return_value="<TOKEN>",
        )
        patcher.start()
        request.addfinalizer(patcher.stop)

    # Let's start recording
    cassette = recorder.use_cassette("http.json")
    cassette.__enter__()
    request.addfinalizer(cassette.__exit__)
    record_config_file = os.path.join(cassette_library_dir, "config.json")

    if RECORD:
        with open(record_config_file, "w") as f:
            f.write(
                json.dumps(
                    RecordConfigType({
                        "organization_id":
                        config.TESTING_ORGANIZATION_ID,
                        "organization_name":
                        config.TESTING_ORGANIZATION_NAME,
                        "repository_id":
                        config.TESTING_REPOSITORY_ID,
                        "repository_name":
                        github_types.GitHubRepositoryName(
                            config.TESTING_REPOSITORY_NAME),
                        "branch_prefix":
                        datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S"),
                    })))

    with open(record_config_file, "r") as f:
        return RecorderFixture(
            typing.cast(RecordConfigType, json.loads(f.read())), recorder)
예제 #29
0
async def report(
    url: str,
) -> typing.Union[context.Context, github.AsyncGithubInstallationClient, None]:
    redis_links = redis_utils.RedisLinks(max_idle_time=0)

    try:
        owner_login, repo, pull_number = _url_parser(url)
    except ValueError:
        print(f"{url} is not valid")
        return None

    try:
        installation_json = await github.get_installation_from_login(
            owner_login)
        client = github.aget_client(installation_json)
    except exceptions.MergifyNotInstalled:
        print(f"* Mergify is not installed on account {owner_login}")
        return None

    # Do a dumb request just to authenticate
    await client.get("/")

    print(f"* INSTALLATION ID: {installation_json['id']}")

    if repo is None:
        slug = None
    else:
        slug = owner_login + "/" + repo

    owner_id = installation_json["account"]["id"]
    cached_sub = await subscription.Subscription.get_subscription(
        redis_links.cache, owner_id)
    db_sub = await subscription.Subscription._retrieve_subscription_from_db(
        redis_links.cache, owner_id)

    cached_tokens = await user_tokens.UserTokens.get(redis_links.cache,
                                                     owner_id)
    if config.SAAS_MODE:
        db_tokens = typing.cast(
            user_tokens.UserTokens,
            (await user_tokens.UserTokensSaas._retrieve_from_db(
                redis_links.cache, owner_id)),
        )
    else:
        db_tokens = cached_tokens

    print("* Features (db):")
    for v in sorted(f.value for f in db_sub.features):
        print(f"  - {v}")
    print("* Features (cache):")
    for v in sorted(f.value for f in cached_sub.features):
        print(f"  - {v}")

    installation = context.Installation(installation_json, cached_sub, client,
                                        redis_links)

    await report_dashboard_synchro(installation.installation["id"], cached_sub,
                                   cached_tokens, "ENGINE-CACHE", slug)
    await report_dashboard_synchro(installation.installation["id"], db_sub,
                                   db_tokens, "DASHBOARD", slug)

    await report_worker_status(owner_login)

    if repo is not None:
        repository = await installation.get_repository_by_name(repo)

        print(
            f"* REPOSITORY IS {'PRIVATE' if repository.repo['private'] else 'PUBLIC'}"
        )

        print(f"* DEFAULT BRANCH: {repository.repo['default_branch']}")

        print("* CONFIGURATION:")
        mergify_config = None
        config_file = await repository.get_mergify_config_file()
        if not config_file:
            print(".mergify.yml is missing")
        else:
            print(f"Config filename: {config_file['path']}")
            print(config_file["decoded_content"])
            try:
                mergify_config = await repository.get_mergify_config()
            except rules.InvalidRules as e:  # pragma: no cover
                print(f"configuration is invalid {str(e)}")

        if pull_number is None:
            async for branch in typing.cast(
                    typing.AsyncGenerator[github_types.GitHubBranch, None],
                    client.items(
                        f"/repos/{owner_login}/{repo}/branches",
                        resource_name="branches",
                        page_limit=100,
                    ),
            ):
                q = merge_train.Train(repository, branch["name"])
                await q.load()
                await report_queue("TRAIN", q)

        else:
            repository = await installation.get_repository_by_name(
                github_types.GitHubRepositoryName(repo))
            try:
                ctxt = await repository.get_pull_request_context(
                    github_types.GitHubPullRequestNumber(int(pull_number)))
            except http.HTTPNotFound:
                print(f"Pull request `{url}` does not exist")
                return client

            # FIXME queues could also be printed if no pull number given
            # TODO(sileht): display train if any
            q = await merge_train.Train.from_context(ctxt)
            print(
                f"* TRAIN: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            print("* PULL REQUEST:")
            pr_data = await ctxt.pull_request.items()
            pprint.pprint(pr_data, width=160)

            is_behind = await ctxt.is_behind
            print(f"is_behind: {is_behind}")

            print(f"mergeable_state: {ctxt.pull['mergeable_state']}")

            print("* MERGIFY LAST CHECKS:")
            for c in await ctxt.pull_engine_check_runs:
                print(
                    f"[{c['name']}]: {c['conclusion']} | {c['output'].get('title')} | {c['html_url']}"
                )
                print("> " + "\n> ".join(
                    ("No Summary", ) if c["output"]["summary"] is None else
                    c["output"]["summary"].split("\n")))

            if mergify_config is not None:
                print("* MERGIFY LIVE MATCHES:")
                pull_request_rules = mergify_config["pull_request_rules"]
                match = await pull_request_rules.get_pull_request_rule(ctxt)
                summary_title, summary = await actions_runner.gen_summary(
                    ctxt, pull_request_rules, match)
                print(f"[Summary]: success | {summary_title}")
                print("> " + "\n> ".join(summary.strip().split("\n")))

            return ctxt

    return client
async def prepare_context(client, redis_cache, subscribed=True):
    sub = subscription.Subscription(
        redis_cache,
        123,
        subscribed,
        "sub or not to sub",
        frozenset(
            getattr(subscription.Features, f)
            for f in subscription.Features.__members__)
        if subscribed else frozenset(),
    )

    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "full_name":
        "user/name",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })
    installation = context.Installation(123, "Mergifyio", sub, client,
                                        redis_cache)
    repository = context.Repository(installation, gh_repo)
    return await context.Context.create(
        repository,
        {
            "id": 12345,
            "number": 123,
            "state": None,
            "mergeable_state": "ok",
            "merged_by": None,
            "merged": None,
            "merged_at": None,
            "user": {
                "login": "******"
            },
            "requested_reviewers": [{
                "login": "******"
            }, {
                "login": "******"
            }],
            "requested_teams": [{
                "slug": "foobar"
            }, {
                "slug": "foobaz"
            }],
            "base": {
                "sha": "sha",
                "ref": "main",
                "user": {
                    "login": {
                        "Mergifyio",
                    },
                },
                "repo": {
                    "full_name": "Mergifyio/demo",
                    "name": "demo",
                    "private": False,
                    "owner": {
                        "login": "******",
                    },
                },
            },
        },
    )