예제 #1
0
async def test_deleting_branch_after_merge(
    labels: List[str],
    expected: bool,
    event_response: queries.EventInfoResponse,
    mocker: MockFixture,
) -> None:
    """
    ensure client.delete_branch is called when a PR that is already merged is
    evaluated.
    """

    event_response.pull_request.state = queries.PullRequestState.MERGED
    event_response.pull_request.labels = labels
    assert isinstance(event_response.config, V1)
    event_response.config.merge.delete_branch_on_merge = True

    mocker.patch.object(PR, "get_event", return_value=wrap_future(event_response))
    mocker.patch.object(PR, "set_status", return_value=wrap_future(None))

    delete_branch = mocker.patch.object(
        queries.Client, "delete_branch", return_value=wrap_future(True)
    )

    pr = PR(
        number=123,
        owner="tester",
        repo="repo",
        installation_id="abc",
        client=queries.Client(owner="tester", repo="repo", installation_id="abc"),
    )

    await pr.mergeability()

    assert delete_branch.called == expected
예제 #2
0
async def test_cross_repo_missing_head(
    event_response: queries.EventInfoResponse, mocker: MockFixture
) -> None:
    """
    if a repository is from a fork (isCrossRepository), we will not be able to
    see head information due to a problem with the v4 api failing to return head
    information for forks, unlike the v3 api.
    """

    event_response.head_exists = False
    event_response.pull_request.isCrossRepository = True
    assert event_response.pull_request.mergeStateStatus == MergeStateStatus.BEHIND
    event_response.pull_request.labels = ["automerge"]
    assert event_response.branch_protection is not None
    event_response.branch_protection.requiresApprovingReviews = False
    event_response.branch_protection.requiresStrictStatusChecks = True
    mocker.patch.object(PR, "get_event", return_value=wrap_future(event_response))
    set_status = mocker.patch.object(PR, "set_status", return_value=wrap_future(None))
    pr = PR(
        number=123,
        owner="tester",
        repo="repo",
        installation_id="abc",
        client=queries.Client(owner="tester", repo="repo", installation_id="abc"),
    )
    await pr.mergeability()

    assert set_status.call_count == 1
    set_status.assert_called_with(
        summary=mocker.ANY, markdown_content=messages.FORKS_CANNOT_BE_UPDATED
    )
예제 #3
0
async def test_attempting_to_notify_pr_author_with_no_automerge_label(
    api_client: queries.Client,
    mocker: MockFixture,
    event_response: queries.EventInfoResponse,
) -> None:
    """
    ensure that when Kodiak encounters a merge conflict it doesn't notify
    the user if an automerge label isn't required.
    """

    pr = PR(
        number=123,
        owner="ghost",
        repo="ghost",
        installation_id="abc123",
        client=api_client,
    )
    assert isinstance(event_response.config, V1)
    event_response.config.merge.require_automerge_label = False
    pr.event = event_response

    create_comment = mocker.patch.object(
        PR, "create_comment", return_value=wrap_future(None)
    )
    # mock to ensure we have a chance of hitting the create_comment call
    mocker.patch.object(PR, "delete_label", return_value=wrap_future(True))

    assert await pr.notify_pr_creator() is False
    assert not create_comment.called
예제 #4
0
async def test_update_pr_with_retry_failure(pr: PR, mocker: MockFixture) -> None:
    asyncio_sleep = mocker.patch(
        "kodiak.queue.asyncio.sleep", return_value=wrap_future(None)
    )
    mocker.patch.object(pr, "update", return_value=wrap_future(False))
    res = await update_pr_with_retry(pr)
    assert not res

    assert asyncio_sleep.call_count == 5
예제 #5
0
async def test_mergeability_missing_skippable_checks(
    mocker: MockFixture, event_response: queries.EventInfoResponse, pr: PR
) -> None:
    mocker.patch.object(PR, "get_event", return_value=wrap_future(event_response))
    mergeable = mocker.patch("kodiak.pull_request.mergeable")
    mergeable.side_effect = MissingSkippableChecks([])
    mocker.patch.object(PR, "set_status", return_value=wrap_future(None))
    res, event = await pr.mergeability()
    assert res == MergeabilityResponse.SKIPPABLE_CHECKS
