Giter VIP home page Giter VIP logo

pytest-httpserver's People

Contributors

aziot avatar azmeuk avatar beliaev-maksim avatar csernazs avatar dependabot[bot] avatar edgarrmondragon avatar emmett-rayes avatar fabaff avatar florianludwig avatar kianmeng avatar matez0 avatar maxshvets avatar mgorny avatar pre-commit-ci[bot] avatar robbotic1 avatar rominf avatar sneakypete81 avatar thrau avatar toddrme2178 avatar wouterklouwen-youview avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pytest-httpserver's Issues

Idea: add variable or partial match support in urls

While I find pytest-httpserver extremely useful and use it daily, I find that it's missing one feature - it's not possible to expect_request() with a variable in it or expect_request() with a partial match. Example use case would be testing a DELETE operation. I'd like to mock an api endpoint that handles customer deletion by url (e.g. /customer/42). I'd like to be able to expect a any request that follows the patter of /customer/<customer_id>. Thanks for the good work!

query_string should not depend on order

Hey!

Great Package but I have an issue with the way query_string is handled. Generally in the real world, it does not make a difference in which order parameters are attached to the request.
I think pytest-httpserver should adhere to that not make a simple string comparison when choosing the correct matcher.

Best regards

Raise error if any request is received

Hi, and thank you for this super useful project!

How would I write a test to assert that no requests are received by the test server?

I've tried using an httpserver with no matchers added, followed by a wait with raise_assertions=True and stop_on_nohandler=True, but I still get the error AssertionError: Wait timeout occurred, but some handlers left.

My code looks a bit like this:

server_url = httpserver.url_for("/go")
env_vars = {"URL_ENV_VAR": server_url}

with patch.dict(os.environ, env_vars), httpserver.wait(
    raise_assertions=True, stop_on_nohandler=True, timeout=2
):
    ...

`werkzeug.urls` deprecation warnings with Werkzeug 2.3.0

Werkzeug 2.3.0 has deprecated most of the werkzeug.urls module:

Due to that, running something like:

import requests

def test_httpserver(httpserver):
    query = {"k": "v"}
    httpserver.expect_request("/", query_string=query).respond_with_data("OK")
    res = requests.get(httpserver.url_for("/"), params=query)
    res.raise_for_status()

now results in warnings:

mocking/test_httpserver_minimal.py::test_httpserver
  .../site-packages/pytest_httpserver/httpserver.py:216: DeprecationWarning: 'werkzeug.urls.url_decode' is deprecated and will be removed in Werkzeug 2.4. Use 'urllib.parse.parse_qs' instead.
    query = werkzeug.urls.url_decode(request_query_string)

mocking/test_httpserver_minimal.py::test_httpserver
  .../site-packages/werkzeug/urls.py:1195: DeprecationWarning: 'werkzeug.urls.url_unquote_plus' is deprecated and will be removed in Werkzeug 2.4. Use 'urllib.parse.unquote_plus' instead.
    url_unquote_plus(key, charset, errors),

mocking/test_httpserver_minimal.py::test_httpserver
  .../site-packages/werkzeug/urls.py:1196: DeprecationWarning: 'werkzeug.urls.url_unquote_plus' is deprecated and will be removed in Werkzeug 2.4. Use 'urllib.parse.unquote_plus' instead.
    url_unquote_plus(value, charset, errors),

coming from here:

query = werkzeug.urls.url_decode(request_query_string)

Errors in documentation

pytest_httpserver.httpserver.RequestMatcher is listed twice, and pytest_httpserver.httpserver.HeaderValueMatcher is not listed.

Async support?

Has anyone looked into running the server asynchronously?

I've got client code that runs using aiohttp and when I'm testing connections that timeout (using httpserver.expect_request(f"/{path}", method="POST").respond_with_handler(timeout)) it looks like the server hangs during the timeout period leaving the inbound async requests from tests to timeout.

Allow using wildcard in request URI

Currently when using the httpserver.expect_request function, I am only allowed to use the full URI string. It would be nice if it would also accept URI's with wildcards or regex.

