Skip to content

Commit

Permalink
fix: Improperly serialized headers in `SerializedHistoryEntry.case.ex…
Browse files Browse the repository at this point in the history
…tra_headers`.
  • Loading branch information
Stranger6667 committed Oct 16, 2023
1 parent 45a254f commit aab02d6
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ After:
- Confusing error message when trying to load schema from a non-existing file. `#1602`_
- Reflect disabled TLS verification in generated code samples. `#1054`_
- Generated cURL commands now include the ``Content-Type`` header, which was previously omitted. `#1783`_
- Improperly serialized headers in ``SerializedHistoryEntry.case.extra_headers``.

**Performance**

Expand Down
41 changes: 26 additions & 15 deletions src/schemathesis/runner/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any, Dict, List, Optional, Set, Tuple, Union

import requests
from requests.structures import CaseInsensitiveDict

from ..code_samples import EXCLUDED_HEADERS
from ..exceptions import FailureContext, InternalError, make_unique_by_key
Expand Down Expand Up @@ -101,21 +102,8 @@ def from_check(cls, check: Check) -> "SerializedCheck":
response = Response.from_wsgi(check.response, check.elapsed)
else:
response = None
headers = {key: value[0] for key, value in request.headers.items() if key not in EXCLUDED_HEADERS}
history = []
case = check.example
while case.source is not None:
if isinstance(case.source.response, requests.Response):
history_response = Response.from_requests(case.source.response)
verify = history_response.verify
else:
history_response = Response.from_wsgi(case.source.response, case.source.elapsed)
verify = True
entry = SerializedHistoryEntry(
case=SerializedCase.from_case(case.source.case, headers, verify=verify), response=history_response
)
history.append(entry)
case = case.source.case
headers = _get_headers(request.headers)
history = get_serialized_history(check.example)
return cls(
name=check.name,
value=check.value,
Expand All @@ -130,12 +118,35 @@ def from_check(cls, check: Check) -> "SerializedCheck":
)


def _get_headers(headers: Union[Dict[str, Any], CaseInsensitiveDict]) -> Dict[str, str]:
return {key: value[0] for key, value in headers.items() if key not in EXCLUDED_HEADERS}


@dataclass
class SerializedHistoryEntry:
case: SerializedCase
response: Response


def get_serialized_history(case: Case) -> List[SerializedHistoryEntry]:
history = []
while case.source is not None:
history_request = case.source.response.request
headers = _get_headers(history_request.headers)
if isinstance(case.source.response, requests.Response):
history_response = Response.from_requests(case.source.response)
verify = history_response.verify
else:
history_response = Response.from_wsgi(case.source.response, case.source.elapsed)
verify = True
entry = SerializedHistoryEntry(
case=SerializedCase.from_case(case.source.case, headers, verify=verify), response=history_response
)
history.append(entry)
case = case.source.case
return history


@dataclass
class SerializedError:
exception: str
Expand Down
48 changes: 48 additions & 0 deletions test/runner/test_events.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import io
from datetime import timedelta

import pytest
import requests
from urllib3 import HTTPResponse

from schemathesis.models import Case, CaseSource, Check, Status
from schemathesis.runner import events
from schemathesis.runner.serialization import SerializedCheck
from schemathesis.utils import WSGIResponse


def test_unknown_exception():
Expand All @@ -8,3 +18,41 @@ def test_unknown_exception():
event = events.InternalError.from_exc(exc)
assert event.message == "An internal error occurred during the test run"
assert event.exception.strip() == "ZeroDivisionError: division by zero"


@pytest.fixture
def case_factory(swagger_20):
def factory():
return Case(operation=swagger_20["/users"]["GET"])

return factory


@pytest.fixture(params=[requests.Response, WSGIResponse])
def response_factory(request, mocker):
def factory(headers):
response = mocker.create_autospec(request.param)
response.status_code = 500
response.reason = "Internal Server Error"
response.encoding = "utf-8"
response.elapsed = timedelta(1.0)
response.headers = {}
response.response = []
response.raw = HTTPResponse(body=io.BytesIO(b""), status=500, headers={})
response.request = requests.PreparedRequest()
response.request.prepare(method="POST", url="http://127.0.0.1", headers=headers)
return response

return factory


def test_serialize_history(case_factory, response_factory):
root_case = case_factory()
value = "A"
root_case.source = CaseSource(case=case_factory(), response=response_factory({"X-Example": value}), elapsed=1.0)
check = Check(
name="test", value=Status.failure, response=response_factory({"X-Example": "B"}), elapsed=1.0, example=root_case
)
serialized = SerializedCheck.from_check(check)
assert len(serialized.history) == 1
assert serialized.history[0].case.extra_headers["X-Example"] == value

0 comments on commit aab02d6

Please sign in to comment.