def test_github_client(self): rules = { "pull_request_rules": [{ "name": "simulator", "conditions": [f"base!={self.master_branch_name}"], "actions": { "merge": {} }, }] } other_branch = self.get_full_branch_name("other") self.setup_repo(yaml.dump(rules), test_branches=[other_branch]) p1, _ = self.create_pr() p2, _ = self.create_pr() self.create_pr(base=other_branch) client = github.get_client(self.o_integration.login) url = f"/repos/{self.o_integration.login}/{self.r_o_integration.name}/pulls" pulls = list(client.items(url)) self.assertEqual(3, len(pulls)) pulls = list(client.items(url, per_page=1)) self.assertEqual(3, len(pulls)) pulls = list(client.items(url, per_page=1, page=2)) self.assertEqual(2, len(pulls)) pulls = list(client.items(url, base=other_branch, state="all")) self.assertEqual(1, len(pulls)) pulls = list(client.items(url, base="unknown")) self.assertEqual(0, len(pulls)) pull = client.item(f"{url}/{p1.number}") self.assertEqual(p1.number, pull["number"]) pull = client.item(f"{url}/{p2.number}") self.assertEqual(p2.number, pull["number"]) with self.assertRaises(http.HTTPStatusError) as ctxt: client.item(f"{url}/10000000000") self.assertEqual(404, ctxt.exception.response.status_code)
def test_github_client(self): rules = { "pull_request_rules": [{ "name": "simulator", "conditions": ["base!=master"], "actions": { "merge": {} }, }] } self.setup_repo(yaml.dump(rules), test_branches=["other"]) self.create_pr() self.create_pr() self.create_pr(base="other") installation = github.get_installation(self.o_integration.login, self.r_o_integration.name) client = github.get_client(self.o_integration.login, self.r_o_integration.name, installation) pulls = list(client.items("pulls")) self.assertEqual(2, len(pulls)) pulls = list(client.items("pulls", per_page=1)) self.assertEqual(2, len(pulls)) pulls = list(client.items("pulls", per_page=1, page=2)) self.assertEqual(1, len(pulls)) pulls = list(client.items("pulls", base="other", state="all")) self.assertEqual(1, len(pulls)) pulls = list(client.items("pulls", base="unknown")) self.assertEqual(0, len(pulls)) pull = client.item("pulls/1") self.assertEqual(1, pull["number"]) pull = client.item("pulls/2") self.assertEqual(2, pull["number"]) with self.assertRaises(httpx.HTTPError) as ctxt: client.item("pulls/4") self.assertEqual(404, ctxt.exception.response.status_code)
def PullRequestUrl(v): _, owner, repo, _, pull_number = urlsplit(v).path.split("/") pull_number = int(pull_number) try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: raise PullRequestUrlInvalid( message="Mergify not installed on repository '%s'" % owner) with github.get_client(owner, repo, installation) as client: try: data = client.item(f"pulls/{pull_number}") except httpx.HTTPNotFound: raise PullRequestUrlInvalid( message=("Pull request '%s' not found" % v)) return mergify_context.MergifyContext(client, data)
def run_engine( owner: str, repo: str, pull_number: int, sources: typing.List[context.T_PayloadEventSource], ) -> None: logger = daiquiri.getLogger(__name__, gh_repo=repo, gh_owner=owner, gh_pull=pull_number) logger.debug("engine in thread start") try: result = asyncio.run( get_pull_for_engine(owner, repo, pull_number, logger)) if result: subscription, pull = result with github.get_client(owner) as client: engine.run(client, pull, subscription, sources) finally: logger.debug("engine in thread end")
def job_refresh(owner, repo, kind, ref=None, action="user"): LOG.info("job refresh", kind=kind, ref=ref, gh_owner=owner, gh_repo=repo) try: client = github.get_client(owner, repo) except httpx.HTTPNotFound as e: LOG.warning( "mergify not installed", kind=kind, ref=ref, gh_owner=owner, gh_repo=repo, error=str(e), ) return if kind == "repo": pulls = list(client.items("pulls")) elif kind == "branch": pulls = list(client.items("pulls", base=ref)) elif kind == "pull": pulls = [client.item(f"pulls/{ref}")] else: raise RuntimeError("Invalid kind") for p in pulls: # Mimic the github event format data = { "action": action, "repository": p["base"]["repo"], "installation": { "id": client.installation_id }, "pull_request": p, "sender": { "login": "******" }, } github_events.job_filter_and_dispatch.s("refresh", str(uuid.uuid4()), data).apply_async()
def test_client_401_raise_ratelimit(httpserver): owner = "owner" repo = "repo" httpserver.expect_request("/repos/owner/repo/installation").respond_with_json( { "id": 12345, "target_type": "User", "permissions": { "checks": "write", "contents": "write", "pull_requests": "write", }, "account": {"login": "******", "id": 12345}, } ) httpserver.expect_request( "/app/installations/12345/access_tokens" ).respond_with_json({"token": "<token>", "expires_at": "2100-12-31T23:59:59Z"}) httpserver.expect_oneshot_request("/rate_limit").respond_with_json( {"resources": {"core": {"remaining": 5000, "reset": 1234567890}}} ) httpserver.expect_oneshot_request("/repos/owner/repo/pull/1").respond_with_json( {"message": "quota !"}, status=403 ) httpserver.expect_oneshot_request("/rate_limit").respond_with_json( {"resources": {"core": {"remaining": 0, "reset": 1234567890}}} ) with mock.patch( "mergify_engine.config.GITHUB_API_URL", httpserver.url_for("/")[:-1], ): client = github.get_client(owner, repo) with pytest.raises(exceptions.RateLimited): client.item("pull/1") httpserver.check_assertions()
def process(self): pull_numbers = self.get_pulls() self.log.info("%d pulls queued", len(pull_numbers), queue=list(pull_numbers)) if not pull_numbers: return pull_number = pull_numbers[0] with github.get_client(self.owner) as client: ctxt = None try: sub = asyncio.run( subscription.Subscription.get_subscription(client.auth.owner_id) ) data = client.item( f"/repos/{self.owner}/{self.repo}/pulls/{pull_number}" ) ctxt = context.Context(client, data, sub) if ctxt.pull["base"]["ref"] != self.ref: ctxt.log.info( "pull request base branch have changed", old_branch=self.ref, new_branch=ctxt.pull["base"]["ref"], ) self.move_pull_to_new_base_branch( ctxt.pull["number"], self.get_queue(ctxt.pull["base"]["ref"]), ) elif ctxt.pull["state"] == "closed" or ctxt.is_behind: # NOTE(sileht): Pick up this pull request and rebase it again # or update its status and remove it from the queue ctxt.log.info( "pull request needs to be updated again or has been closed", ) self.handle_first_pull_in_queue(ctxt) else: # NOTE(sileht): Pull request has not been merged or cancelled # yet wait next loop ctxt.log.info("pull request checks are still in progress") except Exception as exc: # pragma: no cover log = self.log if ctxt is None else ctxt.log if exceptions.should_be_ignored(exc): log.info( "Fail to process merge queue, remove the pull request from the queue", exc_info=True, ) self.remove_pull(ctxt.pull["number"]) elif exceptions.need_retry(exc): log.info("Fail to process merge queue, need retry", exc_info=True) if isinstance(exc, exceptions.MergeableStateUnknown): # NOTE(sileht): We need GitHub to recompute the state here (by # merging something else for example), so move it to the end self._move_pull_at_end(pull_number) else: log.error("Fail to process merge queue", exc_info=True) self._move_pull_at_end(pull_number)
def setUp(self): super(FunctionalTestBase, self).setUp() self.existing_labels = [] self.pr_counter = 0 self.git_counter = 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"], filter_headers=[ ("Authorization", "<TOKEN>"), ("X-Hub-Signature", "<SIGNATURE>"), ("User-Agent", None), ("Accept-Encoding", None), ("Connection", None), ], before_record_response=self.response_filter, custom_patches=((pygithub.MainClass, "HTTPSConnection", vcr.stubs.VCRHTTPSConnection), ), ) 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=None, auth=None): if auth is None: auth = github.get_auth(owner) auth.installation = { "id": config.INSTALLATION_ID, } auth.permissions_need_to_be_updated = False auth.owner_id = config.TESTING_ORGANIZATION_ID return auth async def github_aclient(owner=None, auth=None): return github.AsyncGithubInstallationClient( get_auth(owner, auth)) def github_client(owner=None, auth=None): return github.GithubInstallationClient(get_auth(owner, auth)) mock.patch.object(github, "get_client", github_client).start() mock.patch.object(github, "aget_client", github_aclient).start() with open(engine.mergify_rule_path, "r") as f: engine.MERGIFY_RULE = yaml.safe_load(f.read().replace( "mergify[bot]", "mergify-test[bot]")) mock.patch.object(branch_updater.utils, "Gitter", self.get_gitter).start() mock.patch.object(duplicate_pull.utils, "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) self.addCleanup(self.git.cleanup) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(web.startup()) self.app = testclient.TestClient(web.app) # NOTE(sileht): Prepare a fresh redis self.redis_stream = redis.StrictRedis.from_url(config.STREAM_URL, decode_responses=False) self.redis_stream.flushall() self.redis_cache = utils.get_redis_for_cache() self.redis_cache.flushall() self.subscription = subscription.Subscription( config.INSTALLATION_ID, self.SUBSCRIPTION_ACTIVE, "You're not nice", { "mergify-test1": config.ORG_ADMIN_GITHUB_APP_OAUTH_TOKEN, "mergify-test3": config.ORG_USER_PERSONAL_TOKEN, }, frozenset( getattr(subscription.Features, f) for f in subscription.Features.__members__) if self.SUBSCRIPTION_ACTIVE else frozenset(), ) self.loop.run_until_complete( self.subscription.save_subscription_to_cache()) # Let's start recording cassette = self.recorder.use_cassette("http.json") cassette.__enter__() self.addCleanup(cassette.__exit__) integration = pygithub.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY) self.installation_token = integration.get_access_token( config.INSTALLATION_ID).token base_url = config.GITHUB_API_URL self.g_integration = pygithub.Github(self.installation_token, base_url=base_url) self.g_admin = pygithub.Github(config.ORG_ADMIN_PERSONAL_TOKEN, base_url=base_url) self.g_fork = pygithub.Github(self.FORK_PERSONAL_TOKEN, base_url=base_url) self.o_admin = self.g_admin.get_organization( config.TESTING_ORGANIZATION) self.o_integration = self.g_integration.get_organization( config.TESTING_ORGANIZATION) self.u_fork = self.g_fork.get_user() assert self.o_admin.login == "mergifyio-testing" assert self.o_integration.login == "mergifyio-testing" assert self.u_fork.login in ["mergify-test2", "mergify-test3"] self.r_o_admin = self.o_admin.get_repo(self.REPO_NAME) self.r_o_integration = self.o_integration.get_repo(self.REPO_NAME) self.r_fork = self.u_fork.get_repo(self.REPO_NAME) self.url_main = f"{config.GITHUB_URL}/{self.r_o_integration.full_name}" self.url_fork = ( f"{config.GITHUB_URL}/{self.u_fork.login}/{self.r_o_integration.name}" ) self.cli_integration = github.get_client(config.TESTING_ORGANIZATION, ) real_get_subscription = subscription.Subscription.get_subscription async def fake_retrieve_subscription_from_db(owner_id): if owner_id == config.TESTING_ORGANIZATION_ID: return self.subscription return subscription.Subscription( owner_id, False, "We're just testing", {}, set(), ) async def fake_subscription(owner_id): if owner_id == config.TESTING_ORGANIZATION_ID: return await real_get_subscription(owner_id) return subscription.Subscription( 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() mock.patch( "github.MainClass.Installation.Installation.get_repos", return_value=[self.r_o_integration], ).start() self._event_reader = EventReader(self.app) self._event_reader.drain() self.redis_stream.flushall()
def report(url): redis = utils.get_redis_for_cache() path = url.replace("https://github.com/", "") try: owner, repo, _, pull_number = path.split("/") except ValueError: print(f"Wrong URL: {url}") return slug = owner + "/" + repo try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: print("* Mergify is not installed there") return client = github.get_client(owner, repo, installation) print("* INSTALLATION ID: %s" % client.installation["id"]) cached_sub = sub_utils.get_subscription(redis, client.installation["id"]) db_sub = sub_utils._retrieve_subscription_from_db( client.installation["id"]) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) report_sub(client.installation["id"], slug, cached_sub, "ENGINE-CACHE") report_sub(client.installation["id"], slug, db_sub, "DASHBOARD") pull_raw = client.item(f"pulls/{pull_number}") ctxt = mergify_context.MergifyContext(client, pull_raw) print("* REPOSITORY IS %s" % "PRIVATE" if ctxt.pull["base"]["repo"]["private"] else "PUBLIC") print("* CONFIGURATION:") try: mergify_config_content = rules.get_mergify_config_content(ctxt) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") pull_request_rules = None else: print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema( mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: pull_request_rules_raw = mergify_config[ "pull_request_rules"].as_dict() pull_request_rules_raw["rules"].extend( engine.MERGIFY_RULE["rules"]) pull_request_rules = rules.PullRequestRules( **pull_request_rules_raw) print("* PULL REQUEST:") pprint.pprint(ctxt.to_dict(), width=160) print("is_behind: %s" % ctxt.is_behind) print("mergeable_state: %s" % ctxt.pull["mergeable_state"]) print("* MERGIFY LAST CHECKS:") checks = list(check_api.get_checks(ctxt, mergify_only=True)) for c in checks: print("[%s]: %s | %s" % (c["name"], c["conclusion"], c["output"].get("title"))) print("> " + "\n> ".join(c["output"].get("summary").split("\n"))) if pull_request_rules is not None: print("* MERGIFY LIVE MATCHES:") match = pull_request_rules.get_pull_request_rule(ctxt) summary_title, summary = actions_runner.gen_summary( ctxt, [{ "event_type": "refresh", "data": {} }], match) print("> %s" % summary_title) print(summary) return ctxt
def report( url: str, ) -> typing.Union[context.Context, github.GithubInstallationClient, None]: path = url.replace("https://github.com/", "") pull_number: typing.Optional[str] repo: typing.Optional[str] try: owner, repo, _, pull_number = path.split("/") except ValueError: pull_number = None try: owner, repo = path.split("/") except ValueError: owner = path repo = None try: client = github.get_client(owner) except exceptions.MergifyNotInstalled: print(f"* Mergify is not installed on account {owner}") return None print("* INSTALLATION ID: %s" % client.auth.installation["id"]) cached_sub, db_sub = utils.async_run( subscription.Subscription.get_subscription(client.auth.owner_id), subscription.Subscription._retrieve_subscription_from_db( client.auth.owner_id), ) if repo is None: slug = None else: slug = owner + "/" + repo print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub.active, db_sub.active)) print("* Features (cache):") for f in cached_sub.features: print(f" - {f.value}") report_sub(client.auth.installation["id"], cached_sub, "ENGINE-CACHE", slug) report_sub(client.auth.installation["id"], db_sub, "DASHBOARD", slug) utils.async_run(report_worker_status(client.auth.owner)) if repo is not None: repo_info = client.item(f"/repos/{owner}/{repo}") print( f"* REPOSITORY IS {'PRIVATE' if repo_info['private'] else 'PUBLIC'}" ) print("* CONFIGURATION:") mergify_config = None try: filename, mergify_config_content = rules.get_mergify_config_content( client, repo) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") else: print(f"Config filename: {filename}") print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema( mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: mergify_config["pull_request_rules"].rules.extend( engine.DEFAULT_PULL_REQUEST_RULES.rules) if pull_number is None: for branch in client.items(f"/repos/{owner}/{repo}/branches"): q = queue.Queue( utils.get_redis_for_cache(), client.auth.installation["id"], client.auth.owner, repo, branch["name"], ) pulls = q.get_pulls() if not pulls: continue print(f"* QUEUES {branch['name']}:") for priority, grouped_pulls in itertools.groupby( pulls, key=lambda v: q.get_config(v)["priority"]): try: fancy_priority = helpers.PriorityAliases(priority).name except ValueError: fancy_priority = priority formatted_pulls = ", ".join( (f"#{p}" for p in grouped_pulls)) print(f"** {formatted_pulls} (priority: {fancy_priority})") else: pull_raw = client.item( f"/repos/{owner}/{repo}/pulls/{pull_number}") ctxt = context.Context( client, pull_raw, cached_sub, [{ "event_type": "mergify-debugger", "data": {} }], ) # FIXME queues could also be printed if no pull number given q = queue.Queue.from_context(ctxt) print("* QUEUES: %s" % ", ".join([f"#{p}" for p in q.get_pulls()])) print("* PULL REQUEST:") pr_data = dict(ctxt.pull_request.items()) pprint.pprint(pr_data, width=160) print("is_behind: %s" % ctxt.is_behind) print("mergeable_state: %s" % ctxt.pull["mergeable_state"]) print("* MERGIFY LAST CHECKS:") for c in ctxt.pull_engine_check_runs: print("[%s]: %s | %s" % (c["name"], c["conclusion"], c["output"].get("title"))) print("> " + "\n> ".join(c["output"].get("summary").split("\n"))) if mergify_config is not None: print("* MERGIFY LIVE MATCHES:") match = mergify_config[ "pull_request_rules"].get_pull_request_rule(ctxt) summary_title, summary = actions_runner.gen_summary( ctxt, match) print("> %s" % summary_title) print(summary) return ctxt return client
def report(url): path = url.replace("https://github.com/", "") try: owner, repo, _, pull_number = path.split("/") except ValueError: print(f"Wrong URL: {url}") return slug = owner + "/" + repo try: client = github.get_client(owner, repo) except exceptions.MergifyNotInstalled: print("* Mergify is not installed there") return print("* INSTALLATION ID: %s" % client.auth.installation["id"]) cached_sub, db_sub = utils.async_run( sub_utils.get_subscription(client.auth.owner_id), sub_utils._retrieve_subscription_from_db(client.auth.owner_id), ) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) report_sub(client.auth.installation["id"], slug, cached_sub, "ENGINE-CACHE") report_sub(client.auth.installation["id"], slug, db_sub, "DASHBOARD") utils.async_run(report_worker_status(client.auth.owner)) pull_raw = client.item(f"pulls/{pull_number}") ctxt = context.Context(client, pull_raw, cached_sub, [{ "event_type": "mergify-debugger", "data": {} }]) q = queue.Queue.from_context(ctxt) print("QUEUES: %s" % ", ".join([f"#{p}" for p in q.get_pulls()])) print("* REPOSITORY IS %s" % "PRIVATE" if ctxt.pull["base"]["repo"]["private"] else "PUBLIC") print("* CONFIGURATION:") try: filename, mergify_config_content = rules.get_mergify_config_content( ctxt) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") pull_request_rules = None else: print(f"Config filename: {filename}") print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema( mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: pull_request_rules_raw = mergify_config[ "pull_request_rules"].as_dict() pull_request_rules = rules.PullRequestRules.from_list( pull_request_rules_raw["rules"] + engine.MERGIFY_RULE["rules"]) print("* PULL REQUEST:") pr_data = dict(ctxt.pull_request.items()) pprint.pprint(pr_data, width=160) print("is_behind: %s" % ctxt.is_behind) print("mergeable_state: %s" % ctxt.pull["mergeable_state"]) print("* MERGIFY LAST CHECKS:") for c in ctxt.pull_engine_check_runs: print("[%s]: %s | %s" % (c["name"], c["conclusion"], c["output"].get("title"))) print("> " + "\n> ".join(c["output"].get("summary").split("\n"))) if pull_request_rules is not None: print("* MERGIFY LIVE MATCHES:") match = pull_request_rules.get_pull_request_rule(ctxt) summary_title, summary = actions_runner.gen_summary(ctxt, match) print("> %s" % summary_title) print(summary) return ctxt
def run(event_type, data): """Everything starts here.""" installation_id = data["installation"]["id"] owner = data["repository"]["owner"]["login"] repo = data["repository"]["name"] client = github.get_client(owner, repo, installation_id) raw_pull = get_github_pull_from_event(client, event_type, data) if not raw_pull: # pragma: no cover LOG.info( "No pull request found in the event %s, ignoring", event_type, gh_owner=owner, gh_repo=repo, ) return pull = mergify_pull.MergifyPull(client, raw_pull) # Override pull_request with the updated one data["pull_request"] = pull.data pull.log.info("Pull request found in the event %s", event_type) if ("base" not in pull.data or "repo" not in pull.data["base"] or len(list(pull.data["base"]["repo"].keys())) < 70): pull.log.warning( "the pull request payload looks suspicious", event_type=event_type, data=data, ) if (event_type == "status" and pull.data["head"]["sha"] != data["sha"]): # pragma: no cover pull.log.info( "No need to proceed queue (got status of an old commit)", ) return elif (event_type in ["status", "check_suite", "check_run"] and pull.data["merged"]): # pragma: no cover pull.log.info( "No need to proceed queue (got status of a merged pull request)", ) return elif (event_type in ["check_suite", "check_run"] and pull.data["head"]["sha"] != data[event_type]["head_sha"]): # pragma: no cover pull.log.info( "No need to proceed queue (got %s of an old " "commit)", event_type, ) return if check_configuration_changes(pull.g_pull): pull.log.info("Configuration changed, ignoring", ) return # BRANCH CONFIGURATION CHECKING try: mergify_config = rules.get_mergify_config(pull.g_pull.base.repo) except rules.NoRules: # pragma: no cover pull.log.info("No need to proceed queue (.mergify.yml is missing)", ) return except rules.InvalidRules as e: # pragma: no cover # Not configured, post status check with the error message if event_type == "pull_request" and data["action"] in [ "opened", "synchronize" ]: check_api.set_check_run( pull.g_pull, "Summary", "completed", "failure", output={ "title": "The Mergify configuration is invalid", "summary": str(e), }, ) return # Add global and mandatory rules mergify_config["pull_request_rules"].rules.extend( rules.load_pull_request_rules_schema(MERGIFY_RULE["rules"])) subscription = sub_utils.get_subscription(utils.get_redis_for_cache(), installation_id) if pull.data["base"]["repo"][ "private"] and not subscription["subscription_active"]: check_api.set_check_run( pull.g_pull, "Summary", "completed", "failure", output={ "title": "Mergify is disabled", "summary": subscription["subscription_reason"], }, ) return # CheckRun are attached to head sha, so when user add commits or force push # we can't directly get the previous Mergify Summary. So we copy it here, then # anything that looks at it in next celery tasks will find it. if event_type == "pull_request" and data["action"] == "synchronize": copy_summary_from_previous_head_sha(pull.g_pull, data["before"]) sources = [{"event_type": event_type, "data": data}] commands_runner.spawn_pending_commands_tasks(pull, sources) if event_type == "issue_comment": commands_runner.run_command(pull, sources, data["comment"]["body"], data["comment"]["user"]) else: actions_runner.handle(mergify_config["pull_request_rules"], pull, sources)
def setUp(self): super(FunctionalTestBase, self).setUp() self.pr_counter = 0 self.git_counter = 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"], filter_headers=[ ("Authorization", "<TOKEN>"), ("X-Hub-Signature", "<SIGNATURE>"), ("User-Agent", None), ("Accept-Encoding", None), ("Connection", None), ], before_record_response=self.response_filter, custom_patches=( (pygithub.MainClass, "HTTPSConnection", vcr.stubs.VCRHTTPSConnection), ), ) github_app_client = github_app._Client() mock.patch.object(github_app, "get_client", lambda: github_app_client).start() mock.patch.object( branch_updater.utils, "Gitter", lambda: self.get_gitter() ).start() mock.patch.object( duplicate_pull.utils, "Gitter", lambda: self.get_gitter() ).start() if not RECORD: # NOTE(sileht): Don't wait exponentialy during replay mock.patch.object( mergify_context.MergifyContext._ensure_complete.retry, "wait", None ).start() # Web authentification always pass mock.patch("hmac.compare_digest", return_value=True).start() reponame_path = os.path.join(self.cassette_library_dir, "reponame") if RECORD: REPO_UUID = str(uuid.uuid4()) with open(reponame_path, "w") as f: f.write(REPO_UUID) else: with open(reponame_path, "r") as f: REPO_UUID = f.read() self.name = "repo-%s-%s" % (REPO_UUID, self._testMethodName) self.git = self.get_gitter() self.addCleanup(self.git.cleanup) web.app.testing = True self.app = web.app.test_client() # NOTE(sileht): Prepare a fresh redis self.redis = utils.get_redis_for_cache() self.redis.flushall() self.subscription = { "tokens": {"mergifyio-testing": config.MAIN_TOKEN}, "subscription_active": False, "subscription_reason": "You're not nice", } sub_utils.save_subscription_to_cache( self.redis, config.INSTALLATION_ID, self.subscription, ) # Let's start recording cassette = self.recorder.use_cassette("http.json") cassette.__enter__() self.addCleanup(cassette.__exit__) integration = pygithub.GithubIntegration( config.INTEGRATION_ID, config.PRIVATE_KEY ) self.installation_token = integration.get_access_token( config.INSTALLATION_ID ).token self.g_integration = pygithub.Github( self.installation_token, base_url="https://api.%s" % config.GITHUB_DOMAIN ) self.g_admin = pygithub.Github( config.MAIN_TOKEN, base_url="https://api.%s" % config.GITHUB_DOMAIN ) self.g_fork = pygithub.Github( config.FORK_TOKEN, base_url="https://api.%s" % config.GITHUB_DOMAIN ) self.o_admin = self.g_admin.get_organization(config.TESTING_ORGANIZATION) self.o_integration = self.g_integration.get_organization( config.TESTING_ORGANIZATION ) self.u_fork = self.g_fork.get_user() assert self.o_admin.login == "mergifyio-testing" assert self.o_integration.login == "mergifyio-testing" assert self.u_fork.login == "mergify-test2" self.r_o_admin = self.o_admin.create_repo(self.name) self.r_o_integration = self.o_integration.get_repo(self.name) self.url_main = "https://%s/%s" % ( config.GITHUB_DOMAIN, self.r_o_integration.full_name, ) self.url_fork = "https://%s/%s/%s" % ( config.GITHUB_DOMAIN, self.u_fork.login, self.r_o_integration.name, ) installation = {"id": config.INSTALLATION_ID} try: self.cli_integration = github.get_client( config.TESTING_ORGANIZATION, self.name, installation ) except httpx.HTTPNotFound: self.cli_integration = github.get_client( config.TESTING_ORGANIZATION, self.name, installation ) real_get_subscription = sub_utils.get_subscription def fake_retrieve_subscription_from_db(install_id): if int(install_id) == config.INSTALLATION_ID: return self.subscription else: return { "tokens": {}, "subscription_active": False, "subscription_reason": "We're just testing", } def fake_subscription(r, install_id): if int(install_id) == config.INSTALLATION_ID: return real_get_subscription(r, install_id) else: return { "tokens": {}, "subscription_active": False, "subscription_reason": "We're just testing", } mock.patch( "mergify_engine.branch_updater.sub_utils.get_subscription", side_effect=fake_subscription, ).start() mock.patch( "mergify_engine.branch_updater.sub_utils._retrieve_subscription_from_db", side_effect=fake_retrieve_subscription_from_db, ).start() mock.patch( "mergify_engine.sub_utils.get_subscription", side_effect=fake_subscription, ).start() mock.patch( "github.MainClass.Installation.Installation.get_repos", return_value=[self.r_o_integration], ).start() self._event_reader = EventReader(self.app) self._event_reader.drain()
def setUp(self): super(FunctionalTestBase, self).setUp() self.pr_counter = 0 self.git_counter = 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"], filter_headers=[ ("Authorization", "<TOKEN>"), ("X-Hub-Signature", "<SIGNATURE>"), ("User-Agent", None), ("Accept-Encoding", None), ("Connection", None), ], before_record_response=self.response_filter, custom_patches=( (pygithub.MainClass, "HTTPSConnection", vcr.stubs.VCRHTTPSConnection), ), ) if RECORD: github.CachedToken.STORAGE = {} else: # Never expire token during replay mock.patch.object( github_app.GithubBearerAuth, "get_or_create_jwt", return_value="<TOKEN>" ).start() mock.patch.object( github.GithubInstallationAuth, "get_access_token", return_value="<TOKEN>", ).start() github.CachedToken.STORAGE = {} github.CachedToken( installation_id=config.INSTALLATION_ID, token="<TOKEN>", expiration=datetime.datetime.utcnow() + datetime.timedelta(minutes=10), ) github_app_client = github_app._Client() mock.patch.object(github_app, "get_client", lambda: github_app_client).start() mock.patch.object(branch_updater.utils, "Gitter", self.get_gitter).start() mock.patch.object(duplicate_pull.utils, "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) self.addCleanup(self.git.cleanup) self.app = testclient.TestClient(web.app) # NOTE(sileht): Prepare a fresh redis self.redis = utils.get_redis_for_cache() self.redis.flushall() self.subscription = { "tokens": {"mergifyio-testing": config.MAIN_TOKEN}, "subscription_active": False, "subscription_reason": "You're not nice", } loop = asyncio.get_event_loop() loop.run_until_complete( sub_utils.save_subscription_to_cache( config.INSTALLATION_ID, self.subscription, ) ) # Let's start recording cassette = self.recorder.use_cassette("http.json") cassette.__enter__() self.addCleanup(cassette.__exit__) integration = pygithub.GithubIntegration( config.INTEGRATION_ID, config.PRIVATE_KEY ) self.installation_token = integration.get_access_token( config.INSTALLATION_ID ).token base_url = config.GITHUB_API_URL self.g_integration = pygithub.Github(self.installation_token, base_url=base_url) self.g_admin = pygithub.Github(config.MAIN_TOKEN, base_url=base_url) self.g_fork = pygithub.Github(config.FORK_TOKEN, base_url=base_url) self.o_admin = self.g_admin.get_organization(config.TESTING_ORGANIZATION) self.o_integration = self.g_integration.get_organization( config.TESTING_ORGANIZATION ) self.u_fork = self.g_fork.get_user() assert self.o_admin.login == "mergifyio-testing" assert self.o_integration.login == "mergifyio-testing" assert self.u_fork.login == "mergify-test2" # NOTE(sileht): The repository have been manually created in mergifyio-testing # organization and then forked in mergify-test2 user account self.name = "functional-testing-repo" self.r_o_admin = self.o_admin.get_repo(self.name) self.r_o_integration = self.o_integration.get_repo(self.name) self.r_fork = self.u_fork.get_repo(self.name) self.url_main = f"{config.GITHUB_URL}/{self.r_o_integration.full_name}" self.url_fork = ( f"{config.GITHUB_URL}/{self.u_fork.login}/{self.r_o_integration.name}" ) installation = {"id": config.INSTALLATION_ID} self.cli_integration = github.get_client( config.TESTING_ORGANIZATION, self.name, installation ) real_get_subscription = sub_utils.get_subscription def fake_retrieve_subscription_from_db(install_id): if int(install_id) == config.INSTALLATION_ID: return self.subscription else: return { "tokens": {}, "subscription_active": False, "subscription_reason": "We're just testing", } def fake_subscription(r, install_id): if int(install_id) == config.INSTALLATION_ID: return real_get_subscription(r, install_id) else: return { "tokens": {}, "subscription_active": False, "subscription_reason": "We're just testing", } mock.patch( "mergify_engine.branch_updater.sub_utils.get_subscription", side_effect=fake_subscription, ).start() mock.patch( "mergify_engine.branch_updater.sub_utils._retrieve_subscription_from_db", side_effect=fake_retrieve_subscription_from_db, ).start() mock.patch( "mergify_engine.sub_utils.get_subscription", side_effect=fake_subscription, ).start() mock.patch( "github.MainClass.Installation.Installation.get_repos", return_value=[self.r_o_integration], ).start() self._event_reader = EventReader(self.app) self._event_reader.drain() self._worker_thread = WorkerThread() self._worker_thread.start()