|
| 1 | +"""Matching HTTP parts (request or response) feature tests.""" |
| 2 | + |
| 3 | +import pickle |
| 4 | +import re |
| 5 | +import sys |
| 6 | +from pathlib import Path |
| 7 | +from typing import Generator |
| 8 | + |
| 9 | +import pytest |
| 10 | +from pytest_bdd import ( |
| 11 | + given, |
| 12 | + parsers, |
| 13 | + scenario, |
| 14 | + then, |
| 15 | + when, |
| 16 | +) |
| 17 | +from yarl import URL |
| 18 | + |
| 19 | +from pact.v3 import Pact |
| 20 | +from pact.v3.verifier import Verifier |
| 21 | +from tests.v3.compatibility_suite.util.interaction_definition import ( |
| 22 | + InteractionDefinition, |
| 23 | +) |
| 24 | +from tests.v3.compatibility_suite.util.provider import start_provider |
| 25 | + |
| 26 | +################################################################################ |
| 27 | +## Scenarios |
| 28 | +################################################################################ |
| 29 | + |
| 30 | + |
| 31 | +@pytest.mark.skipif( |
| 32 | + sys.platform.startswith("win"), |
| 33 | + reason="See pact-foundation/pact-python#639", |
| 34 | +) |
| 35 | +@scenario( |
| 36 | + "definition/features/V3/http_matching.feature", |
| 37 | + "Comparing accept headers where the actual has additional parameters", |
| 38 | +) |
| 39 | +def test_comparing_accept_headers_where_the_actual_has_additional_parameters() -> None: |
| 40 | + """Comparing accept headers where the actual has additional parameters.""" |
| 41 | + |
| 42 | + |
| 43 | +@pytest.mark.skipif( |
| 44 | + sys.platform.startswith("win"), |
| 45 | + reason="See pact-foundation/pact-python#639", |
| 46 | +) |
| 47 | +@scenario( |
| 48 | + "definition/features/V3/http_matching.feature", |
| 49 | + "Comparing accept headers where the actual has is missing a value", |
| 50 | +) |
| 51 | +def test_comparing_accept_headers_where_the_actual_has_is_missing_a_value() -> None: |
| 52 | + """Comparing accept headers where the actual has is missing a value.""" |
| 53 | + |
| 54 | + |
| 55 | +@pytest.mark.skipif( |
| 56 | + sys.platform.startswith("win"), |
| 57 | + reason="See pact-foundation/pact-python#639", |
| 58 | +) |
| 59 | +@scenario( |
| 60 | + "definition/features/V3/http_matching.feature", |
| 61 | + "Comparing content type headers where the actual has a charset", |
| 62 | +) |
| 63 | +def test_comparing_content_type_headers_where_the_actual_has_a_charset() -> None: |
| 64 | + """Comparing content type headers where the actual has a charset.""" |
| 65 | + |
| 66 | + |
| 67 | +@pytest.mark.skipif( |
| 68 | + sys.platform.startswith("win"), |
| 69 | + reason="See pact-foundation/pact-python#639", |
| 70 | +) |
| 71 | +@scenario( |
| 72 | + "definition/features/V3/http_matching.feature", |
| 73 | + "Comparing content type headers where the actual has a different charset", |
| 74 | +) |
| 75 | +def test_comparing_content_type_headers_where_the_actual_has_a_different_charset() -> ( |
| 76 | + None |
| 77 | +): |
| 78 | + """Comparing content type headers where the actual has a different charset.""" |
| 79 | + |
| 80 | + |
| 81 | +@pytest.mark.skipif( |
| 82 | + sys.platform.startswith("win"), |
| 83 | + reason="See pact-foundation/pact-python#639", |
| 84 | +) |
| 85 | +@scenario( |
| 86 | + "definition/features/V3/http_matching.feature", |
| 87 | + "Comparing content type headers where the actual is missing a charset", |
| 88 | +) |
| 89 | +def test_comparing_content_type_headers_where_the_actual_is_missing_a_charset() -> None: |
| 90 | + """Comparing content type headers where the actual is missing a charset.""" |
| 91 | + |
| 92 | + |
| 93 | +@pytest.mark.skipif( |
| 94 | + sys.platform.startswith("win"), |
| 95 | + reason="See pact-foundation/pact-python#639", |
| 96 | +) |
| 97 | +@scenario( |
| 98 | + "definition/features/V3/http_matching.feature", |
| 99 | + "Comparing content type headers where they have the same charset", |
| 100 | +) |
| 101 | +def test_comparing_content_type_headers_where_they_have_the_same_charset() -> None: |
| 102 | + """Comparing content type headers where they have the same charset.""" |
| 103 | + |
| 104 | + |
| 105 | +@pytest.mark.skipif( |
| 106 | + sys.platform.startswith("win"), |
| 107 | + reason="See pact-foundation/pact-python#639", |
| 108 | +) |
| 109 | +@scenario( |
| 110 | + "definition/features/V3/http_matching.feature", |
| 111 | + "Comparing content type headers which are equal", |
| 112 | +) |
| 113 | +def test_comparing_content_type_headers_which_are_equal() -> None: |
| 114 | + """Comparing content type headers which are equal.""" |
| 115 | + |
| 116 | + |
| 117 | +################################################################################ |
| 118 | +## Given |
| 119 | +################################################################################ |
| 120 | + |
| 121 | + |
| 122 | +@given( |
| 123 | + parsers.re( |
| 124 | + r'a request is received with an? "(?P<name>[^"]+)" header of "(?P<value>[^"]+)"' |
| 125 | + ) |
| 126 | +) |
| 127 | +def a_request_is_received_with_header(name: str, value: str, temp_dir: Path) -> None: |
| 128 | + """A request is received with a "content-type" header of "application/json".""" |
| 129 | + interaction_definition = InteractionDefinition(method="GET", path="/", type="HTTP") |
| 130 | + interaction_definition.response_headers.update({name: value}) |
| 131 | + with (temp_dir / "interactions.pkl").open("wb") as pkl_file: |
| 132 | + pickle.dump([interaction_definition], pkl_file) |
| 133 | + |
| 134 | + |
| 135 | +@given( |
| 136 | + parsers.re( |
| 137 | + r'an expected request with an? "(?P<name>[^"]+)" header of "(?P<value>[^"]+)"' |
| 138 | + ), |
| 139 | +) |
| 140 | +def an_expected_request_with_header(name: str, value: str, temp_dir: Path) -> None: |
| 141 | + """An expected request with a "content-type" header of "application/json".""" |
| 142 | + pact = Pact("consumer", "provider") |
| 143 | + pact.with_specification("V3") |
| 144 | + interaction_definition = InteractionDefinition(method="GET", path="/", type="HTTP") |
| 145 | + interaction_definition.response_headers.update({name: value}) |
| 146 | + interaction_definition.add_to_pact(pact, name) |
| 147 | + (temp_dir / "pacts").mkdir(exist_ok=True, parents=True) |
| 148 | + pact.write_file(temp_dir / "pacts") |
| 149 | + |
| 150 | + |
| 151 | +################################################################################ |
| 152 | +## When |
| 153 | +################################################################################ |
| 154 | + |
| 155 | + |
| 156 | +@when("the request is compared to the expected one", target_fixture="provider_url") |
| 157 | +def the_request_is_compared_to_the_expected_one( |
| 158 | + temp_dir: Path, |
| 159 | +) -> Generator[URL, None, None]: |
| 160 | + """The request is compared to the expected one.""" |
| 161 | + yield from start_provider(temp_dir) |
| 162 | + |
| 163 | + |
| 164 | +################################################################################ |
| 165 | +## Then |
| 166 | +################################################################################ |
| 167 | + |
| 168 | + |
| 169 | +@then( |
| 170 | + parsers.re("the comparison should(?P<negated>( NOT)?) be OK"), |
| 171 | + converters={"negated": lambda x: x == " NOT"}, |
| 172 | + target_fixture="verifier_result", |
| 173 | +) |
| 174 | +def the_comparison_should_not_be_ok( |
| 175 | + provider_url: URL, |
| 176 | + verifier: Verifier, |
| 177 | + temp_dir: Path, |
| 178 | + negated: bool, # noqa: FBT001 |
| 179 | +) -> Verifier: |
| 180 | + """The comparison should NOT be OK.""" |
| 181 | + verifier.set_info("provider", url=provider_url) |
| 182 | + verifier.add_transport( |
| 183 | + protocol="http", |
| 184 | + port=provider_url.port, |
| 185 | + path="/", |
| 186 | + ) |
| 187 | + verifier.add_source(temp_dir / "pacts") |
| 188 | + if negated: |
| 189 | + with pytest.raises(RuntimeError): |
| 190 | + verifier.verify() |
| 191 | + else: |
| 192 | + verifier.verify() |
| 193 | + return verifier |
| 194 | + |
| 195 | + |
| 196 | +@then( |
| 197 | + parsers.parse( |
| 198 | + 'the mismatches will contain a mismatch with error "{mismatch_key}" ' |
| 199 | + "-> \"Expected header '{header_name}' to have value '{expected_value}' " |
| 200 | + "but was '{actual_value}'\"" |
| 201 | + ) |
| 202 | +) |
| 203 | +def the_mismatches_will_contain_a_mismatch_with_error( |
| 204 | + verifier_result: Verifier, |
| 205 | + mismatch_key: str, |
| 206 | + header_name: str, |
| 207 | + expected_value: str, |
| 208 | + actual_value: str, |
| 209 | +) -> None: |
| 210 | + """Mismatches will contain a mismatch with error.""" |
| 211 | + expected_value_matcher = re.compile(expected_value) |
| 212 | + actual_value_matcher = re.compile(actual_value) |
| 213 | + expected_error_matcher = re.compile( |
| 214 | + rf"Mismatch with header \'{mismatch_key}\': Expected header \'{header_name}\' " |
| 215 | + rf"to have value \'{expected_value}\' but was \'{actual_value}\'" |
| 216 | + ) |
| 217 | + mismatch = verifier_result.results["errors"][0]["mismatch"]["mismatches"][0] |
| 218 | + assert mismatch["key"] == mismatch_key |
| 219 | + assert mismatch["type"] == "HeaderMismatch" |
| 220 | + assert expected_value_matcher.match(mismatch["expected"]) is not None |
| 221 | + assert actual_value_matcher.match(mismatch["actual"]) is not None |
| 222 | + assert expected_error_matcher.match(mismatch["mismatch"]) is not None |
0 commit comments