Skip to content

Commit 1bb580e

Browse files
committed
feat(v3): implement interaction methods
This commit adds implementation for all FFI functions which act on an `InteractionHandle`. Most of these map in Python to a method of the `Interaction` class. As the InteractionHandle can point to a HTTP, Sync Message or Async Message Interaction, the initial `Interaction` class has been converted into an abstract class, and then subclassed into three new concrete classes. This will help end-users specifically when it comes to auto-completions within the IDE. Signed-off-by: JP-Ellis <josh@jpellis.me>
1 parent 95d183b commit 1bb580e

9 files changed

+1606
-622
lines changed

pact/v3/ffi.py

+382-146
Large diffs are not rendered by default.

pact/v3/pact.py

+524-87
Large diffs are not rendered by default.

tests/conftest.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
Common fixtures for tests.
3+
"""
4+
import json
5+
import shutil
6+
import tempfile
7+
from pathlib import Path
8+
from typing import Any, Generator
9+
10+
import pytest
11+
12+
13+
@pytest.fixture()
14+
def temp_dir() -> Generator[Path, Any, None]:
15+
"""
16+
Create a temporary directory.
17+
18+
This fixture automatically handles cleanup of the temporary directory once
19+
the test has finished.
20+
21+
The directory is populated with a few minimal files:
22+
23+
- `test.py`: A minimal hello-world Python script.
24+
- `test.txt`: A minimal text file.
25+
- `test.json`: A minimal JSON file.
26+
- `test.png`: A minimal PNG image.
27+
"""
28+
temp_dir = Path(tempfile.mkdtemp())
29+
with (temp_dir / "test.py").open("w") as f:
30+
f.write('print("Hello, world!")')
31+
with (temp_dir / "test.txt").open("w") as f:
32+
f.write("Hello, world!")
33+
with (temp_dir / "test.json").open("w") as f:
34+
json.dump({"hello": "world"}, f)
35+
with (temp_dir / "test.png").open("wb") as f:
36+
f.write(
37+
b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
38+
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4"
39+
b"\x89\x00\x00\x00\x0a\x49\x44\x41\x54\x78\x9c\x63\x00\x01\x00\x00"
40+
b"\x05\x00\x01\x0d\x0a\x2d\xb4\x00\x00\x00\x00\x49\x45\x4e\x44\xae"
41+
b"\x42\x60\x82",
42+
)
43+
44+
yield temp_dir
45+
shutil.rmtree(temp_dir)

tests/v3/conftest.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
directory.
66
"""
77

8+
89
import pytest
910

1011

@@ -15,4 +16,4 @@ def _setup_pact_logging() -> None:
1516
"""
1617
from pact.v3 import ffi
1718

18-
ffi.log_to_stderr(ffi.LevelFilter.DEBUG)
19+
ffi.log_to_stderr("DEBUG")

tests/v3/test_async_interaction.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Pact Async Message Interaction unit tests.
3+
"""
4+
5+
from __future__ import annotations
6+
7+
import re
8+
9+
import pytest
10+
from pact.v3 import Pact
11+
12+
13+
@pytest.fixture()
14+
def pact() -> Pact:
15+
"""
16+
Fixture for a Pact instance.
17+
"""
18+
return Pact("consumer", "provider")
19+
20+
21+
def test_str(pact: Pact) -> None:
22+
interaction = pact.upon_receiving("a basic request", "Async")
23+
assert str(interaction) == "AsyncMessageInteraction(a basic request)"
24+
25+
26+
def test_repr(pact: Pact) -> None:
27+
interaction = pact.upon_receiving("a basic request", "Async")
28+
assert (
29+
re.match(
30+
r"^AsyncMessageInteraction\(InteractionHandle\(\d+\)\)$",
31+
repr(interaction),
32+
)
33+
is not None
34+
)

tests/v3/test_ffi.py

+38
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,49 @@
55
They are not intended to test the Pact API itself, as that is handled by the
66
client library.
77
"""
8+
import re
89

10+
import pytest
911
from pact.v3 import ffi
1012

1113

1214
def test_version() -> None:
1315
assert isinstance(ffi.version(), str)
1416
assert len(ffi.version()) > 0
1517
assert ffi.version().count(".") == 2
18+
19+
20+
def test_string_result_ok() -> None:
21+
result = ffi.StringResult(ffi.lib.pactffi_generate_datetime_string(b"yyyy"))
22+
assert result.is_ok
23+
assert not result.is_failed
24+
assert re.match(r"^\d{4}$", result.text)
25+
assert str(result) == result.text
26+
assert repr(result) == f"<StringResult: OK, {result.text!r}>"
27+
result.raise_exception()
28+
29+
30+
def test_string_result_failed() -> None:
31+
result = ffi.StringResult(ffi.lib.pactffi_generate_datetime_string(b"t"))
32+
assert not result.is_ok
33+
assert result.is_failed
34+
assert result.text.startswith("Error parsing")
35+
with pytest.raises(RuntimeError):
36+
result.raise_exception()
37+
38+
39+
def test_datetime_valid() -> None:
40+
ffi.validate_datetime("2023-01-01", "yyyy-MM-dd")
41+
42+
43+
def test_datetime_invalid() -> None:
44+
with pytest.raises(ValueError, match=r"Invalid datetime value.*"):
45+
ffi.validate_datetime("01/01/2023", "yyyy-MM-dd")
46+
47+
48+
def test_get_error_message() -> None:
49+
# The first bit makes sure that an error is generated.
50+
invalid_utf8 = b"\xc3\x28"
51+
ret: int = ffi.lib.pactffi_validate_datetime(invalid_utf8, invalid_utf8)
52+
assert ret == 2
53+
assert ffi.get_error_message() == "error parsing value as UTF-8"

0 commit comments

Comments
 (0)