Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unconsistent behavior between uvicorn and pytest #501

Open
ticapix opened this issue Jun 23, 2024 · 2 comments
Open

unconsistent behavior between uvicorn and pytest #501

ticapix opened this issue Jun 23, 2024 · 2 comments

Comments

@ticapix
Copy link

ticapix commented Jun 23, 2024

Hello,

In a server.py, I have a simple health check

# in server.py

from datetime import datetime, timezone
from blacksheep import Application, get, ok

app = Application()

@get("/health")
async def health():
    return ok(f"{datetime.now(timezone.utc).isoformat()}")

and reusing the TestClient from https://www.neoteroi.dev/blacksheep/testing/#using-the-testclient-with-pytest, I have a simple test

# in test_api.py

import pytest
from blacksheep.testing import TestClient

@pytest.mark.asyncio(scope="session")
async def test_health_check(test_client: TestClient) -> None:
    response = await test_client.get("/health")
    assert response is not None
    assert response.status is 200
    print(response.headers)
    assert response.has_header(b'content-type')

Bug

When running with pytest -sv, I'm getting an error because response.headers is empty.

$ PYTHONPATH=. pytest -sv
============================= test session starts ==============================
platform linux -- Python 3.11.2, pytest-8.2.2, pluggy-1.5.0 -- /home/debian/workspace/cde/cde-global/applications/ontology-server/.direnv/python-3.11.2/bin/python3
cachedir: .pytest_cache
rootdir: /home/debian/workspace/cde/cde-global/applications/ontology-server
plugins: asyncio-0.23.7
asyncio: mode=Mode.STRICT
collected 1 item

tests/test_api.py::test_health_check <Headers []>
FAILED

=================================== FAILURES ===================================
______________________________ test_health_check _______________________________

test_client = <blacksheep.testing.client.TestClient object at 0x7f2d176763d0>

    @pytest.mark.asyncio(scope="session")
    async def test_health_check(test_client: TestClient) -> None:
        response = await test_client.get("/health")
        assert response is not None
        assert response.status is 200
        print(response.headers)
>       assert response.has_header(b'content-type')
E       AssertionError: assert False
E        +  where False = <bound method Message.has_header of <Response 200>>(b'content-type')
E        +    where <bound method Message.has_header of <Response 200>> = <Response 200>.has_header

tests/test_api.py:13: AssertionError
=========================== short test summary info ============================
FAILED tests/test_api.py::test_health_check - AssertionError: assert False
============================== 1 failed in 0.04s ===============================

Expected

However, when I run the server with uvicorn server:app and I try with wget, I do get a content-type header.

$ wget -qS -O - http://127.0.0.1:8000/health
  HTTP/1.1 200 OK
  date: Sun, 23 Jun 2024 21:11:01 GMT
  server: uvicorn
  content-type: text/plain; charset=utf-8
  content-length: 32
2024-06-23T21:11:02.151666+00:00

Has anyone an idea of the component/code adding this header after I return my response object and not present when testing ?

Thank you,
Pierre

@ticapix ticapix changed the title unconsistent behavior when testing unconsistent behavior between uvicorn and pytest Jun 23, 2024
@tyzhnenko
Copy link
Contributor

Hey, @ticapix, I found your question interesting. So, I've spent some time trying to figure out the root cause. Here's it...

Blacksheep sets Content-Type header when preparing a response for ASGI. By default, TestSimulator class doesn't set it. As a result you can see the header only during a regular HTTP request.

cdef void set_headers_for_response_content(Response message):
    cdef Content content = message.content

    if not content:
        message._add_header(b'content-length', b'0')
        return

    message._add_header(b'content-type', content.type or b'application/octet-stream')

    if should_use_chunked_encoding(content):
        message._add_header(b'transfer-encoding', b'chunked')
    else:
        message._add_header(b'content-length', str(content.length).encode())

@tyzhnenko
Copy link
Contributor

@ticapix take a look my PR #502

❯ pytest -svvv
====================================================================== test session starts ======================================================================
platform darwin -- Python 3.12.4, pytest-8.2.2, pluggy-1.5.0 -- /Users/User/.virtualenvs/blacksheep-issues/bin/python
cachedir: .pytest_cache
rootdir: /Users/User/workspace/neoteroi/BlackSheepIssues
plugins: asyncio-0.23.7
asyncio: mode=Mode.STRICT
collected 1 item                                                                                                                                                

test_issue_501.py::test_health_check <Headers [(b'content-type', b'text/plain; charset=utf-8'), (b'content-length', b'32')]>
PASSED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants