async def test_mergeable_queue_in_progress() -> None: """ If a PR has pending status checks or is behind, we still consider it eligible for merge and throw it in the merge queue. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() config.merge.optimistic_updates = False pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] context.state = StatusState.PENDING context.context = "ci/test-api" await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], ) assert api.set_status.call_count == 1 assert "enqueued for merge" in api.set_status.calls[0]["msg"] assert api.dequeue.call_count == 0 assert api.queue_for_merge.call_count == 1 # verify we haven't tried to merge the PR assert api.merge.called is False assert api.update_branch.called is False
async def test_mergeable_requires_commit_signatures_rebase() -> None: """ requiresCommitSignatures doesn't work with Kodiak when rebase is configured https://github.com/chdsbd/kodiak/issues/89 """ api = create_api() mergeable = create_mergeable() branch_protection = create_branch_protection() config = create_config() branch_protection.requiresCommitSignatures = True config.merge.method = MergeMethod.rebase await mergeable( api=api, config=config, branch_protection=branch_protection, valid_merge_methods=[MergeMethod.rebase], ) assert ('"Require signed commits" branch protection is only supported' in api.set_status.calls[0]["msg"]) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 1 # verify we haven't tried to update/merge the PR assert api.update_branch.called is False assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_requires_commit_signatures_squash_and_merge() -> None: """ requiresCommitSignatures works with merge commits and squash https://github.com/chdsbd/kodiak/issues/89 """ api = create_api() mergeable = create_mergeable() config = create_config() branch_protection = create_branch_protection() branch_protection.requiresCommitSignatures = True for index, method in enumerate((MergeMethod.squash, MergeMethod.merge)): config.merge.method = method await mergeable( api=api, config=config, branch_protection=branch_protection, valid_merge_methods=[method], ) assert api.set_status.call_count == index + 1 assert "enqueued for merge" in api.set_status.calls[index]["msg"] assert api.queue_for_merge.call_count == index + 1 assert api.dequeue.call_count == 0 # verify we haven't tried to update/merge the PR assert api.update_branch.called is False assert api.merge.called is False
async def test_mergeable_do_not_merge_with_update_branch_immediately_waiting_for_checks( ) -> None: """ merge.do_not_merge is only useful with merge.update_branch_immediately, Test when PR doesn't need update but is waiting for checks to finish. """ mergeable = create_mergeable() api = create_api() pull_request = create_pull_request() config = create_config() branch_protection = create_branch_protection() context = create_context() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED config.merge.do_not_merge = True config.merge.update_branch_immediately = True branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] context.context = "ci/test-api" context.state = StatusState.PENDING await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], ) assert api.set_status.called is True assert ("waiting for required status checks: {'ci/test-api'}" in api.set_status.calls[0]["msg"]) assert api.update_branch.called is False assert api.queue_for_merge.called is False assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_wait_for_checks() -> None: """ test merge.optimistic_updates when we don't have checks to wait for. Since merge.optimistic_updates is disabled we should update the branch. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() config.merge.optimistic_updates = False pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True with pytest.raises(PollForever): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, merging=True, ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert api.update_branch.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] # verify we haven't tried to merge the PR assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_update_always_enabled_merging_behind_pull_request( ) -> None: """ When we're merging with update.always enabled we don't want to update the branch using our update.always logic. We want to update using our merging logic so we trigger the PollForever exception necessary to continue our merge loop. If we used the update.always logic we'd eject a PR if it became out of sync during merge. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() config.update.always = True pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True with pytest.raises(PollForever): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, merging=True, ) assert api.set_status.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] assert api.update_branch.call_count == 1 assert api.queue_for_merge.call_count == 0 assert api.merge.call_count == 0 assert api.dequeue.call_count == 0
async def test_mergeable_requires_conversation_resolution() -> None: """ When requiresConversationResolution is enabled and we have unresolved review threads, we should block merge. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED pull_request.reviewThreads.nodes = [ ReviewThread(isCollapsed=True), ReviewThread(isCollapsed=False), ] branch_protection.requiresConversationResolution = True await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, ) assert api.set_status.call_count == 1 assert "cannot merge (unresolved conversations)" in api.set_status.calls[ 0]["msg"]
async def test_neutral_required_check_runs() -> None: """ When merge.block_on_neutral_required_check_runs is enabled, we should block merge if a required check run has a neutral conclusion. """ api = create_api() config = create_config() config.merge.block_on_neutral_required_check_runs = True branch_protection = create_branch_protection() branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["Pre-merge checks"] mergeable = create_mergeable() await mergeable( api=api, branch_protection=branch_protection, config=config, check_runs=[ create_check_run(name="Pre-merge checks", conclusion=CheckConclusionState.NEUTRAL), ], ) assert "neutral required check runs" in api.set_status.calls[0]["msg"] assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 1
async def test_mergeable_uncollapsed_reviews() -> None: """ We should only block merge for unresolved review threads if requiresConversationResolution is enabled. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED pull_request.reviewThreads.nodes = [ ReviewThread(isCollapsed=True), ReviewThread(isCollapsed=False), ] await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, ) assert api.set_status.call_count == 1 assert ("cannot merge (Merging blocked by GitHub requirements)" in api.set_status.calls[0]["msg"])
def test_get_merge_body_include_coauthors_invalid_body_style() -> None: """ We only include trailers for MergeBodyStyle.pull_request_body and MergeBodyStyle.empty. Verify we don't add coauthor trailers for MergeBodyStyle.github_default. """ pull_request = create_pull_request() pull_request.body = "hello world" config = create_config() config.merge.message.include_coauthors = True config.merge.message.body = MergeBodyStyle.github_default actual = get_merge_body( config=config, pull_request=pull_request, merge_method=MergeMethod.merge, commits=[ create_commit(database_id=9023904, name="", login="******", type="User"), create_commit(database_id=590434, name="Maeve Millay", login="******", type="Bot"), ], ) expected = MergeBody(merge_method="merge", commit_message=None) assert actual == expected
async def test_mergeable_update_branch_immediately_mode_merging() -> None: """ update branch immediately if configured. When we are merging we should raise the PollForever exception to keep the merge loop going instead of returning. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True config.merge.update_branch_immediately = True with pytest.raises(PollForever): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, merging=True, ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert api.update_branch.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] assert "branch updated because" in api.set_status.calls[0][ "markdown_content"] # verify we haven't tried to merge the PR assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_update_branch_immediately() -> None: """ update branch immediately if configured """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True config.merge.update_branch_immediately = True await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert api.update_branch.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] assert "branch updated because" in api.set_status.calls[0][ "markdown_content"] # verify we haven't tried to merge the PR assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_update_username_blacklist() -> None: """ Kodiak should not update PR if user is blacklisted. """ mergeable = create_mergeable() blacklist_config = create_config() blacklist_config.update.always = True blacklist_config.update.blacklist_usernames = ["mr-test"] blacklist_config.update.require_automerge_label = True ignored_config = create_config() ignored_config.update.always = True ignored_config.update.ignored_usernames = ["mr-test"] ignored_config.update.require_automerge_label = True pull_request = create_pull_request() pull_request.author.login = "******" pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection = create_branch_protection() branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] check_run = create_check_run() check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.FAILURE for is_merging in (True, False): for config in (blacklist_config, ignored_config): api = create_api() await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], merging=is_merging, ) assert api.update_branch.call_count == 0 assert api.set_status.call_count == 1 assert "updates blocked by update." in api.set_status.calls[0][ "msg"] assert api.dequeue.call_count == 1 assert api.queue_for_merge.call_count == 0 assert api.merge.call_count == 0
async def test_mergeable_update_autoupdate_label() -> None: """ Kodiak should update the PR when the autoupdate_label is set on the PR. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() check_run = create_check_run() config.update.autoupdate_label = "update me please!" # create a pull requests that's behind and failing checks. We should still # update on failing checks. pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.FAILURE await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], ) assert ( api.update_branch.call_count == 0 ), "we shouldn't update when update.autoupdate_label isn't available on the PR" # PR should be updated when set on the PR pull_request.labels = [config.update.autoupdate_label] api = create_api() await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], ) assert api.update_branch.call_count == 1 assert api.set_status.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] assert "branch updated because" in api.set_status.calls[0][ "markdown_content"] assert api.queue_for_merge.call_count == 0 assert api.merge.call_count == 0 assert api.dequeue.call_count == 0
async def test_rebase_merge_fast_forward() -> None: """ Happy case. """ mergeable = create_mergeable() api = create_api() config = create_config() config.merge.method = MergeMethod.rebase_fast_forward await mergeable(api=api, config=config, merging=True) assert api.update_ref.call_count == 1 assert api.merge.call_count == 0 assert api.set_status.call_count == 2 assert "(merging)" in api.set_status.calls[0]["msg"] assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 0
async def test_merge_mismatch_username() -> None: """ We should only merge the pull request if the userrname is specified within "usernames" and the version type is in the "versions" field. """ mergeable = create_mergeable() config = create_config() config.merge.automerge_dependencies.versions = ["minor", "patch"] config.merge.automerge_dependencies.usernames = ["dependabot"] pull_request = create_pull_request() pull_request.labels = [] pull_request.author.login = "******" pull_request.title = "Bump lodash from 4.17.15 to 4.17.19" api = create_api() await mergeable(api=api, pull_request=pull_request, config=config) assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 1
async def test_merge_disallowed_version() -> None: """ We should only auto merge if the upgrade type is specified in "versions". So if a PR is a major upgrade, we should only auto merge if "major" is in the "versions" configuration. """ mergeable = create_mergeable() config = create_config() config.merge.automerge_dependencies.versions = ["minor", "patch"] config.merge.automerge_dependencies.usernames = ["my-custom-dependabot"] pull_request = create_pull_request() pull_request.labels = [] pull_request.author.login = "******" pull_request.title = "Bump lodash from 4.17.15 to 5.0.1" api = create_api() await mergeable(api=api, pull_request=pull_request, config=config) assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 1
async def test_mergeable_include_coauthors() -> None: """ Include coauthors should attach coauthor when `merge.message.body = "pull_request_body"` """ mergeable = create_mergeable() config = create_config() config.merge.message.include_coauthors = True for body_style, commit_message in ( ( MergeBodyStyle.pull_request_body, "# some description\n\nCo-authored-by: Barry Block <*****@*****.**>", ), ( MergeBodyStyle.empty, "Co-authored-by: Barry Block <*****@*****.**>", ), ): config.merge.message.body = body_style api = create_api() await mergeable( api=api, config=config, commits=[ create_commit( database_id=73213123, name="Barry Block", login="******", type="User", ) ], merging=True, ) assert api.set_status.call_count == 2 assert "attempting to merge PR" in api.set_status.calls[0]["msg"] assert api.set_status.calls[1]["msg"] == "merge complete 🎉" assert api.merge.call_count == 1 assert commit_message == api.merge.calls[0]["commit_message"] assert api.update_branch.call_count == 0 assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 0
async def test_mergeable_skippable_contexts_merging_pull_request() -> None: """ If a skippable check hasn't finished but we're merging, we need to raise an exception to retry for a short period of time to allow the check to finish. We won't retry forever because skippable checks will likely never finish. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() check_run = create_check_run() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"] config.merge.dont_wait_on_status_checks = ["WIP"] context.state = StatusState.PENDING context.context = "WIP" check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.SUCCESS with pytest.raises(RetryForSkippableChecks): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], contexts=[context], merging=True, ) assert api.set_status.call_count == 1 assert ( "merging PR (waiting a bit for dont_wait_on_status_checks: ['WIP'])" in api.set_status.calls[0]["msg"]) assert api.dequeue.call_count == 0 # verify we haven't tried to update/merge the PR assert api.update_branch.called is False assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_queue_in_progress_with_ready_to_merge() -> None: """ If a PR has pending status checks or is behind, we still consider it eligible for merge and throw it in the merge queue. regression test to verify that with config.merge.prioritize_ready_to_merge = true we don't attempt to merge a PR directly but called queue_for_merge instead. If the status checks haven't passed or the branch needs an update it's not good to be merged directly, but it can be queued for the merge queue. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() config.merge.optimistic_updates = False pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] context.state = StatusState.PENDING context.context = "ci/test-api" config.merge.prioritize_ready_to_merge = True await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], ) assert api.set_status.call_count == 1 assert "enqueued for merge" in api.set_status.calls[0]["msg"] assert api.dequeue.call_count == 0 assert api.queue_for_merge.call_count == 1 # verify we haven't tried to merge the PR assert api.merge.called is False assert api.update_branch.called is False
async def test_merge_okay() -> None: """ Happy case. The upgrade type (patch) is specified in "versions" and the PR author "my-custom-dependabot" is specified in "usernames". """ mergeable = create_mergeable() config = create_config() config.merge.automerge_dependencies.versions = ["minor", "patch"] config.merge.automerge_dependencies.usernames = ["my-custom-dependabot"] pull_request = create_pull_request() pull_request.labels = [] pull_request.author.login = "******" pull_request.title = "Bump lodash from 4.17.15 to 4.17.19" api = create_api() await mergeable(api=api, pull_request=pull_request, config=config) assert api.set_status.call_count == 1 assert "enqueued" in api.set_status.calls[0]["msg"] assert api.queue_for_merge.call_count == 1 assert api.dequeue.call_count == 0
async def test_merge_no_version_found() -> None: """ If we can't find a version from the PR title, we shouldn't merge. Packages don't necessarily use semver. """ mergeable = create_mergeable() config = create_config() config.merge.automerge_dependencies.versions = ["minor", "patch"] config.merge.automerge_dependencies.usernames = ["my-custom-dependabot"] pull_request = create_pull_request() pull_request.labels = [] pull_request.author.login = "******" for title in ("Bump lodash from 4.17.15 to", "Bump lodash from griffin to phoenix"): pull_request.title = title api = create_api() await mergeable(api=api, pull_request=pull_request, config=config) assert api.queue_for_merge.call_count == 0 assert api.dequeue.call_count == 1
def test_get_merge_body_include_pull_request_author_invalid_body_style( ) -> None: """ We only include trailers MergeBodyStyle.pull_request_body and MergeBodyStyle.empty. Verify we don't include trailers for MergeBodyStyle.github_default. """ pull_request = create_pull_request() pull_request.body = "hello world" config = create_config() config.merge.message.include_pull_request_author = True config.merge.message.body = MergeBodyStyle.github_default actual = get_merge_body( config=config, pull_request=pull_request, merge_method=MergeMethod.merge, commits=[], ) expected = MergeBody(merge_method="merge", commit_message=None) assert actual == expected
async def test_mergeable_skippable_check_timeout() -> None: """ we wait for skippable checks when merging because it takes time for check statuses to be sent and acknowledged by GitHub. We time out after some time because skippable checks are likely to never complete. In this case we want to notify the user of this via status check. """ mergeable = create_mergeable() api = create_api() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() check_run = create_check_run() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"] config.merge.dont_wait_on_status_checks = ["WIP"] context.state = StatusState.PENDING context.context = "WIP" check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.SUCCESS await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], check_runs=[check_run], merging=True, skippable_check_timeout=0, ) assert api.set_status.called is True assert ("timeout reached for dont_wait_on_status_checks: ['WIP']" in api.set_status.calls[0]["msg"]) assert api.update_branch.called is False assert api.queue_for_merge.called is False assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_update_always_no_require_automerge_label_missing_label( ) -> None: """ Kodiak should update branch if update.require_automerge_label is True and we're missing the automerge label. """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() check_run = create_check_run() config.update.always = True config.update.require_automerge_label = False pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.FAILURE pull_request.labels = [] await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], ) assert api.update_branch.call_count == 1 assert api.set_status.call_count == 1 assert "updating branch" in api.set_status.calls[0]["msg"] assert "branch updated because" in api.set_status.calls[0][ "markdown_content"] assert api.queue_for_merge.call_count == 0 assert api.merge.call_count == 0 assert api.dequeue.call_count == 0
async def test_mergeable_optimistic_update_wait_for_checks() -> None: """ test merge.optimistic_updates when we don't need a branch update. Since merge.optimistic_updates is enabled we should wait_for_checks """ mergeable = create_mergeable() api = create_api() config = create_config() pull_request = create_pull_request() branch_protection = create_branch_protection() context = create_context() config.merge.optimistic_updates = True pull_request.mergeStateStatus = MergeStateStatus.BLOCKED branch_protection.requiresStrictStatusChecks = True branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] context.state = StatusState.PENDING context.context = "ci/test-api" with pytest.raises(PollForever): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], # merging=True, ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert api.update_branch.call_count == 0 assert ("merging PR (waiting for status checks: {'ci/test-api'})" in api.set_status.calls[0]["msg"]) # verify we haven't tried to merge the PR assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_skippable_contexts_passing() -> None: """ If a skippable check is passing we should queue the PR for merging """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() check_run = create_check_run() pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"] config.merge.dont_wait_on_status_checks = ["WIP"] context.state = StatusState.SUCCESS context.context = "WIP" check_run.name = "ci/test-api" check_run.conclusion = CheckConclusionState.SUCCESS await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], contexts=[context], ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert api.queue_for_merge.call_count == 1 assert "enqueued for merge (position=4th)" in api.set_status.calls[0][ "msg"] # verify we haven't tried to update/merge the PR assert api.update_branch.called is False assert api.merge.called is False
async def test_mergeable_skippable_contexts_with_check_run() -> None: """ If a skippable check hasn't finished, we shouldn't do anything. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() check_run = create_check_run() pull_request.mergeStateStatus = MergeStateStatus.BLOCKED branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"] config.merge.dont_wait_on_status_checks = ["WIP"] context.state = StatusState.SUCCESS context.context = "ci/test-api" check_run.name = "WIP" check_run.conclusion = None await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, check_runs=[check_run], contexts=[context], ) assert api.set_status.call_count == 1 assert api.dequeue.call_count == 0 assert ("not waiting for dont_wait_on_status_checks: ['WIP']" in api.set_status.calls[0]["msg"]) # verify we haven't tried to update/merge the PR assert api.update_branch.called is False assert api.merge.called is False assert api.queue_for_merge.called is False
async def test_mergeable_missing_push_allowance_merge_do_not_merge() -> None: """ When merge.do_not_merge is enabled, we should ignore any issues with restrictPushes because Kodiak isn't pushing. """ api = create_api() mergeable = create_mergeable() branch_protection = create_branch_protection() config = create_config() branch_protection.restrictsPushes = True config.merge.do_not_merge = True branch_protection.pushAllowances = NodeListPushAllowance(nodes=[]) await mergeable(api=api, config=config, branch_protection=branch_protection) assert api.set_status.call_count == 1 assert api.set_status.calls[0]["msg"] == "✅ okay to merge" assert api.queue_for_merge.called is False assert api.dequeue.call_count == 0 assert api.update_branch.called is False assert api.merge.called is False
async def test_mergeable_need_branch_update() -> None: """ prioritize waiting for checks over branch updates when merging if merge.optimistic_updates is disabled. """ api = create_api() mergeable = create_mergeable() pull_request = create_pull_request() branch_protection = create_branch_protection() config = create_config() context = create_context() config.merge.optimistic_updates = False pull_request.mergeStateStatus = MergeStateStatus.BEHIND branch_protection.requiresStrictStatusChecks = True branch_protection.requiresStatusChecks = True branch_protection.requiredStatusCheckContexts = ["ci/test-api"] context.state = StatusState.PENDING context.context = "ci/test-api" with pytest.raises(PollForever): await mergeable( api=api, config=config, pull_request=pull_request, branch_protection=branch_protection, contexts=[context], merging=True, ) assert api.set_status.call_count == 1 assert ("merging PR (waiting for status checks: {'ci/test-api'})" in api.set_status.calls[0]["msg"]) assert api.dequeue.call_count == 0 # verify we haven't tried to merge the PR assert api.merge.called is False assert api.queue_for_merge.called is False assert api.update_branch.called is False