For instance, I have an endpoint called GET /users/{USER_ID}/role that will return the role of a user with a given USER_ID. If I would like to mock this endpoint I would have to specify a httpserver.expect_request for each of the users I am using in my tests:
httpserver.expect_request("/users/1/role").respond_with_json("admin")
httpserver.expect_request("/users/2/role").respond_with_json("admin")
httpserver.expect_request("/users/3/role").respond_with_json("admin")
It would be way more convenient if we could just pass in a wildcard instead:
httpserver.expect_request("/users/*/role").respond_with_json("admin")

except_request can't handle `?` or `%3F`

Hi, great plugin! I'm using it for one of my current projects and it works mostly great!

Today I came up with an error when the URL contains a ? (question mark). The Server Fails with 500 no matter what I try. Is that a limitation of the query match implementation?

Use Session scoped fixture instead of global

I may be missing a particular design reason that means this is not possible, however I think it could be beneficial to make the httpserver fixture a session-scoped fixture rather than use a global variable to store it.

By making it session-scoped it will only be created once as needed, which I believe is the intention of the global variable. This also means that other session-scoped fixtures can use it, where currently they are unable to.

propagate assertion errors from custom handlers

I would like to perform fuzzy assertions on the request body. Since that doesn't seem possible with the default RequestHandler, I added my own handler that does the assertions, and would like that assertion errors produced in these handlers are propagated to the pytest run via httpserver.check_assertions().

What I've currently done is:

my test:

def assert_appender(httpserver, handler):
    def _handler(response):
        try:
            return handler(response)
        except AssertionError as e:
            httpserver.add_assertion(e)
            raise

    return _handler


def test_append(httpserver: HTTPServer):
    def handler(request):
        actual_data = request.data.decode()
        assert 'hello' in actual_data
        # ... more asserts
        return Response('', 200)

    httpserver.expect_request("/path").respond_with_handler(assert_appender(httpserver, handler))

    httpserver.check_assertions()  # should re-raise the assertion error from "handler"
    assert response.ok

and i've modified this bit here:

def check_assertions(self):
"""
Raise AssertionError when at least one assertion added
The first assertion added by :py:meth:`add_assertion` will be raised and
it will be removed from the list.
This method can be useful to get some insights into the errors happened in
the sever, and to have a proper error reporting in pytest.
"""
if self.assertions:
raise AssertionError(self.assertions.pop(0))

to do the following:

        if self.assertions:
            assertion = self.assertions.pop(0)
            if isinstance(assertion, AssertionError):
                raise assertion
            raise AssertionError(assertion)

now i get the proper assertion formatting in pytest:

    def handler(request):
        actual_data = request.data.decode()
>       assert "hello" in actual_data
E       assert 'hello' in '{"foo":"bar"}'

test_http.py:34: AssertionError

would this be interesting to add? i'm happy to contribute a PR

Proxy support

Add proxy support so the code which needs to be tested can use the server in proxy mode.
It means that a slightly different requests needs to be handled, in addition to the CONNECT method which is used for TLS.

