Skip to content

Commit

Permalink
Merge pull request #2 from pact-foundation/master
Browse files Browse the repository at this point in the history
merge upstream
  • Loading branch information
dhoomakethu authored Oct 31, 2017
2 parents b91f6c3 + 1bcb7d5 commit c1a5402
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 36 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
### 0.9.0
* 735aa87 - Set new project minimum requirements (Matthew Balvanz, Sun Oct 22 16:30:12 2017 -0500)
* 295f17c - Merge pull request #60 from ftobia/requirements (Matthew Balvanz, Sun Oct 22 16:09:59 2017 -0500)
* 1dc72da - Merge pull request #48 from bassdread/allow-later-versions-of-requests (Matthew Balvanz, Sun Oct 22 16:09:39 2017 -0500)
* 3265b45 - add suggestion (Chris Hannam, Fri Oct 20 09:33:05 2017 +0100)
* 33504a6 - Resolve #51 verify outputs text instead of bytes (Matthew Balvanz, Thu Oct 19 21:28:39 2017 -0500)
* 51dcda3 - Merge pull request #57 from jceplaras/fix-e2e-test-incorrect-number-of-arg (Matthew Balvanz, Thu Oct 19 20:57:49 2017 -0500)
* 1a4d136 - Relax version requirements in setup.py (vs requirements.txt) (ftobia, Fri Oct 13 19:42:46 2017 -0400)
* 8ece1d6 - Fix incorrect indent on test_incorrect_number_of_arguments on test_e2e (James Plaras, Fri Oct 13 12:54:56 2017 +0800)
* 5f8257b - Resolve #50: Note which version of the Pact specification is supported (Matthew Balvanz, Sat Oct 7 14:05:26 2017 -0500)
* e728301 - Resolve #45: Document request query parameter (Matthew Balvanz, Sat Oct 7 13:58:07 2017 -0500)
* 5de7200 - Merge pull request #49 from pact-foundation/rename-somethinglike (Matt Fellows, Wed Oct 4 22:36:21 2017 +1100)
* d73aa1c - Resolve #43: Rename SomethingLike to Like (Matthew Balvanz, Mon Sep 4 15:49:13 2017 -0500)
* a07c8b6 - Merge pull request #46 from bassdread/fix-setup-url-name (Matthew Balvanz, Mon Sep 4 15:44:45 2017 -0500)
* b5e1f95 - allow later versions of requests (Chris Hannam, Tue Aug 29 13:38:42 2017 +0100)
* 08fe123 - make setup-url name format match above reference (Chris Hannam, Fri Aug 25 11:03:35 2017 +0100)

### 0.8.0
* edb6c72 - Merge pull request #41 from pact-foundation/fix-running-on-windows (Matthew Balvanz, Thu Aug 10 21:39:27 2017 -0500)
* 244fff1 - Merge pull request #42 from pact-foundation/deprecate-provider-states-url (Matthew Balvanz, Thu Aug 10 21:38:44 2017 -0500)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ clean:

.PHONY: deps
deps:
pip install -r requirements_dev.txt
pip install -r requirements_dev.txt -e .


define E2E
Expand Down
56 changes: 44 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Python version of Pact. Enables consumer driven contract testing,
providing a mock service and DSL for the consumer project, and
interaction playback and verification for the service provider project.
Currently supports version 2 of the [Pact specification].

