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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consumer Compatibility Suite (V1) #468

Merged
merged 14 commits into from Nov 15, 2023
Merged
2 changes: 2 additions & 0 deletions .cirrus.yml
Expand Up @@ -11,6 +11,7 @@ TEST_TEMPLATE: &TEST_TEMPLATE
linux_arm64_task:
env:
PATH: ${HOME}/.local/bin:${PATH}
CIRRUS_CLONE_SUBMODULES: "true"
matrix:
- IMAGE: "python:3.8-slim"
- IMAGE: "python:3.9-slim"
Expand All @@ -30,6 +31,7 @@ macosx_arm64_task:
image: ghcr.io/cirruslabs/macos-ventura-base:latest
env:
PATH: ${HOME}/.local/bin:${HOME}/.pyenv/shims:${PATH}
CIRRUS_CLONE_SUBMODULES: "true"
matrix:
- PYTHON: "3.8"
- PYTHON: "3.9"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -38,6 +38,8 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
@@ -0,0 +1,3 @@
[submodule "compatibility-suite"]
path = tests/v3/compatiblity_suite/definition
url = ../pact-compatibility-suite.git
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Expand Up @@ -99,7 +99,7 @@ You can also try using the new [github.dev](https://github.dev/pact-foundation/p
pipx install hatch
```

3. After cloning the repository, run `hatch shell` in the root of the repository. This will install all dependencies in a Python virtual environment and then ensure that the virtual environment is activated.
3. After cloning the repository, run `hatch shell` in the root of the repository. This will install all dependencies in a Python virtual environment and then ensure that the virtual environment is activated. You will also need to run `git submodule init` if you want to run tests, as Pact Python makes use of the Pact Compability Suite.

4. To run tests, run `hatch run test` to make sure the test suite is working. You should also make sure the example works by running `hatch run example`. For the examples, you will have to make sure that you have Docker (or a suitable alternative) installed and running.

Expand Down
79 changes: 41 additions & 38 deletions pact/v3/ffi.py
Expand Up @@ -82,10 +82,11 @@
from __future__ import annotations

import gc
import json
import typing
import warnings
from enum import Enum
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Any, List

from ._ffi import ffi, lib # type: ignore[import]

Expand Down Expand Up @@ -4557,11 +4558,7 @@ def create_mock_server_for_transport(
addr.encode("utf-8"),
port,
transport.encode("utf-8"),
(
transport_config.encode("utf-8")
if transport_config is not None
else ffi.NULL
),
(transport_config.encode("utf-8") if transport_config else ffi.NULL),
)
if ret > 0:
return PactServerHandle(ret)
Expand All @@ -4581,42 +4578,42 @@ def create_mock_server_for_transport(
raise RuntimeError(msg)


def mock_server_matched(mock_server_port: int) -> bool:
def mock_server_matched(mock_server_handle: PactServerHandle) -> bool:
"""
External interface to check if a mock server has matched all its requests.

The port number is passed in, and if all requests have been matched, true is
returned. False is returned if there is no mock server on the given port, or
If all requests have been matched, `true` is returned. `false` is returned
if any request has not been successfully matched, or the method panics.

[Rust
`pactffi_mock_server_matched`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/?search=pactffi_mock_server_matched)
"""
raise NotImplementedError
return lib.pactffi_mock_server_matched(mock_server_handle._ref)


def mock_server_mismatches(mock_server_port: int) -> str:
def mock_server_mismatches(
mock_server_handle: PactServerHandle,
) -> list[dict[str, Any]]:
"""
External interface to get all the mismatches from a mock server.

The port number of the mock server is passed in, and a pointer to a C string
with the mismatches in JSON format is returned.

[Rust
`pactffi_mock_server_mismatches`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/?search=pactffi_mock_server_mismatches)

**NOTE:** The JSON string for the result is allocated on the heap, and will
have to be freed once the code using the mock server is complete. The
[`cleanup_mock_server`](fn.cleanup_mock_server.html) function is provided
for this purpose.

# Errors

If there is no mock server with the provided port number, or the function
panics, a NULL pointer will be returned. Don't try to dereference it, it
will not end well for you.
Raises:
RuntimeError: If there is no mock server with the provided port number,
or the function panics.
"""
raise NotImplementedError
ptr = lib.pactffi_mock_server_mismatches(mock_server_handle._ref)
if ptr == ffi.NULL:
msg = f"No mock server found with port {mock_server_handle}."
raise RuntimeError(msg)
string = ffi.string(ptr)
if isinstance(string, bytes):
string = string.decode("utf-8")
return json.loads(string)


def cleanup_mock_server(mock_server_handle: PactServerHandle) -> None:
Expand Down Expand Up @@ -4673,7 +4670,7 @@ def write_pact_file(
"""
ret: int = lib.pactffi_write_pact_file(
mock_server_handle._ref,
directory,
str(directory).encode("utf-8"),
overwrite,
)
if ret == 0:
Expand All @@ -4698,21 +4695,27 @@ def write_pact_file(
raise RuntimeError(msg)


def mock_server_logs(mock_server_port: int) -> str:
def mock_server_logs(mock_server_handle: PactServerHandle) -> str:
"""
Fetch the logs for the mock server.

This needs the memory buffer log sink to be setup before the mock server is
started. Returned string will be freed with the `cleanup_mock_server`
function call.
started.

[Rust
`pactffi_mock_server_logs`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/?search=pactffi_mock_server_logs)

Will return a NULL pointer if the logs for the mock server can not be
retrieved.
Raises:
RuntimeError: If the logs for the mock server can not be retrieved.
"""
raise NotImplementedError
ptr = lib.pactffi_mock_server_logs(mock_server_handle._ref)
if ptr == ffi.NULL:
msg = f"Unable to obtain logs from {mock_server_handle!r}"
raise RuntimeError(msg)
string = ffi.string(ptr)
if isinstance(string, bytes):
string = string.decode("utf-8")
return string


def generate_datetime_string(format: str) -> StringResult:
Expand Down Expand Up @@ -5452,7 +5455,7 @@ def response_status(interaction: InteractionHandle, status: int) -> None:
def with_body(
interaction: InteractionHandle,
part: InteractionPart,
content_type: str,
content_type: str | None,
body: str | None,
) -> None:
"""
Expand Down Expand Up @@ -5488,8 +5491,8 @@ def with_body(
success: bool = lib.pactffi_with_body(
interaction._ref,
part.value,
content_type.encode("utf-8"),
body.encode("utf-8") if body is not None else None,
content_type.encode("utf-8") if content_type else ffi.NULL,
body.encode("utf-8") if body else None,
)
if not success:
msg = f"Unable to set body for {interaction}."
Expand All @@ -5499,7 +5502,7 @@ def with_body(
def with_binary_file(
interaction: InteractionHandle,
part: InteractionPart,
content_type: str,
content_type: str | None,
body: bytes | None,
) -> None:
"""
Expand Down Expand Up @@ -5542,7 +5545,7 @@ def with_binary_file(
success: bool = lib.pactffi_with_binary_file(
interaction._ref,
part.value,
content_type.encode("utf-8"),
content_type.encode("utf-8") if content_type else ffi.NULL,
body if body else ffi.NULL,
len(body) if body else 0,
)
Expand All @@ -5554,7 +5557,7 @@ def with_binary_file(
def with_multipart_file_v2( # noqa: PLR0913
interaction: InteractionHandle,
part: InteractionPart,
content_type: str,
content_type: str | None,
file: Path | None,
part_name: str,
boundary: str | None,
Expand Down Expand Up @@ -5597,7 +5600,7 @@ def with_multipart_file_v2( # noqa: PLR0913
lib.pactffi_with_multipart_file_v2(
interaction._ref,
part.value,
content_type.encode("utf-8"),
content_type.encode("utf-8") if content_type else ffi.NULL,
str(file).encode("utf-8") if file else ffi.NULL,
part_name.encode("utf-8"),
boundary.encode("utf-8") if boundary else ffi.NULL,
Expand Down Expand Up @@ -6635,7 +6638,7 @@ def using_plugin(
ret: int = lib.pactffi_using_plugin(
pact._ref,
plugin_name.encode("utf-8"),
plugin_version.encode("utf-8") if plugin_version is not None else ffi.NULL,
plugin_version.encode("utf-8") if plugin_version else ffi.NULL,
)
if ret == 0:
return
Expand Down