예제 #6
0
async def test_pr_update_ok(
    mocker: MockFixture, event_response: queries.EventInfoResponse, pr: PR
) -> None:
    """
    Update should return true on success
    """
    mocker.patch.object(PR, "get_event", return_value=wrap_future(event_response))
    res = Response()
    res.status_code = 200
    mocker.patch(
        "kodiak.pull_request.queries.Client.merge_branch", return_value=wrap_future(res)
    )

    res = await pr.update()
    assert res, "should be true when we have a successful call"
예제 #7
0
async def test_get_event_info_blocked(
    api_client: Client,
    blocked_response: dict,
    block_event: EventInfoResponse,
    mocker: MockFixture,
    setup_redis: object,
) -> None:

    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(
            GraphQLResponse(data=blocked_response.get("data"),
                            errors=blocked_response.get("errors"))),
    )

    async def get_permissions_for_username_patch(username: str) -> Permission:
        if username in ("walrus", "ghost"):
            return Permission.WRITE
        if username in ("kodiak", ):
            return Permission.ADMIN
        raise Exception

    mocker.patch.object(api_client, "get_permissions_for_username",
                        get_permissions_for_username_patch)

    res = await api_client.get_event_info(branch_name="master", pr_number=100)
    assert res is not None
    assert res == block_event
예제 #8
0
async def test_pr_update_bad_merge(
    mocker: MockFixture, event_response: queries.EventInfoResponse, pr: PR
) -> None:
    """
    Update should return false on an error
    """
    mocker.patch.object(PR, "get_event", return_value=wrap_future(event_response))
    res = Response()
    res.status_code = 409
    res._content = b"{}"
    mocker.patch(
        "kodiak.pull_request.queries.Client.merge_branch", return_value=wrap_future(res)
    )

    res = await pr.update()
    assert not res
예제 #9
0
def test_webhook_event(client: TestClient, event_name: str,
                       mocker: MockFixture) -> None:
    """Test all of the events we have"""
    fake_redis = FakeRedis()
    mocker.patch("kodiak.entrypoints.ingest.get_redis",
                 return_value=wrap_future(fake_redis))
    for index, fixture_path in enumerate(
        (Path(__file__).parent / "test" / "fixtures" / "events" /
         event_name).rglob("*.json")):
        data = json.loads(fixture_path.read_bytes())

        body, sha = get_body_and_hash(data)

        assert fake_redis.called_rpush_cnt == index
        res = client.post(
            "/api/github/hook",
            data=body,
            headers={
                "X-Github-Event": event_name,
                "X-Hub-Signature": sha
            },
        )
        assert res.status_code == status.HTTP_200_OK
        assert fake_redis.called_rpush_cnt == index + 1

    assert fake_redis.called_rpush_cnt == fake_redis.called_ltrim_cnt
예제 #10
0
파일: test_queries.py 프로젝트: wuub/kodiak
async def test_get_config_for_ref_dot_github(
    api_client: Client, mocker: MockFixture
) -> None:
    """
    We should be able to parse from .github/.kodiak.toml
    """
    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(
            dict(
                data=dict(
                    repository=dict(
                        rootConfigFile=None,
                        githubConfigFile=dict(
                            text="# .github/.kodiak.toml\nversion = 1\nmerge.method = 'rebase'"
                        ),
                    )
                )
            )
        ),
    )

    res = await api_client.get_config_for_ref(ref="main")
    assert res is not None
    assert isinstance(res.parsed, V1) and res.parsed.merge.method == MergeMethod.rebase
예제 #11
0
async def test_pr_update_missing_event(mocker: MockFixture, pr: PR) -> None:
    """
    Return False if get_event res is missing
    """
    mocker.patch.object(PR, "get_event", return_value=wrap_future(None))

    res = await pr.update()
    assert not res
예제 #12
0
async def test_get_permissions_for_username_missing(
    api_client: Client, mocker: MockFixture, mock_get_token_for_install: None
) -> None:
    not_found = Response()
    not_found.status_code = 404
    mocker.patch("kodiak.queries.http.Session.get", return_value=wrap_future(not_found))
    async with api_client as api_client:
        res = await api_client.get_permissions_for_username("_invalid_username")
    assert res == Permission.NONE
예제 #13
0
async def test_get_permissions_for_username_read(
    api_client: Client, mocker: MockFixture, mock_get_token_for_install: None
) -> None:
    response = Response()
    response.status_code = 200
    response._content = PERMISSION_OK_READ_USER_RESPONSE

    mocker.patch("kodiak.queries.http.Session.get", return_value=wrap_future(response))
    async with api_client as api_client:
        res = await api_client.get_permissions_for_username("ghost")
    assert res == Permission.READ
