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