Unfortunatelly werkzeug has no proxy support, so it may be better to use the wsgiprox library (https://github.com/webrecorder/wsgiprox). It also support TLS.

Allow tests requiring multiple servers at once

Remove Plugin.SERVER singleton in order to make http-server reusable

Based on the need we had we used pytest-httpserver to test multiple server instances running at once. After checking documentation and background we found that on the purpose of the single was to override start-up time on werzeuk server library. We confirmed that that is not the case anymore at least at Werkzeug==1.0.1 (so we need to update documentation too).

tests_original_src

tests_with_changes

1.0.4: sphinx warnings `reference target not found`

On building my packages I'm using sphinx-build command with -n switch which shows warmings about missing references. These are not critical issues.
Here is the output with warnings:

[tkloczko@devel-g2v pytest-httpserver-1.0.4]$ /usr/bin/sphinx-build -n -T -b man doc build/sphinx/man
Running Sphinx v4.5.0
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: 0 added, 1 changed, 0 removed
reading sources... [100%] changes
looking for now-outdated files... none found
pickling environment... done
checking consistency... /home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/doc/guide.rst: WARNING: document isn't included in any toctree
done
writing... python-pytest-httpserver.3 { tutorial howto fixtures api background changes upgrade } /home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer:: WARNING: py:class reference target not found: ssl.SSLContext
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer:11: WARNING: py:class reference target not found: Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer:11: WARNING: py:class reference target not found: Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.application:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.create_matcher:1: WARNING: py:class reference target not found: RequestMatcher
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.dispatch:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.dispatch:: WARNING: py:class reference target not found: werkzeug.wrappers.response.Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_oneshot_request:5: WARNING: py:class reference target not found: URIPattern
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_oneshot_request:5: WARNING: py:func reference target not found: re.compile
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_ordered_request:5: WARNING: py:class reference target not found: URIPattern
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_ordered_request:5: WARNING: py:func reference target not found: re.compile
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_request:16: WARNING: py:class reference target not found: URIPattern
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.expect_request:16: WARNING: py:func reference target not found: re.compile
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.respond_nohandler:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.HTTPServer.start:10: WARNING: py:class reference target not found: HTTPServerError
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond:: WARNING: py:class reference target not found: werkzeug.wrappers.response.Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond:3: WARNING: py:class reference target not found: NoHandlerError
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond_with_data:3: WARNING: py:class reference target not found: Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond_with_handler:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond_with_handler:: WARNING: py:class reference target not found: werkzeug.wrappers.response.Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandler.respond_with_response:: WARNING: py:class reference target not found: werkzeug.wrappers.response.Response
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher:5: WARNING: py:class reference target not found: URIPattern
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher:5: WARNING: py:func reference target not found: re.compile
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher.difference:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher.match:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher.match_data:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestMatcher.match_json:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
/home/tkloczko/rpmbuild/BUILD/pytest-httpserver-1.0.4/pytest_httpserver/httpserver.py:docstring of pytest_httpserver.httpserver.RequestHandlerList.match:: WARNING: py:class reference target not found: werkzeug.wrappers.request.Request
done
build succeeded, 30 warnings.

Type hints

Hi,

This is an awesome project! I'm trying to incorporate it into a codebase that has type checking done with mypy. It looks like the type hints aren't loaded because there's no py.typed file.

This generally requires creating a py.typed empty file and two extra lines in setup.py.

https://mypy.readthedocs.io/en/latest/installed_packages.html#creating-pep-561-compatible-packages

Is this something you would be interested in? I see there's some type hints already. It's not a bad idea to test the hints with mypy, either.

Thank you!

expect_result and expect_oneshot_request do no handle ?

when using:
httpserver.expect_request(url_addition).respond_with_json(expected_result)
if url_addition has a ? in it, the server response fails (500).

Example of working url_addition:
/widgetperiod=2020

Example of non-working url_addition:
/widget?period=2020

This is something I need so I can't get around not having ?. Please have a look.

Add all the necessary imports in the code examples in the doc

In some snippets the documentation provides the import statement for requests but not for the HTTPServer object, adding all the required imports should make the documentation more clear and easier to use specially for new users.

Example:
Current:

import requests


def test_json_client(httpserver: HTTPServer):
    httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})
    assert requests.get(httpserver.url_for("/foobar")).json() == {"foo": "bar"}

Proposed:

import requests
from pytest_httpserver import HTTPServer

def test_json_client(httpserver: HTTPServer):
    httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})
    assert requests.get(httpserver.url_for("/foobar")).json() == {"foo": "bar"}

header auth matching breaks with Werkzeug>=2.3.0

With Werkzeug 2.3.0 the Authorization header parsing has been refactored quite a lot: pallets/werkzeug@0bdf9ba.
Here's an excerpt from the changeset:

  • Refactor the Authorization and WWWAuthenticate header data structures.
    :issue:1769, :pr:2619
    ...
    • Neither class is a dict anymore, although they still implement getting,
      setting, and deleting auth[key] and auth.key syntax, as well as
      auth.get(key) and key in auth.
      ...

Unfortunately, this means that this class does not implement the __eq__ dunder method, which breaks this equality check:

def authorization_header_value_matcher(actual: Optional[str], expected: str) -> bool:
return parse_authorization_header(actual) == parse_authorization_header(expected)

Here's an example which works with pytest-httpserver==1.0.6 and Werkzeug==2.2.3, but breaks with Werkzeug==2.3.0:

def test_httpserver(httpserver):
    test_headers = { "Authorization": "Bearer Rooooar" }
    httpserver.expect_request("/", headers=test_headers).respond_with_data("OK")
    response = requests.get(httpserver.url_for("/"), headers=test_headers)
    assert response.ok