예제 #14
0
async def test_get_default_branch_name_error(
    api_client: Client, mocker: MockFixture
) -> None:
    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(dict(data=None, errors=[{"test": 123}])),
    )

    res = await api_client.get_default_branch_name()
    assert res is None
예제 #15
0
async def test_get_subscription_missing_blocker_fully(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    If a user is new to Kodiak we will not have set subscription information in
    Redis. We should handle this case by returning an empty subscription.
    """
    fake_redis = create_fake_redis_reply({})
    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res is None
예제 #16
0
파일: test_queries.py 프로젝트: wuub/kodiak
async def test_get_open_pull_requests(
    mocker: MockFixture, api_client: Client, mock_get_token_for_install: None
) -> None:
    """
    We should stop calling the API after reaching an empty page.
    """
    patched_session_get = mocker.patch(
        "kodiak.queries.http.Session.get",
        side_effect=[
            wrap_future(generate_page_of_prs(range(1, 101))),
            wrap_future(generate_page_of_prs(range(101, 201))),
            wrap_future(generate_page_of_prs(range(201, 251))),
            wrap_future(generate_page_of_prs([])),
        ],
    )

    async with api_client as api_client:
        res = await api_client.get_open_pull_requests()

    assert res is not None
    assert len(res) == 250
    assert patched_session_get.call_count == 4
예제 #17
0
파일: test_main.py 프로젝트: toder34/kodiak
def test_webhook_event_missing_github_event(client: TestClient,
                                            mocker: MockFixture) -> None:
    handle_webhook_event = mocker.patch("kodiak.main.handle_webhook_event",
                                        return_value=wrap_future(None))
    data = {"hello": 123}

    body, sha = get_body_and_hash(data)

    assert handle_webhook_event.called is False
    res = client.post("/api/github/hook",
                      data=body,
                      headers={"X-Hub-Signature": sha})
    assert res.status_code == status.HTTP_400_BAD_REQUEST
    assert handle_webhook_event.called is False
예제 #18
0
파일: test_queries.py 프로젝트: wuub/kodiak
async def test_get_config_for_ref_error(
    api_client: Client, mocker: MockFixture
) -> None:
    """
    We should return None when there is an error.
    """
    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(dict(data=None, errors=[{"test": 123}])),
    )

    res = await api_client.get_config_for_ref(ref="main")
    assert res is None
예제 #19
0
def test_webhook_event_missing_github_event(client: TestClient,
                                            mocker: MockFixture) -> None:
    fake_redis = FakeRedis()
    mocker.patch("kodiak.entrypoints.ingest.get_redis",
                 return_value=wrap_future(fake_redis))
    data = {"hello": 123}

    body, sha = get_body_and_hash(data)

    assert fake_redis.called_rpush_cnt == 0
    res = client.post("/api/github/hook",
                      data=body,
                      headers={"X-Hub-Signature": sha})
    assert res.status_code == status.HTTP_400_BAD_REQUEST
    assert fake_redis.called_rpush_cnt == 0
예제 #20
0
async def test_get_subscription_missing_blocker_and_data(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    Check with empty string for data
    """
    fake_redis = create_fake_redis_reply({
        b"account_id": b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker": b"",
        b"data": b"",
    })
    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=None)
예제 #21
0
파일: test_queries.py 프로젝트: wuub/kodiak
async def test_get_open_pull_requests_page_limit(
    mocker: MockFixture, api_client: Client, mock_get_token_for_install: None
) -> None:
    """
    We should fetch at most 20 pages.
    """
    pages = [range(n, n + 100) for n in range(1, 3001, 100)]
    assert len(pages) == 30
    patched_session_get = mocker.patch(
        "kodiak.queries.http.Session.get",
        side_effect=[wrap_future(generate_page_of_prs(p)) for p in pages],
    )

    async with api_client as api_client:
        res = await api_client.get_open_pull_requests()
    assert res is not None
    assert len(res) == 2000
    assert patched_session_get.call_count == 20, "stop calling after 20 pages"