For more information about what Pact is, and how it can help you
test your code more efficiently, check out the [Pact documentation].
Expand Down Expand Up @@ -102,7 +103,36 @@ configured and the interactions verified, use the `setup` and `verify` methods,
result = user('UserA')
# Some additional steps before verifying all interactions have occurred
pact.verify()
````
```

### Requests

When defining the expected HTTP request that your code is expected to make you
can specify the method, path, body, headers, and query:

```python
pact.with_request(
method='GET',
path='/api/v1/my-resources/',
query={'search': 'example'}
)
```

`query` is used to specify URL query parameters, so the above example expects
a request made to `/api/v1/my-resources/?search=example`.

```python
pact.with_request(
method='POST',
path='/api/v1/my-resources/123',
body={'user_ids': [1, 2, 3]},
headers={'Content-Type': 'application/json'},
)
```

You can define exact values for your expected request like the examples above,
or you can use the matchers defined later to assist in handling values that are
variable.

The default hostname and port for the Pact mock service will be
`localhost:1234` but you can adjust this during Pact creation:
Expand Down Expand Up @@ -156,24 +186,25 @@ as `generate`, in this case `2016-12-15T20:16:01`. When the contract is verified
provider, the regex will be used to search the response from the real provider service
and the test will be considered successful if the regex finds a match in the response.

### SomethingLike(matcher)
### Like(matcher)
Asserts the element's type matches the matcher. For example:

```python
from pact import SomethingLike
SomethingLike(123) # Matches if the value is an integer
SomethingLike('hello world') # Matches if the value is a string
SomethingLike(3.14) # Matches if the value is a float
from pact import Like
Like(123) # Matches if the value is an integer
Like('hello world') # Matches if the value is a string
Like(3.14) # Matches if the value is a float
```
The argument supplied to `SomethingLike` will be what the mock service responds with.
The argument supplied to `Like` will be what the mock service responds with.

When a dictionary is used as an argument for SomethingLike, all the child objects (and their child objects etc.) will be matched according to their types, unless you use a more specific matcher like a Term.
When a dictionary is used as an argument for Like, all the child objects (and their child objects etc.) will be matched according to their types, unless you use a more specific matcher like a Term.

```python
SomethingLike({
from pact import Like, Term
Like({
'username': Term('[a-zA-Z]+', 'username'),
'id': 123, # integer
'confirmed': false, # boolean
'confirmed': False, # boolean
'address': { # dictionary
'street': '200 Bourke St' # string
}
Expand All @@ -194,7 +225,7 @@ EachLike('hello') # All items are strings
Or other matchers can be nested inside to assert more complex objects:

```python
from pact import EachLike, SomethingLike, Term
from pact import EachLike, Term
EachLike({
'username': Term('[a-zA-Z]+', 'username'),
'id': 123,
Expand Down Expand Up @@ -278,7 +309,7 @@ states to communicate from the consumer what data should exist on the provider.

When setting up the testing of a provider you will also need to setup the management of
these provider states. The Pact verifier does this by making additional HTTP requests to
the `provider_states_setup_url` you provide. This URL could be
the `--provider-states-setup-url` you provide. This URL could be
on the provider application or a separate one. Some strategies for managing state include:

- Having endpoints in your application that are not active in production that create and delete your datastore state
Expand Down Expand Up @@ -321,6 +352,7 @@ End to end: `make e2e`
[Pact Broker]: https://docs.pact.io/documentation/sharings_pacts.html
[Pact documentation]: https://docs.pact.io/
[Pact Mock Service]: https://github.com/bethesque/pact-mock_service
[Pact specification]: https://github.com/pact-foundation/pact-specification
[Provider States]: https://docs.pact.io/documentation/provider_states.html
[pact-provider-verifier]: https://github.com/pact-foundation/pact-provider-verifier
[pyenv]: https://github.com/pyenv/pyenv
Expand Down
6 changes: 4 additions & 2 deletions e2e/contracts/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,7 @@ def two(a, b):
with pact:
two('one')

self.assertEqual(
e.exception.message, 'two() takes exactly 2 arguments (1 given)')
self.assertEqual(
e.exception.message,
'two() takes exactly 2 arguments (1 given)'
)
5 changes: 3 additions & 2 deletions pact/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Python methods for interactive with a Pact Mock Service."""
from .consumer import Consumer
from .matchers import EachLike, SomethingLike, Term
from .matchers import EachLike, Like, SomethingLike, Term
from .pact import Pact
from .provider import Provider
from .__version__ import __version__ # noqa: F401

__all__ = ('Consumer', 'EachLike', 'Pact', 'Provider', 'SomethingLike', 'Term')
__all__ = ('Consumer', 'EachLike', 'Like', 'Pact', 'Provider', 'SomethingLike',
'Term')
2 changes: 1 addition & 1 deletion pact/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Pact version info."""

__version__ = '0.8.0'
__version__ = '0.9.0'
8 changes: 6 additions & 2 deletions pact/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def generate(self):
'min': self.minimum}


class SomethingLike(Matcher):
class Like(Matcher):
"""
Expect the type of the value to be the same as matcher.
Expand All @@ -79,7 +79,7 @@ class SomethingLike(Matcher):
... .upon_receiving('a request for a random number')
... .with_request('get', '/generate-number')
... .will_respond_with(200, body={
... 'number': SomethingLike(1111222233334444)
... 'number': Like(1111222233334444)
... }))
Would expect the response body to be a JSON object, containing the key
Expand Down Expand Up @@ -120,6 +120,10 @@ def generate(self):
'contents': from_term(self.matcher)}


# Remove SomethingLike in major version 1.0.0
SomethingLike = Like


class Term(Matcher):
"""
Expect the response to match a specified regular expression.
Expand Down
8 changes: 4 additions & 4 deletions pact/pact.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ def setup(self):
resp = requests.delete(
self.uri + '/interactions', headers=self.HEADERS)

assert resp.status_code == 200, resp.content
assert resp.status_code == 200, resp.text
resp = requests.put(
self.uri + '/interactions',
headers=self.HEADERS,
json={"interactions": self._interactions})

assert resp.status_code == 200, resp.content
assert resp.status_code == 200, resp.text
except AssertionError:
raise

Expand Down Expand Up @@ -191,15 +191,15 @@ def verify(self):
resp = requests.get(
self.uri + '/interactions/verification',
headers=self.HEADERS)
assert resp.status_code == 200, resp.content
assert resp.status_code == 200, resp.text
payload = {
'consumer': {'name': self.consumer.name},
'provider': {'name': self.provider.name},
'pact_dir': self.pact_dir
}
resp = requests.post(
self.uri + '/pact', headers=self.HEADERS, json=payload)
assert resp.status_code == 200, resp.content
assert resp.status_code == 200, resp.text

def with_request(self, method, path, body=None, headers=None, query=None):
"""
Expand Down
7 changes: 6 additions & 1 deletion pact/test/test_matchers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from unittest import TestCase

from ..matchers import EachLike, Matcher, SomethingLike, Term, from_term
from ..matchers import EachLike, Like, Matcher, SomethingLike, Term, from_term


class MatcherTestCase(TestCase):
Expand Down Expand Up @@ -56,6 +56,11 @@ def test_nested_matchers(self):
'min': 1})


class LikeTestCase(TestCase):
def test_is_something_like(self):
self.assertIs(SomethingLike, Like)


class SomethingLikeTestCase(TestCase):
def test_valid_types(self):
types = [None, list(), dict(), 1, 1.0, 'string', u'unicode', Matcher()]
Expand Down
8 changes: 4 additions & 4 deletions pact/test/test_pact.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def setUp(self):

def test_error_deleting_interactions(self):
self.mock_requests.side_effect = iter([
Mock(status_code=500, content='deletion error')])
Mock(status_code=500, text='deletion error')])

with self.assertRaises(AssertionError) as e:
self.target.setup()
Expand All @@ -185,7 +185,7 @@ def test_error_deleting_interactions(self):
def test_error_posting_interactions(self):
self.mock_requests.side_effect = iter([
Mock(status_code=200),
Mock(status_code=500, content='post interactions error')])
Mock(status_code=500, text='post interactions error')])

with self.assertRaises(AssertionError) as e:
self.target.setup()
Expand Down Expand Up @@ -387,7 +387,7 @@ def test_success(self):

def test_error_verifying_interactions(self):
self.mock_requests.side_effect = iter([
Mock(status_code=500, content='verification error')])
Mock(status_code=500, text='verification error')])

with self.assertRaises(AssertionError) as e:
self.target.verify()
Expand All @@ -400,7 +400,7 @@ def test_error_verifying_interactions(self):
def test_error_writing_pacts_to_file(self):
self.mock_requests.side_effect = iter([
Mock(status_code=200),
Mock(status_code=500, content='error writing pact to file')])
Mock(status_code=500, text='error writing pact to file')])

with self.assertRaises(AssertionError) as e:
self.target.verify()
Expand Down
4 changes: 0 additions & 4 deletions requirements.txt

This file was deleted.

2 changes: 0 additions & 2 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ pyflakes==1.3.0
tox==2.5.0
tox-travis==0.8
wheel==0.24.0

-r requirements.txt
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ def read(filename):
return f.read().decode('utf-8')


dependencies = read('requirements.txt').split()
dependencies = [
'click>=2.0.0',
'psutil>=2.0.0',
'requests>=2.5.0',
'six>=1.9.0',
]

if sys.version_info.major == 2:
dependencies.append('subprocess32')
Expand Down

0 comments on commit c1a5402

Please sign in to comment.