This could either be fixed by adjusting the matcher, or by adding the __eq__ dunder method to Werkzeug's Authorization class (which might be useful for others as well?).

Change status code when no handler is found

Currently the library responds with http status 500 when no handler is found for the request.
In some cases having 404 would be better so it would be great to the user to override this number.

Server only listens on IPv4

It seems that the server only listens on IPv4, but httpserver.host returns localhost.

This will resolve to ::1 on a dual-stack system, so connecting to it will fail. httpserver.host should either return an IPv4 address, or the server should listen on both.

On most OSs, listening to just IPv6 implicitly listens on IPv4, so it might just be a matter of listening on the latter.

Can HTTPServer().handlers be modified from user code?

Hi, I need to delete a specific item from the permanent handlers list while leaving all other handlers intact. I know that I can delete it from HTTPServer().handlers. But it is not listed in API docs like HTTPServerBase().port and I am unsure whether it is a part of the public interface and whether modifying it directly is recommended/allowed.

turn off (disable) fixture httpserver for some tests

Hello csernazs.

There are several test cases, where I should be emulate timeout (connection error) to httpserver.

If I run one (current) test file by name without fixture httpserver inside - then my app have connection error and test case passed.
But if I run pytest for several test files (fixture httpserver used inside other test files in folder) - then pytest used fixture httpserver global, and httserver return 500 status instead connection error (timeout) for my app.

Can you provide example, how I can to turn off (disable) fixture httpserver for some tests in folder?

Best Regards,
Viktor L.

Latest release version mismatch

The tag says 3.0.2, the pypi says 0.3.2, some commits say the same yet changelog again talks about 3.0.2. In pypi archive when the code unpacks it also contians folder pytest-httpserver-3.0.2.

Could you perhaps release 0.3.3 and sync it everywhere in order to avoid confusion?

Give a way to choose between listenning to IPV4 or IPV6

I want to test how my application behaves when making requests to http servers responding on IPV4 or IPV6 or both. I would love if there was a way to explicitly tell httpserver to listen on IPV6.

I have read the documentation about dual stack, but it does not cover my usecase. From what I read on #61 I understand that it cannot listen to both IP protocols, but this is not a big deal, I could run several unit tests.

I could create my own httpserver_listen_address fixture with [::1] as the hostname, but the httpserver fixture is session-wide and I don't want to choose between IPV4 and IPV6 for the whole session as I would like to test both.

Could a solution be to implement a httpserver6 fixture that would only run on IPV6?

What do you think?

Add client-side to the https code sample

I naively tried to use the https code sample with aiohttp and ended with a ClientConnectorSSLError: aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host localhost:37467 ssl:default [Cannot create a client socket with a PROTOCOL_TLS_SERVER context (_ssl.c:801)]

import aiohttp
import pytest
import ssl
import trustme


@pytest.fixture(scope="session")
def ca():
    return trustme.CA()


@pytest.fixture(scope="session")
def localhost_cert(ca):
    return ca.issue_cert("localhost")


@pytest.fixture(scope="session")
def httpserver_ssl_context(localhost_cert):
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

    crt = localhost_cert.cert_chain_pems[0]
    key = localhost_cert.private_key_pem
    with crt.tempfile() as crt_file, key.tempfile() as key_file:
        context.load_cert_chain(crt_file, key_file)

    return context


@pytest.mark.asyncio
async def test_https(httpserver, httpserver_ssl_context):
    httpserver.expect_request("/").respond_with_data("hello world!")
    connector = aiohttp.TCPConnector(ssl_context=httpserver_ssl_context)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(httpserver.url_for("/")) as result:
            assert result == "hello world!"

This is not technically a pytest-httpserver issue but I though this would be nice to provide the code sample for famous http libraries as requests or aiohttp.

This might be related to #60.

What do you think?

MyPy complains when trying to update DEFAULT_MATCHERS

The advanced header matching section of the documentation suggests that to set a custom header matcher globally one can do:

HeaderValueMatcher.DEFAULT_MATCHERS["X-Foo"] = some_matcher

However, when attempting to actually do that in a project that uses mypy one gets the following error:

Unsupported target for indexed assignment ("Mapping[str, Callable[[Optional[str], str], bool]]")

