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()
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()
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
def github_aclient(owner_name=None, owner_id=None, auth=None): return github.AsyncGithubInstallationClient( get_auth(owner_name, owner_id, auth))
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()
async def github_aclient(owner=None, auth=None): return github.AsyncGithubInstallationClient( get_auth(owner, auth))
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()
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
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
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)