示例#1
0
async def test_client_installation_HTTP_301(
        httpserver: httpserver.HTTPServer) -> None:
    httpserver.expect_request("/users/owner/installation").respond_with_data(
        status=301,
        headers={
            "Location": httpserver.url_for("/repositories/12345/installation")
        },
    )
    httpserver.expect_request(
        "/repositories/12345/installation").respond_with_json(
            {"message": "Repository not found"}, status=404)
    with mock.patch(
            "mergify_engine.config.GITHUB_API_URL",
            httpserver.url_for("/")[:-1],
    ):
        async with github.AsyncGithubInstallationClient(
                github.get_auth(github_types.GitHubLogin("owner"))) as client:
            with pytest.raises(exceptions.MergifyNotInstalled):
                await client.get(httpserver.url_for("/"))

    assert len(httpserver.log) == 2

    httpserver.check_assertions()
示例#2
0
async def test_client_installation_HTTP_500(
        httpserver: httpserver.HTTPServer) -> None:
    httpserver.expect_request("/users/owner/installation").respond_with_data(
        "This is an 5XX error", status=500)
    with mock.patch(
            "mergify_engine.config.GITHUB_API_URL",
            httpserver.url_for("/")[:-1],
    ):
        async with github.AsyncGithubInstallationClient(
                github.get_auth(github_types.GitHubLogin("owner"))) as client:
            with pytest.raises(http.HTTPServerSideError) as exc_info:
                await client.get(httpserver.url_for("/"))

    # 5 retries
    assert len(httpserver.log) == 5

    assert exc_info.value.message == "This is an 5XX error"
    assert exc_info.value.status_code == 500
    assert exc_info.value.response.status_code == 500
    assert str(exc_info.value.request.url) == httpserver.url_for(
        "/users/owner/installation")

    httpserver.check_assertions()
示例#3
0
async def test_client_user_token(httpserver: httpserver.HTTPServer) -> None:
    with mock.patch(
            "mergify_engine.config.GITHUB_API_URL",
            httpserver.url_for("/")[:-1],
    ):
        httpserver.expect_request("/user").respond_with_json({
            "login": "******",
            "id": 12345,
        })
        httpserver.expect_request("/",
                                  headers={
                                      "Authorization": "token <user-token>"
                                  }).respond_with_json({"work": True},
                                                       status=200)

        async with github.AsyncGithubInstallationClient(
                github.get_auth(github_types.GitHubLogin("owner"))) as client:
            ret = await client.get(httpserver.url_for("/"),
                                   oauth_token="<user-token>"
                                   )  # type: ignore[call-arg]
            assert ret.json()["work"]

    assert len(httpserver.log) == 2
示例#4
0
 def github_aclient(owner_name=None, owner_id=None, auth=None):
     return github.AsyncGithubInstallationClient(
         get_auth(owner_name, owner_id, auth))