Thread hang with hypothesis>=6.0.4 on Apple M1 Pro, Mac OS 12.6.2, Python>=3.10

The testing library hypothesis made a threading change in release 6.0.4 to address a possible race condition.

On my Apple M1 Pro laptop, this works fine with pytest-httpserver for Python 3.7 - 3.9 (and presumably earlier but i have not checked).

On Python 3.10 and 3.11, all tests run just fine, but the process then hangs and will not exit without ctrl-C. The problem occurs with the latest version of hypothesis as well, but not with hypothesis 6.0.3.

The problem only occurs with pytest-httpserver installed (I tested the current 1.0.6 version, 1.0.0, and the last 0.x version, all of which had the problem). I will also file the bug with hypothesis in case there is an issue on their end.

I have included all environment details, code to reproduce, session logs, and relevant python -vvv output in this gist.

Simulate poorly preforming API

Testing failure mechanisms with external API's can be difficult when the pytest httpserver always responds back instantly. It would be nice to have the option to pass ether a set delay or range to each path. This would allow easier testing of fallback routines and backoff code.

Example
httpserver.expect_request("/poorly/performing/api", delay=1.8).respond_with_json(
{"status": "3"}, status=503
)

Try poetry

Look at poetry, how much it developed during the years and how it feels to abandon setup.py and its friends.

test_ssl fails after 2019-09-03

While working on reproducible builds for openSUSE, I found that
our python-pytest-httpserver package fails to build 2019-09-04 and later
because tests/assets/rootCA.crt expired.
additionally tests/assets/server.crt will expire 2020-01-16

Either those certs should be re-generated as part of the test or expiry should be > 2100

Extension for delayed matching and response

Hi,

I have a system with 2 independent interfaces: A <----> System under test (SUT) <----> B
A typical communication sequence looks like:
A sends request to SUT that
triggers that SUT sends request to B;
B responds that
triggers that SUT responds.

I would like to use behave to test this communication sequence.
Currently, using pytest_httpserver, this looks like:

Then SUT sends some request to B
When B responds something
When A sends some request to SUT
Then SUT responds  something

That looks a bit confusing, isn't it?

Would an extension of HTTPServer be possible (and worth to do) which:

  • accepts any incoming request;
  • matches the request only when calling, e.g. expect_request(...) -> RequestHandler;
  • responds only when calling, e.g. request_handler.respond_with_json({})

So, the dispatch would be delayed until the expect_request call and the response would be delayed until the respond_with_* call.

Usage would look like:

with BlockingHttpServer() as httpserver:
    endpoint = '/reserve-table'
    sut_response_handler = send_request_to_sut_async(endpoint)
    
    request_handler = httpserver.expect_request(endpoint)  # here we can get assertion error
    
    request_handler.respond_with_json({})
    
    sut_response_handler.expect_response('valagit')  # here we can get assertion error

add json to expect_request

right now data can only match exact text, which is less useful for testing apis that takes json payload.

expect_request should be able to take data or json (mutually exclusive) and handle the match correspondingly.

btw, thanks for the great project! Saves wring a bunch of boilerplates code!

Matching failure for expect_request

pytest-httpserver==1.0.4

I would like to use pytest_httpserver in behave by instantiating HTTPServer.
When I use the expect_request method, the matching does not work.
Curiously, it works for expect_oneshot_request.

Reproduction of issue:

from pytest_httpserver import HTTPServer
import requests


def test_wait():
    with HTTPServer() as httpserver:
        httpserver.expect_request('/x').respond_with_json({})
        with httpserver.wait(timeout=2) as waiting:
            requests.get(httpserver.url_for('/x'))
        assert waiting.result

Run:

pytest test.py

Output:

E               AssertionError: Wait timeout occurred, but some handlers left:
E               Ordered matchers:
E                   none
E               
E               Oneshot matchers:
E                   none
E               
E               Persistent matchers:
E                   <RequestMatcher uri='/x' method='__ALL' query_string=None headers={} data=None json=<UNDEFINED>>

.venv/lib/python3.8/site-packages/pytest_httpserver/httpserver.py:1121: AssertionError
-------------------------------------------------------------------------------- Captured log call --------------------------------------------------------------------------------
INFO     werkzeug:_internal.py:224 127.0.0.1 - - [08/Apr/2022 14:03:32] "GET /x HTTP/1.1" 200 -