예제 #22
0
async def test_get_subscription_missing_blocker(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    We set subscription_blocker to empty string from the web_api. This should be
    consider equivalent to a missing subscription blocker.
    """
    fake_redis = create_fake_redis_reply({
        b"account_id": b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker": b"",
    })
    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=None)
예제 #23
0
async def test_get_event_info_blocked(
    api_client: Client,
    blocked_response: Dict[str, Any],
    block_event: EventInfoResponse,
    mocker: MockFixture,
    setup_redis: object,
) -> None:

    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(
            GraphQLResponse(data=blocked_response.get("data"),
                            errors=blocked_response.get("errors"))),
    )
    res = await api_client.get_event_info(pr_number=100)
    assert res is not None
    assert res == block_event
예제 #24
0
파일: test_main.py 프로젝트: toder34/kodiak
def test_webhook_event_invalid_signature(client: TestClient,
                                         mocker: MockFixture) -> None:
    handle_webhook_event = mocker.patch("kodiak.main.handle_webhook_event",
                                        return_value=wrap_future(None))
    data = {"hello": 123}

    # use a different dict for the signature so we get an signature mismatch
    _, sha = get_body_and_hash({})

    assert handle_webhook_event.called is False
    res = client.post(
        "/api/github/hook",
        json=data,
        headers={
            "X-Github-Event": "content_reference",
            "X-Hub-Signature": sha
        },
    )
    assert res.status_code == status.HTTP_400_BAD_REQUEST
    assert handle_webhook_event.called is False
예제 #25
0
async def test_get_subscription_unknown_blocker(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    Handle unknown blocker by allowing user access.
    """
    fake_redis = create_fake_redis_reply({
        b"account_id":
        b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker":
        b"invalid_subscription_blocker",
    })

    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=None)
예제 #26
0
async def test_get_subscription_seats_exceeded_invalid_data(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    Handle invalid data gracefully.
    """
    fake_redis = create_fake_redis_reply({
        b"account_id": b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker": b"seats_exceeded",
        b"data": b"*(invalid-data4#",
    })

    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=SeatsExceeded(allowed_user_ids=[]),
    )
예제 #27
0
async def test_get_event_info_blocked(
    api_client: Client,
    blocked_response: dict,
    block_event: EventInfoResponse,
    mocker: MockFixture,
) -> None:
    # TODO(sbdchd): we should use monkeypatching
    # mypy doesn't handle this circular type

    mocker.patch.object(
        api_client,
        "send_query",
        return_value=wrap_future(
            GraphQLResponse(data=blocked_response.get("data"),
                            errors=blocked_response.get("errors"))),
    )

    res = await api_client.get_event_info(
        config_file_expression="master:.kodiak.toml", pr_number=100)
    assert res is not None
    assert res == block_event
예제 #28
0
def test_webhook_event_invalid_signature(client: TestClient,
                                         mocker: MockFixture) -> None:
    fake_redis = FakeRedis()
    mocker.patch("kodiak.entrypoints.ingest.get_redis",
                 return_value=wrap_future(fake_redis))
    data = {"hello": 123}

    # use a different dict for the signature so we get an signature mismatch
    _, sha = get_body_and_hash({})

    assert fake_redis.called_rpush_cnt == 0
    res = client.post(
        "/api/github/hook",
        json=data,
        headers={
            "X-Github-Event": "content_reference",
            "X-Hub-Signature": sha
        },
    )
    assert res.status_code == status.HTTP_400_BAD_REQUEST
    assert fake_redis.called_rpush_cnt == 0
예제 #29
0
async def test_get_subscription_seats_exceeded_missing_data(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    For backwards compatibility we cannot guarantee that "seats_exceeded" will
    have the data parameter.
    """
    fake_redis = create_fake_redis_reply({
        b"account_id":
        b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker":
        b"seats_exceeded",
    })

    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=SeatsExceeded(allowed_user_ids=[]),
    )
예제 #30
0
async def test_get_subscription_seats_exceeded_no_seats(
        api_client: Client, mocker: MockFixture,
        mock_get_token_for_install: None) -> None:
    """
    When an account has 0 seats we will not have any allowed_user_ids.
    """
    fake_redis = create_fake_redis_reply({
        b"account_id":
        b"DF5C23EB-585B-4031-B082-7FF951B4DE15",
        b"subscription_blocker":
        b"seats_exceeded",
        b"data":
        b'{"kind":"seats_exceeded", "allowed_user_ids": []}',
    })
    mocker.patch("kodiak.event_handlers.get_redis",
                 return_value=wrap_future(fake_redis))
    async with api_client as api_client:
        res = await api_client.get_subscription()
    assert res == Subscription(
        account_id="DF5C23EB-585B-4031-B082-7FF951B4DE15",
        subscription_blocker=SeatsExceeded(allowed_user_ids=[]),
    )