示例#5
0
    async def asyncSetUp(self):
        super(FunctionalTestBase, self).setUp()
        self.existing_labels: typing.List[str] = []
        self.protected_branches: typing.Set[str] = set()
        self.pr_counter: int = 0
        self.git_counter: int = 0
        self.cassette_library_dir = os.path.join(CASSETTE_LIBRARY_DIR_BASE,
                                                 self.__class__.__name__,
                                                 self._testMethodName)

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

        self.recorder = vcr.VCR(
            cassette_library_dir=self.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=self.response_filter,
        )

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

            # NOTE(sileht): httpx pyvcr stubs does not replay auth_flow as it directly patch client.send()
            # So anything occurring during auth_flow have to be mocked during replay
            def get_auth(owner_name=None, owner_id=None, auth=None):
                if auth is None:
                    auth = github.get_auth(owner_name, owner_id)
                    auth.installation = {
                        "id": config.INSTALLATION_ID,
                    }
                    auth.permissions_need_to_be_updated = False
                    auth.owner_id = config.TESTING_ORGANIZATION_ID
                    auth.owner = config.TESTING_ORGANIZATION
                return auth

            def github_aclient(owner_name=None, owner_id=None, auth=None):
                return github.AsyncGithubInstallationClient(
                    get_auth(owner_name, owner_id, auth))

            mock.patch.object(github, "aget_client", github_aclient).start()

        mock.patch.object(branch_updater.gitter, "Gitter",
                          self.get_gitter).start()
        mock.patch.object(duplicate_pull.gitter, "Gitter",
                          self.get_gitter).start()

        if not RECORD:
            # NOTE(sileht): Don't wait exponentialy during replay
            mock.patch.object(context.Context._ensure_complete.retry, "wait",
                              None).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        branch_prefix_path = os.path.join(self.cassette_library_dir,
                                          "branch_prefix")

        if RECORD:
            self.BRANCH_PREFIX = datetime.datetime.utcnow().strftime(
                "%Y%m%d%H%M%S")
            with open(branch_prefix_path, "w") as f:
                f.write(self.BRANCH_PREFIX)
        else:
            with open(branch_prefix_path, "r") as f:
                self.BRANCH_PREFIX = f.read()

        self.master_branch_name = self.get_full_branch_name("master")

        self.git = self.get_gitter(LOG)
        await self.git.init()
        self.addAsyncCleanup(self.git.cleanup)

        await root.startup()
        self.app = httpx.AsyncClient(app=root.app, base_url="http://localhost")

        await self.clear_redis_cache()
        self.redis_cache = utils.create_aredis_for_cache(max_idle_time=0)
        self.subscription = subscription.Subscription(
            self.redis_cache,
            config.TESTING_ORGANIZATION_ID,
            self.SUBSCRIPTION_ACTIVE,
            "You're not nice",
            frozenset(
                getattr(subscription.Features, f)
                for f in subscription.Features.__members__)
            if self.SUBSCRIPTION_ACTIVE else frozenset(),
        )
        await self.subscription._save_subscription_to_cache()
        self.user_tokens = user_tokens.UserTokens(
            self.redis_cache,
            config.TESTING_ORGANIZATION_ID,
            {
                "mergify-test1": config.ORG_ADMIN_GITHUB_APP_OAUTH_TOKEN,
                "mergify-test3": config.ORG_USER_PERSONAL_TOKEN,
            },
        )
        await self.user_tokens.save_to_cache()

        # Let's start recording
        cassette = self.recorder.use_cassette("http.json")
        cassette.__enter__()
        self.addCleanup(cassette.__exit__)

        self.client_integration = github.aget_client(
            config.TESTING_ORGANIZATION, config.TESTING_ORGANIZATION_ID)
        self.client_admin = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=config.ORG_ADMIN_PERSONAL_TOKEN))
        self.client_fork = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=self.FORK_PERSONAL_TOKEN))
        self.addAsyncCleanup(self.client_integration.aclose)
        self.addAsyncCleanup(self.client_admin.aclose)
        self.addAsyncCleanup(self.client_fork.aclose)

        await self.client_admin.item("/user")
        await self.client_fork.item("/user")
        if RECORD:
            assert self.client_admin.auth.owner == "mergify-test1"
            assert self.client_fork.auth.owner == "mergify-test2"
        else:
            self.client_admin.auth.owner = "mergify-test1"
            self.client_fork.auth.owner = "mergify-test2"

        self.url_main = f"/repos/mergifyio-testing/{self.REPO_NAME}"
        self.url_fork = f"/repos/{self.client_fork.auth.owner}/{self.REPO_NAME}"
        self.git_main = f"{config.GITHUB_URL}/mergifyio-testing/{self.REPO_NAME}"
        self.git_fork = (
            f"{config.GITHUB_URL}/{self.client_fork.auth.owner}/{self.REPO_NAME}"
        )

        self.installation_ctxt = context.Installation(
            config.TESTING_ORGANIZATION_ID,
            config.TESTING_ORGANIZATION,
            self.subscription,
            self.client_integration,
            self.redis_cache,
        )
        self.repository_ctxt = context.Repository(self.installation_ctxt,
                                                  self.REPO_NAME, self.REPO_ID)

        real_get_subscription = subscription.Subscription.get_subscription

        async def fake_retrieve_subscription_from_db(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return self.subscription
            return subscription.Subscription(
                redis_cache,
                owner_id,
                False,
                "We're just testing",
                set(),
            )

        async def fake_subscription(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return await real_get_subscription(redis_cache, owner_id)
            return subscription.Subscription(
                redis_cache,
                owner_id,
                False,
                "We're just testing",
                set(),
            )

        mock.patch(
            "mergify_engine.subscription.Subscription._retrieve_subscription_from_db",
            side_effect=fake_retrieve_subscription_from_db,
        ).start()

        mock.patch(
            "mergify_engine.subscription.Subscription.get_subscription",
            side_effect=fake_subscription,
        ).start()

        async def fake_retrieve_user_tokens_from_db(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return self.user_tokens
            return user_tokens.UserTokens(redis_cache, owner_id, {})

        real_get_user_tokens = user_tokens.UserTokens.get

        async def fake_user_tokens(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return await real_get_user_tokens(redis_cache, owner_id)
            return user_tokens.UserTokens(redis_cache, owner_id, {})

        mock.patch(
            "mergify_engine.user_tokens.UserTokens._retrieve_from_db",
            side_effect=fake_retrieve_user_tokens_from_db,
        ).start()

        mock.patch(
            "mergify_engine.user_tokens.UserTokens.get",
            side_effect=fake_user_tokens,
        ).start()

        self._event_reader = EventReader(self.app)
        await self._event_reader.drain()

        # NOTE(sileht): Prepare a fresh redis
        await self.clear_redis_stream()
示例#6
0
 async def github_aclient(owner=None, auth=None):
     return github.AsyncGithubInstallationClient(
         get_auth(owner, auth))
示例#7
0
async def test_configuration_initial(
    github_server: httpserver.HTTPServer,
    redis_cache: utils.RedisCache,
) -> None:
    github_server.expect_oneshot_request(
        f"{BASE_URL}/pulls/1", ).respond_with_json(
            GH_PULL,
            status=200,
        )

    github_server.expect_oneshot_request(
        f"{BASE_URL}/contents/.mergify.yml", ).respond_with_data(status=404)

    github_server.expect_oneshot_request(
        f"{BASE_URL}/contents/.mergify/config.yml", ).respond_with_data(
            status=404)

    github_server.expect_oneshot_request(
        f"{BASE_URL}/contents/.github/mergify.yml", ).respond_with_data(
            status=404)

    github_server.expect_oneshot_request(
        f"{BASE_URL}/contents/.mergify.yml",
        query_string={
            "ref": GH_PULL["head"]["sha"]
        },
    ).respond_with_json(
        github_types.GitHubContentFile({
            "type":
            "file",
            "content":
            FAKE_MERGIFY_CONTENT,
            "path":
            ".mergify.yml",
            "sha":
            github_types.SHAType("739e5ec79e358bae7a150941a148b4131233ce2c"),
        }),
        status=200,
    )

    github_server.expect_oneshot_request(
        f"{BASE_URL}/commits/{GH_PULL['head']['sha']}/check-runs"
    ).respond_with_json({"check_runs": []}, status=200)

    github_server.expect_oneshot_request(f"{BASE_URL}/check-runs",
                                         method="POST").respond_with_json(
                                             {}, status=200)

    async with github.AsyncGithubInstallationClient(
            github.get_auth(GH_OWNER["login"])) as client:
        installation = context.Installation(
            GH_OWNER["id"],
            GH_OWNER["login"],
            subscription.Subscription(redis_cache, 0, False, "", frozenset(),
                                      0),
            client,
            redis_cache,
        )
        repository = context.Repository(installation, GH_REPO)
        ctxt = await repository.get_pull_request_context(
            github_types.GitHubPullRequestNumber(1))

        main_config_file = await repository.get_mergify_config_file()
        assert main_config_file is None

        changed = await engine._check_configuration_changes(
            ctxt, main_config_file)
        assert changed

    github_server.check_assertions()
示例#8
0
async def test_configuration_check_not_needed_with_configuration_deleted(
    github_server: respx.MockRouter, redis_cache: utils.RedisCache
) -> None:
    github_server.get("/user/12345/installation").respond(
        200,
        json={
            "id": 12345,
            "permissions": {
                "checks": "write",
                "contents": "write",
                "pull_requests": "write",
            },
            "target_type": GH_OWNER["type"],
            "account": GH_OWNER,
        },
    )
    github_server.get(f"{BASE_URL}/pulls/1",).respond(
        200,
        json=typing.cast(typing.Dict[typing.Any, typing.Any], GH_PULL),
    )
    github_server.get(f"{BASE_URL}/contents/.mergify.yml").respond(
        200,
        json=typing.cast(
            typing.Dict[typing.Any, typing.Any],
            github_types.GitHubContentFile(
                {
                    "type": "file",
                    "content": FAKE_MERGIFY_CONTENT,
                    "path": ".mergify.yml",
                    "sha": github_types.SHAType(
                        "739e5ec79e358bae7a150941a148b4131233ce2c"
                    ),
                }
            ),
        ),
    )

    # Summary is present, no need to redo the check
    github_server.get(
        f"{BASE_URL}/commits/{GH_PULL['head']['sha']}/check-runs"
    ).respond(
        200,
        json={"check_runs": [SUMMARY_CHECK, CONFIGURATION_DELETED_CHECK]},
    )

    installation_json = await github.get_installation_from_account_id(GH_OWNER["id"])
    async with github.AsyncGithubInstallationClient(
        github.GithubAppInstallationAuth(installation_json)
    ) as client:
        installation = context.Installation(
            installation_json,
            subscription.Subscription(
                redis_cache,
                0,
                "",
                frozenset([subscription.Features.PUBLIC_REPOSITORY]),
                0,
            ),
            client,
            redis_cache,
            mock.Mock(),
        )
        repository = context.Repository(installation, GH_REPO)
        ctxt = await repository.get_pull_request_context(
            github_types.GitHubPullRequestNumber(1)
        )

        main_config_file = await repository.get_mergify_config_file()
        changed = await engine._check_configuration_changes(ctxt, main_config_file)
        assert changed
示例#9
0
async def test_configuration_initial(
    github_server: respx.MockRouter, redis_cache: utils.RedisCache
) -> None:
    github_server.get("/user/12345/installation").respond(
        200,
        json={
            "id": 12345,
            "permissions": {
                "checks": "write",
                "contents": "write",
                "pull_requests": "write",
            },
            "target_type": GH_OWNER["type"],
            "account": GH_OWNER,
        },
    )
    github_server.get(f"{BASE_URL}/pulls/1",).respond(
        200,
        json=typing.cast(typing.Dict[typing.Any, typing.Any], GH_PULL),
    )

    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.mergify.yml")
        & ~respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(404)

    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.mergify/config.yml")
        & ~respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(404)

    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.github/mergify.yml")
        & ~respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(404)

    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.mergify.yml")
        & respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(
        200,
        json=typing.cast(
            typing.Dict[typing.Any, typing.Any],
            github_types.GitHubContentFile(
                {
                    "type": "file",
                    "content": FAKE_MERGIFY_CONTENT,
                    "path": ".mergify.yml",
                    "sha": github_types.SHAType(
                        "739e5ec79e358bae7a150941a148b4131233ce2c"
                    ),
                }
            ),
        ),
    )
    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.github/mergify.yml")
        & respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(404)
    github_server.route(
        respx.patterns.M(method="GET", path=f"{BASE_URL}/contents/.mergify/config.yml")
        & respx.patterns.M(params__contains={"ref": GH_PULL["merge_commit_sha"]})
    ).respond(404)

    github_server.get(
        f"{BASE_URL}/commits/{GH_PULL['head']['sha']}/check-runs"
    ).respond(200, json={"check_runs": []})

    github_server.post(f"{BASE_URL}/check-runs").respond(
        200, json=typing.cast(typing.Dict[typing.Any, typing.Any], CHECK_RUN)
    )

    installation_json = await github.get_installation_from_account_id(GH_OWNER["id"])
    async with github.AsyncGithubInstallationClient(
        github.GithubAppInstallationAuth(installation_json)
    ) as client:
        installation = context.Installation(
            installation_json,
            subscription.Subscription(
                redis_cache,
                0,
                "",
                frozenset([subscription.Features.PUBLIC_REPOSITORY]),
                0,
            ),
            client,
            redis_cache,
            mock.Mock(),
        )
        repository = context.Repository(installation, GH_REPO)
        ctxt = await repository.get_pull_request_context(
            github_types.GitHubPullRequestNumber(1)
        )

        main_config_file = await repository.get_mergify_config_file()
        assert main_config_file is None

        changed = await engine._check_configuration_changes(ctxt, main_config_file)
        assert changed
示例#10
0
    async def asyncSetUp(self) -> None:
        super(FunctionalTestBase, self).setUp()

        # NOTE(sileht): don't preempted bucket consumption
        # Otherwise preemption doesn't occur at the same moment during record
        # and replay. Making some tests working during record and failing
        # during replay.
        config.BUCKET_PROCESSING_MAX_SECONDS = 100000

        config.API_ENABLE = True

        self.existing_labels: typing.List[str] = []
        self.pr_counter: int = 0
        self.git_counter: int = 0

        mock.patch.object(branch_updater.gitter, "Gitter",
                          self.get_gitter).start()
        mock.patch.object(duplicate_pull.gitter, "Gitter",
                          self.get_gitter).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        self.main_branch_name = self.get_full_branch_name("main")

        self.git = self.get_gitter(LOG)
        await self.git.init()
        self.addAsyncCleanup(self.git.cleanup)

        self.redis_links = redis_utils.RedisLinks(max_idle_time=0)
        await self.clear_redis()

        installation_json = await github.get_installation_from_account_id(
            config.TESTING_ORGANIZATION_ID)
        self.client_integration = github.aget_client(installation_json)
        self.client_admin = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=config.ORG_ADMIN_PERSONAL_TOKEN,
                                        ))
        self.client_fork = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=self.FORK_PERSONAL_TOKEN, ))
        self.addAsyncCleanup(self.client_integration.aclose)
        self.addAsyncCleanup(self.client_admin.aclose)
        self.addAsyncCleanup(self.client_fork.aclose)

        await self.client_admin.item("/user")
        await self.client_fork.item("/user")

        self.url_origin = (
            f"/repos/mergifyio-testing/{self.RECORD_CONFIG['repository_name']}"
        )
        self.url_fork = f"/repos/mergify-test2/{self.RECORD_CONFIG['repository_name']}"
        self.git_origin = f"{config.GITHUB_URL}/mergifyio-testing/{self.RECORD_CONFIG['repository_name']}"
        self.git_fork = (
            f"{config.GITHUB_URL}/mergify-test2/{self.RECORD_CONFIG['repository_name']}"
        )

        self.installation_ctxt = context.Installation(
            installation_json,
            self.subscription,
            self.client_integration,
            self.redis_links,
        )
        self.repository_ctxt = await self.installation_ctxt.get_repository_by_id(
            github_types.GitHubRepositoryIdType(
                self.RECORD_CONFIG["repository_id"]))

        # NOTE(sileht): We mock this method because when we replay test, the
        # timing maybe not the same as when we record it, making the formatted
        # elapsed time different in the merge queue summary.
        def fake_pretty_datetime(dt: datetime.datetime) -> str:
            return "<fake_pretty_datetime()>"

        mock.patch(
            "mergify_engine.date.pretty_datetime",
            side_effect=fake_pretty_datetime,
        ).start()

        self._event_reader = EventReader(self.app,
                                         self.RECORD_CONFIG["repository_id"])
        await self._event_reader.drain()

        # Track when worker work
        real_consume_method = worker.StreamProcessor.consume

        self.worker_concurrency_works = 0

        async def tracked_consume(
            inner_self: worker.StreamProcessor,
            bucket_org_key: worker_lua.BucketOrgKeyType,
            owner_id: github_types.GitHubAccountIdType,
            owner_login_for_tracing: github_types.GitHubLoginForTracing,
        ) -> None:
            self.worker_concurrency_works += 1
            try:
                await real_consume_method(inner_self, bucket_org_key, owner_id,
                                          owner_login_for_tracing)
            finally:
                self.worker_concurrency_works -= 1

        worker.StreamProcessor.consume = tracked_consume  # type: ignore[assignment]

        def cleanup_consume() -> None:
            worker.StreamProcessor.consume = real_consume_method  # type: ignore[assignment]

        self.addCleanup(cleanup_consume)