Add a way to notify test code that all handers were executed

Now we have code like this:

httpserver.expect_oneshot_request(...).respond_with_response(Response(status=200))                                                                             
# make request from external software, which doesn't expose responses to requests
wait_for_truthy(lambda: not httpserver.oneshot_handlers, description='all requests processed')
httpserver.check_assertions()

where wait_for_truthy is a function that periodically calls its first argument until either the result becomes True or a timeout occurs.
This solution is not very beautiful and fast. Do you have ideas about how to do this properly?

1.0.4: sphinx fails because directories layout issue

Looks like sphinx is failing because directories layaout issue has been found.

+ /usr/bin/python3 -sBm build -w --no-isolation
* Getting dependencies for wheel...
* Building wheel...
Successfully built pytest_httpserver-1.0.4-py3-none-any.whl
+ '[' '!' -f setup.py ']'
+ DELETE_SETUP_PY=yes
+ echo 'from setuptools import setup; setup()'
+ PBR_VERSION=1.0.4
+ SETUPTOOLS_SCM_PRETEND_VERSION=1.0.4
+ /usr/bin/python3 setup.py build_sphinx -b man --build-dir build/sphinx
error: Multiple top-level packages discovered in a flat-layout: ['releasenotes', 'pytest_httpserver'].

To avoid accidental inclusion of unwanted files or directories,
setuptools will not proceed with this build.

If you are trying to create a single distribution with multiple packages
on purpose, you should not rely on automatic discovery.
Instead, consider the following options:

1. set up custom discovery (`find` directive with `include` or `exclude`)
2. use a `src-layout`
3. explicitly set `py_modules` or `packages` with a list of names

To find more information, look for "package discovery" on setuptools docs.

Unify expect_oneshot_request and expect_request

expect_oneshot_request and expect_request are responsible for similar actions, but expect_oneshot_request takes one optional keyword argument more (ordered). I propose to:

  1. Define enum HandlerType:
class HandlerType(Enum):
    PERMANENT = 'permanent'
    ONESHOT = 'oneshot'
    ORDERED = 'ordered'
  1. Make expect_request universal so that it could register permanent, oneshot, and ordered handlers by adding optional enum HandlerType argument.
  2. Remove ordered argument from expect_oneshot_request.
  3. Add convinience method expect_ordered_request.

Recommendations for running both http (non-s) and https test within the same session

When defining the following fixtures within a test module all tests that use httpserver will also use https, even if the test is intended to use http (non-S). What is the recommended way to both http (non-s) and https test within the same test session?

@pytest.fixture(scope="session")
def create_ca():
    return trustme.CA()


@pytest.fixture(scope="session")
def httpserver_ssl_context(create_ca: trustme.CA):
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    localhost_cert = create_ca.issue_cert("localhost")
    localhost_cert.configure_cert(context)
    return context

check_assertions usage

Hi there,

first off, thank you for the great project!

I have run into an issue multiple times and I am wondering if I am using the plugin wrong.

When writing tests or refactoring I ran into situations where the test fails because the server response with a 500 - which makes my test fail before I could call check_assetions. So nothing is logged or raised to provide any guidance what when wrong.

I solved it by having a patched version of the plugin which writes logs on any failed match.

How is it supposed to work?

howto response more than one cookie

i can response one cookie with

respond_with_data(headers={"Set-Cookie":"somecookie=somevalue;"})

but how can i make a response with more than one cookies?

Improve documentation

Improve documentation by adding description about the fixtures provided by the library.

Stackable expectations

In many cases, the parameters for expect_request() is very long, especially when multiple headers are specified or the request have a long data or json in its body.
In such case the expect_request() call can be very long. If it bothers the developer, it needs to be shortened, for example by moving the literals to variables and then specifying the variables for the expect_request() call.
In some cases, there are common expectations specified for each request, such as the content-type header is set to application/json.

It would be great to somehow bake a command, similar to the sh package's bake method.

So, it would look like:

server = httpserver.bake(headers={"content-type": "application/json"}) # and probably other common kwargs
server.expect_request("/foo", json={"foo": "bar"}).respond_with_json({"foo": "bar"})

Here, the bake is similar to the functools.partial function so it creates a new httpserver-like object whose defaults are changed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.