async def test_checks_feature_disabled(self): self.subscription = subscription.Subscription( utils.create_aredis_for_cache(max_idle_time=0), config.INSTALLATION_ID, self.SUBSCRIPTION_ACTIVE, "You're not nice", frozenset( getattr(subscription.Features, f) for f in subscription.Features.__members__ if f is not subscription.Features.CUSTOM_CHECKS ) if self.SUBSCRIPTION_ACTIVE else frozenset(), ) await self.subscription.save_subscription_to_cache() rules = { "pull_request_rules": [ { "name": "body need sentry ticket", "conditions": [ f"base={self.master_branch_name}", "#title>10", "#title<50", "#body<4096", "#files<100", "body~=(?m)^(Fixes|Related|Closes) (MERGIFY-ENGINE|MRGFY)-", "-label=ignore-guideline", ], "actions": {"post_check": {}}, } ] } await self.setup_repo(yaml.dump(rules)) p, _ = await self.create_pr() await self.run_engine() p = await self.get_pull(p["number"]) ctxt = await context.Context.create(self.repository_ctxt, p, []) sorted_checks = sorted( await ctxt.pull_engine_check_runs, key=operator.itemgetter("name") ) assert len(sorted_checks) == 2 check = sorted_checks[0] assert "action_required" == check["conclusion"] assert "Custom checks are disabled" == check["output"]["title"]
async def start(self): self._stopping.clear() self._redis_stream = utils.create_aredis_for_stream() self._redis_cache = utils.create_aredis_for_cache() if "stream" in self.enabled_services: worker_ids = self.get_worker_ids() LOG.info("workers starting", count=len(worker_ids)) for worker_id in worker_ids: self._worker_tasks.append( asyncio.create_task(self.stream_worker_task(worker_id))) LOG.info("workers started", count=len(worker_ids)) if "stream-monitoring" in self.enabled_services: LOG.info("monitoring starting") self._stream_monitoring_task = asyncio.create_task( self.monitoring_task()) LOG.info("monitoring started")
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 startup(): global _AREDIS_STREAM, _AREDIS_CACHE _AREDIS_STREAM = utils.create_aredis_for_stream( max_connections=config.REDIS_STREAM_WEB_MAX_CONNECTIONS) _AREDIS_CACHE = utils.create_aredis_for_cache( max_connections=config.REDIS_CACHE_WEB_MAX_CONNECTIONS)
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