Colin-b/pytest_httpx

Problem running pytests in GitHub environment

Closed this issue · 2 comments

I have a FastAPI application that's using httpx, and I'm having trouble getting the pytests to run successfully in the GitHub environment when I push the code. The tests run fine when run in a local Docker container.

Here's my Dockerfile

FROM python:3.9.7-slim
WORKDIR /app/
ENV PYTHONPATH "${PYTHONPATH}:/"
COPY requirements.txt .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]

And here's the requirements.txt file it uses to get the modules the application needs:

fastapi==0.82.0
uvicorn==0.18.3
pydantic==1.10.2
pylint==2.15.2
pytest==7.1.3
boto3==1.24.68
python-dotenv==0.21.0
pytest-mock==3.8.2
sseclient==0.0.27
pytest-asyncio==0.19.0
wheel==0.37.1
httpx==0.23.0
pytest-httpx==0.21.0
pytest-trio==0.7.0

Here's the .github/workflows/unit-tests.yml file that runs the tests in the GitHub environment:

name: unit-tests

on: [push]
jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 1
          ref: ${{ github.event.inputs.branch_name }}

      - name: Set up Python 3.9.x
        uses: actions/setup-python@v1
        with:
          python-version: 3.9.x

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install fastapi==0.82.0
          pip install pydantic==1.10.2
          pip install pytest==7.1.3
          pip install boto3==1.24.68
          pip install python-dotenv==0.21.0
          pip install pytest-mock==3.8.2
          pip install sseclient==0.0.27
          pip install pytest-asyncio==0.19.0
          pip install httpx==0.23.0
          pip install pytest-httpx==0.21.0
          pip install pytest-cov==4.0.0
          pip install pytest-trio==0.7.0

      - name: Running unit tests
        run: |
          pytest -v --cov=app tests/unit/ --asyncio-mode=strict

And here's part of the errors generated when run in the GitHub environment:

Run pytest --cov=app tests/unit/ --asyncio-mode=strict
============================= test session starts ==============================
platform linux -- Python 3.9.14, pytest-7.1.3, pluggy-1.0.0
rootdir: /home/runner/work/integration-engine/integration-engine
plugins: httpx-0.21.0, anyio-3.[6](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:7).1, cov-4.0.0, asyncio-0.19.0, mock-3.8.2, trio-0.[7](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:8).0
asyncio: mode=strict
collected 69 items

tests/unit/test_main.py .                                                [  1%]
tests/unit/dependencies/test_validate_requests.py ..                     [  4%]
tests/unit/helpers/test_agent_command_helper.py .......                  [ 14%]
tests/unit/helpers/test_agent_message_helper.py ....                     [ 20%]
tests/unit/helpers/test_cognito_helper.py ssFF                           [ 26%]
tests/unit/helpers/test_mercure_helper.py .....                          [ 33%]
tests/unit/helpers/test_pmb_helper.py ....                               [ 39%]
tests/unit/helpers/test_repository_helper.py .......                     [ 49%]
tests/unit/repositories/test_agent_message_repository.py ...             [ 53%]
tests/unit/repositories/test_agent_repository.py .....                   [ 60%]
tests/unit/routers/test_agent_communication.py ..............            [ [8](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:9)1%]
tests/unit/routers/test_agent_search.py ....                             [ 86%]
tests/unit/routers/test_authentication.py ..                             [ 8[9](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:10)%]
tests/unit/routers/test_diagnostics.py ..                                [ 92%]
tests/unit/routers/test_healthz.py ..                                    [ 95%]
tests/unit/routers/test_registration.py ...                              [[10](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:11)0%]

=================================== FAILURES ===================================
____ test_get_token_with_invalid_credentials_returns_error_message[asyncio] ____

httpx_mock = <pytest_httpx._httpx_mock.HTTPXMock object at 0x7f9346b296d0>

    @pytest.mark.anyio
    async def test_get_token_with_invalid_credentials_returns_error_message(httpx_mock: HTTPXMock):
        """Test that get token with invalid credentials returns error message"""
        mock_valid_response = {
            "errorMessage": "test_error_message"
        }
        mock_user_auth_request = UserAuthenticationRequest(
            username="test_invalid_username",
            ***
        )
        httpx_mock.add_response(json=mock_valid_response)
>       response = await get_token(mock_user_auth_request)

tests/unit/helpers/test_cognito_helper.py:50: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
app/helpers/cognito_helper.py:37: in get_token
    data = await call_cognito(url=url, body=body)
app/helpers/cognito_helper.py:59: in call_cognito
    request = httpx.Request("GET", url=url, json=body)
/opt/hostedtoolcache/Python/3.9.[14](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:15)/x64/lib/python3.9/site-packages/httpx/_models.py:326: in __init__
    self.url = URL(url)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'URL' object has no attribute '_uri_reference'") raised in repr()] URL object at 0x7f9346aa[23](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:24)70>
url = None, kwargs = {}

    def __init__(
        self, url: typing.Union["URL", str, RawURL] = "", **kwargs: typing.Any
    ) -> None:
        if isinstance(url, (str, tuple)):
            if isinstance(url, tuple):
                raw_scheme, raw_host, port, raw_path = url
                scheme = raw_scheme.decode("ascii")
                host = raw_host.decode("ascii")
                if host and ":" in host and host[0] != "[":
                    # it's an IPv6 address, so it should be enclosed in "[" and "]"
                    # ref: https://tools.ietf.org/html/rfc[27](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:28)[32](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:33)#section-2
                    # ref: https://tools.ietf.org/html/rfc[39](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:40)86#section-3.2.2
                    host = f"[{host}]"
                port_str = "" if port is None else f":{port}"
                path = raw_path.decode("ascii")
                url = f"{scheme}://{host}{port_str}{path}"
    
            try:
                self._uri_reference = rfc3986.iri_reference(url).encode()
            except rfc3986.exceptions.InvalidAuthority as exc:
                raise InvalidURL(message=str(exc)) from None
    
            if self.is_absolute_url:
                # We don't want to normalize relative URLs, since doing so
                # removes any leading `../` portion.
                self._uri_reference = self._uri_reference.normalize()
        elif isinstance(url, URL):
            self._uri_reference = url._uri_reference
        else:
>           raise TypeError(
                f"Invalid type for url.  Expected str or httpx.URL, got {type(url)}: {url!r}"
            )
E           TypeError: Invalid type for url.  Expected str or httpx.URL, got <class 'NoneType'>: None

/opt/hostedtoolcache/Python/3.9.14/x[64](https://github.com/rectanglehealth/integration-engine/actions/runs/3207285033/jobs/5242003697#step:5:65)/lib/python3.9/site-packages/httpx/_urls.py:102: TypeError

My apologies if this isn't the right forum to be asking this, but I'm at my wits end trying to resolve this. Any ideas, pointers or suggestions are welcome.

Hello @writeson ,

According to your stacktrace the issue comes from your code, the url you provide is None and obviously it makes no sense ;)

>       response = await get_token(mock_user_auth_request)

tests/unit/helpers/test_cognito_helper.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app/helpers/cognito_helper.py:37: in get_token
    data = await call_cognito(url=url, body=body)

Colin-b,
Thanks for the response, I'll have to look into that as I thought the URL was coming from an environment variable, but maybe not in the GitHub environment.