diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..1006d85
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+omit = pact_test/utils/logger.py
+
diff --git a/.gitignore b/.gitignore
index d254fc4..31d93a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,12 +2,19 @@
.cache/
.idea/
build/
-pypact.egg-info/
+pact_test.egg-info/
tests/__pycache__/
.eggs/
.coverage
*.log
*.egg
*.iml
+pact_test.egg-info/
+pact_test/__pycache__
+__pycache__
+.DS_Store
pytest_pact.egg-info/
-pytest_pact/__pycache__
+dist/
+.tox
+.idea/
+*.iml
diff --git a/.travis.yml b/.travis.yml
index 5bfd00c..568ca67 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,17 @@
language: python
+
+sudo: false
+
python:
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
+
install:
- - "pip install -r requirements.txt"
- - "pip install pytest pytest-cov pytest-sugar"
- - "pip install coveralls"
- - "pip install -e ."
-script:
- - py.test --cov pytest_pact --cov-report term-missing
-after_success:
- - coveralls
+ - pip install -e .
+ - pip install -r requirements.txt
+
+script: pytest
+
diff --git a/README.md b/README.md
deleted file mode 100644
index d755fb3..0000000
--- a/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-[![Build Status](https://travis-ci.org/Kalimaha/pytest-pact.svg?branch=master)](https://travis-ci.org/Kalimaha/pytest-pact)
-[![Coverage Status](https://coveralls.io/repos/github/Kalimaha/pytest-pact/badge.svg?branch=master)](https://coveralls.io/github/Kalimaha/pytest-pact?branch=master)
-
-# PyPact
-Python implementation for Pact (http://pact.io/)
-
-## Setup
-```
-python setup.py install
-```
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..ef7ab84
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,228 @@
+.. image:: https://img.shields.io/badge/license-MIT-brightgreen.svg
+ :target: https://github.com/Kalimaha/pact-test/blob/master/LICENSE
+.. image:: https://img.shields.io/badge/python-2.7,%203.3,%203.4,%203.5,%203.6-brightgreen.svg
+ :target: https://travis-ci.org/Kalimaha/pact-test
+.. image:: https://img.shields.io/badge/pypi-0.1.1-brightgreen.svg
+ :target: https://pypi.python.org/pypi?:action=display&name=pact-test&version=0.1.1
+.. image:: https://img.shields.io/pypi/wheel/Django.svg
+ :target: https://pypi.python.org/pypi?:action=display&name=pact-test&version=0.1.1
+.. image:: https://travis-ci.org/Kalimaha/pact-test.svg?branch=master
+ :target: https://travis-ci.org/Kalimaha/pact-test
+.. image:: https://coveralls.io/repos/github/Kalimaha/pact-test/badge.svg?branch=development
+ :target: https://coveralls.io/github/Kalimaha/pact-test?branch=development
+.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
+ :target: https://saythanks.io/to/Kalimaha
+
+Pact Test for Python
+====================
+
+This repository contains a Python implementation for `Pact `_. Pact is a specification for
+Consumer Driven Contracts Testing. For further information about Pact project, contracts testing, pros and cons and
+useful resources please refer to the `Pact website `_.
+
+There are two phases in Consumer Driven Contracts Testing: a Consumer sets up a contract (*it's consumer driven
+after all!*), and a Provider honours it. But, before that...
+
+Installation
+~~~~~~~~~~~~
+
+Pact Test is distributed through `PyPi `_ so it can be easily included in the
+:code:`requirements.txt` file or normally installed with :code:`pip`:
+
+.. code:: bash
+
+ $ pip install pact-test
+
+Providers Tests (*Set the Contracts*)
+-------------------------------------
+
+.. image:: https://img.shields.io/badge/Pact-1.0-brightgreen.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-1
+.. image:: https://img.shields.io/badge/Pact-1.1-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-1.1
+.. image:: https://img.shields.io/badge/Pact-2.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-2
+.. image:: https://img.shields.io/badge/Pact-3.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-3
+.. image:: https://img.shields.io/badge/Pact-4.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-4
+
+Consumers run Provider Tests to create pacts and establish a contract between
+them and service providers. An example of a Python client using pact test is
+available at `here `_. Consumers define
+all the interactions with their providers in the following way:
+
+.. code:: python
+
+ @service_consumer('PythonEats')
+ @has_pact_with('PyzzaHut')
+ class PyzzaHutTest(ServiceProviderTest):
+
+ @given('some pizzas exist')
+ @upon_receiving('a request for a pepperoni pizza')
+ @with_request({'method': 'get', 'path': '/pizzas/pepperoni/'})
+ @will_respond_with({'status': 200, 'body': {'id': 42, 'type': 'pepperoni'}})
+ def test_get_pepperoni_pizza(self):
+ pizza = get_pizza('pepperoni')
+ assert pizza['id'] == 42
+ assert pizza['type'] == 'pepperoni'
+
+This test verifies, against a mock server, the expected interaction and creates
+a JSON file (*the pact*) that will be stored locally and also sent to the
+Pact Broker, if available. It is possible to define multiple tests for the same
+state in order to verify all the scenarios of interest, For example, we can
+test an unhappy :code:`404` situation:
+
+.. code:: python
+
+ @given('some pizzas exist')
+ @upon_receiving('a request for an hawaiian pizza')
+ @with_request({'method': 'get', 'path': '/pizzas/hawaiian/'})
+ @will_respond_with({'status': 404, 'body': {'message': 'we do not serve pineapple with pizza'}})
+ def test_get_hawaiian_pizza(self):
+ pizza = get_pizza('hawaiian')
+ assert pizza.status_code == 404
+ assert pizza.json()['message'] == 'we do not serve pineapple with pizza'
+
+Consumers Tests (*Honour Your Contracts*)
+-----------------------------------------
+
+.. image:: https://img.shields.io/badge/Pact-1.0-brightgreen.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-1
+.. image:: https://img.shields.io/badge/Pact-1.1-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-1.1
+.. image:: https://img.shields.io/badge/Pact-2.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-2
+.. image:: https://img.shields.io/badge/Pact-3.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-3
+.. image:: https://img.shields.io/badge/Pact-4.0-red.svg
+ :target: https://github.com/pact-foundation/pact-specification/tree/version-4
+
+Providers run Consumer Tests to verify that they are honouring their pacts with the consumers. There are few examples
+of an hypothetical restaurant service implemented with the most popular Python web frameworks:
+
+* Djanjo (*TODO*)
+* `Flask `_
+* `Pyramid `_
+
+There are few things required to setup and run consumer tests.
+
+Pact Helper
+~~~~~~~~~~~
+
+This helper class is used by Pact Test to start and stop the web-app before and after the test. It also defines the
+ports and endpoint to be used by the test. The following is an example of Pact Helper:
+
+.. code:: python
+
+ class RestaurantPactHelper(PactHelper):
+ process = None
+
+ def setup(self):
+ self.process = subprocess.Popen('gunicorn start:app -w 3 -b :8080 --log-level error', shell=True)
+
+ def tear_down(self):
+ self.process.kill()
+
+There are few rules for the helper:
+
+* it **must** extend :code:`PactHelper` class from :code:`pact_test`
+* it **must** define a :code:`setup` method
+* it **must** define a :code:`tear_down` method
+
+It is also possible to override default url (*localhost*) and port (*9999*):
+
+.. code:: python
+
+ class RestaurantPactHelper(PactHelper):
+ test_url = '0.0.0.0'
+ test_port = 5000
+
+
+States
+~~~~~~
+
+When a consumer sets a pact, it defines certain states. States are basically pre-requisites to your test. Before
+honouring the pacts, a provider needs to define such states. For example:
+
+.. code:: python
+
+ @honours_pact_with('UberEats')
+ @pact_uri('http://Kalimaha.github.io/src/pacts/pact.json')
+ class UberEats(ServiceConsumerTest):
+
+ @state('some menu items exist')
+ def test_get_menu_items(self):
+ DB.save(MenuItem('spam'))
+ DB.save(MenuItem('eggs'))
+
+In this example, the provider stores some test data in its DB in order to make the system ready to receive mock calls
+from the consumer and therefore verify the pact.
+
+Configuration
+-------------
+
+The default configuration of Pact Test assumes the following values:
+
+* **consumer_tests_path:** :code:`tests/service_consumers`
+* **provider_tests_path:** :code:`tests/service_providers`
+* **pact_broker_uri:** :code:`None`
+
+It is possible to override such values by creating a file named :code:`.pact.json` in the project's root. The following
+is an example of a valid configuration file:
+
+.. code:: json
+
+ {
+ "consumer_tests_path": "mypath/mytests",
+ "provider_tests_path": "mypath/mytests",
+ "pact_broker_uri": "http://example.com/"
+ }
+
+All fields are optional: only specified fields will override default configuration values.
+
+Development
+===========
+
+Setup
+~~~~~
+
+.. code:: bash
+
+ python3 setup.py install
+
+Test
+~~~~
+
+It is possible to run the tests locally with Docker through the following command:
+
+.. code:: bash
+
+ $ ./bin/test
+
+By default this command tests the library against Python 3.6. It is possible to specify the Python version as follows:
+
+.. code:: bash
+
+ $ ./bin/test
+
+Available values for `ENV` are: :code:`py27`, :code:`py33`, :code:`py34`, :code:`py35` and :code:`py36`. It is also
+possible to test all the versions at once with:
+
+.. code:: bash
+
+ $ ./bin/test all
+
+Upload New Version
+~~~~~~~~~~~~~~~~~~
+
+.. code:: bash
+
+ $ python3 setup.py sdist upload
+
+With `Python Wheels `_:
+
+.. code:: bash
+
+ $ python3 setup.py sdist bdist_wheel
+ $ twine upload dist/*
diff --git a/bin/pact-test b/bin/pact-test
new file mode 100755
index 0000000..48a897d
--- /dev/null
+++ b/bin/pact-test
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+import os
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pact_test.runners.pact_tests_runner import verify
+
+
+def print_help():
+ print(' ')
+ print('========================================================')
+ print(' Welcome to Pact Test for Python! ')
+ print('--------------------------------------------------------')
+ print(' Available Commands ')
+ print(' ')
+ print('help..............................Display this help page')
+ print('verify.............................Verify all Pact tests')
+ print('verify consumers..............Verify Consumer Pact tests')
+ print('verify providers..............Verify Provider Pact tests')
+ print('========================================================')
+ print(' ')
+
+
+if len(sys.argv) is 1:
+ print_help()
+else:
+ if sys.argv[1].lower() == 'verify':
+ if len(sys.argv) is 2:
+ verify(verify_consumers=True, verify_providers=True)
+ elif sys.argv[2].lower() == 'consumers':
+ verify(verify_consumers=True, verify_providers=False)
+ elif sys.argv[2].lower() == 'providers':
+ verify(verify_consumers=False, verify_providers=True)
+ else:
+ print_help()
+ elif sys.argv[1].lower() == 'help':
+ print_help()
+ else:
+ print_help()
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..07e0f3b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,43 @@
+version: '2.1'
+
+services:
+
+ py27:
+ command: sh -c "find . -name '*.pyc' -delete && python setup.py test"
+ volumes:
+ - .:/app
+ build:
+ context: .
+ dockerfile: '/environments/Dockerfilepy27'
+
+ py33:
+ command: sh -c "find . -name '*.pyc' -delete && python setup.py test"
+ volumes:
+ - .:/app
+ build:
+ context: .
+ dockerfile: '/environments/Dockerfilepy33'
+
+ py34:
+ command: sh -c "find . -name '*.pyc' -delete && python setup.py test"
+ volumes:
+ - .:/app
+ build:
+ context: .
+ dockerfile: '/environments/Dockerfilepy34'
+
+ py35:
+ command: sh -c "find . -name '*.pyc' -delete && python setup.py test"
+ volumes:
+ - .:/app
+ build:
+ context: .
+ dockerfile: '/environments/Dockerfilepy35'
+
+ py36:
+ command: sh -c "find . -name '*.pyc' -delete && python setup.py test"
+ volumes:
+ - .:/app
+ build:
+ context: .
+ dockerfile: '/environments/Dockerfilepy36'
diff --git a/environments/Dockerfilepy27 b/environments/Dockerfilepy27
new file mode 100644
index 0000000..133fa58
--- /dev/null
+++ b/environments/Dockerfilepy27
@@ -0,0 +1,7 @@
+FROM python:2.7.13-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+ADD requirements.txt /app
+RUN pip install -r requirements.txt
+ADD . /app
diff --git a/environments/Dockerfilepy33 b/environments/Dockerfilepy33
new file mode 100644
index 0000000..cbdae56
--- /dev/null
+++ b/environments/Dockerfilepy33
@@ -0,0 +1,7 @@
+FROM python:3.3.6-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+ADD requirements.txt /app
+RUN pip install -r requirements.txt
+ADD . /app
diff --git a/environments/Dockerfilepy34 b/environments/Dockerfilepy34
new file mode 100644
index 0000000..6d53b41
--- /dev/null
+++ b/environments/Dockerfilepy34
@@ -0,0 +1,7 @@
+FROM python:3.4.6-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+ADD requirements.txt /app
+RUN pip install -r requirements.txt
+ADD . /app
diff --git a/environments/Dockerfilepy35 b/environments/Dockerfilepy35
new file mode 100644
index 0000000..604c073
--- /dev/null
+++ b/environments/Dockerfilepy35
@@ -0,0 +1,7 @@
+FROM python:3.5.3-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+ADD requirements.txt /app
+RUN pip install -r requirements.txt
+ADD . /app
diff --git a/environments/Dockerfilepy36 b/environments/Dockerfilepy36
new file mode 100644
index 0000000..010abec
--- /dev/null
+++ b/environments/Dockerfilepy36
@@ -0,0 +1,7 @@
+FROM python:3.6.1-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+ADD requirements.txt /app
+RUN pip install -r requirements.txt
+ADD . /app
diff --git a/pact_test/__init__.py b/pact_test/__init__.py
new file mode 100644
index 0000000..e2e5037
--- /dev/null
+++ b/pact_test/__init__.py
@@ -0,0 +1,26 @@
+from pact_test.models.pact_helper import PactHelper
+from pact_test.models.service_consumer_test import state
+from pact_test.models.service_consumer_test import pact_uri
+from pact_test.models.service_provider_test import given
+from pact_test.models.service_provider_test import with_request
+from pact_test.models.service_provider_test import has_pact_with
+from pact_test.models.service_provider_test import upon_receiving
+from pact_test.models.service_provider_test import service_consumer
+from pact_test.models.service_consumer_test import honours_pact_with
+from pact_test.models.service_provider_test import will_respond_with
+from pact_test.models.service_consumer_test import ServiceConsumerTest
+from pact_test.models.service_provider_test import ServiceProviderTest
+
+
+state = state
+given = given
+pact_uri = pact_uri
+PactHelper = PactHelper
+with_request = with_request
+has_pact_with = has_pact_with
+upon_receiving = upon_receiving
+service_consumer = service_consumer
+will_respond_with = will_respond_with
+honours_pact_with = honours_pact_with
+ServiceConsumerTest = ServiceConsumerTest
+ServiceProviderTest = ServiceProviderTest
diff --git a/pytest_pact/__init__.py b/pact_test/clients/__init__.py
similarity index 100%
rename from pytest_pact/__init__.py
rename to pact_test/clients/__init__.py
diff --git a/pact_test/clients/http_client.py b/pact_test/clients/http_client.py
new file mode 100644
index 0000000..34d25bf
--- /dev/null
+++ b/pact_test/clients/http_client.py
@@ -0,0 +1,62 @@
+import json
+import requests
+from pact_test.either import *
+from pact_test.constants import *
+from pact_test.utils.logger import *
+from pact_test.models.response import PactResponse
+
+
+def execute_interaction_request(url, port, interaction):
+ url = _build_url(url, port, interaction)
+ method = interaction[REQUEST].get('method', 'GET')
+ body = interaction[REQUEST].get('body', {})
+ headers = interaction[REQUEST].get('headers', {})
+ server_response = _server_response(method, url=url, body=body, headers=headers)
+
+ if type(server_response) is Right:
+ headers = _parse_headers(server_response.value)
+ content_type = _get_content_type(headers)
+ out = Right(PactResponse(
+ status=server_response.value.status_code,
+ headers=headers,
+ body=_parse_body(server_response.value, content_type)
+ ))
+ return out
+
+ return server_response
+
+
+def _server_response(method, url, body, headers):
+ try:
+ payload = json.dumps(body)
+ return Right(requests.request(method, url=url, data=payload, headers=headers))
+ except Exception as e:
+ return Left(str(e))
+
+
+def _parse_body(server_response, content_type):
+ return server_response.json() if JSON in content_type else server_response.text
+
+
+def _parse_headers(server_response):
+ headers = []
+ server_headers = server_response.headers or []
+ for key in server_headers:
+ headers.append((key, server_headers[key]))
+ return headers
+
+
+def _build_url(url, port, interaction):
+ path = interaction[REQUEST].get('path', '')
+ query = interaction[REQUEST].get('query', '')
+
+ test_port = str(port) if port else str(80)
+ return 'http://' + url + ':' + test_port + path + query
+
+
+def _get_content_type(headers):
+ content_type = TEXT
+ types = list(filter(lambda h: h[0].upper() == 'CONTENT-TYPE', headers))
+ if types:
+ content_type = types[0][1]
+ return content_type
diff --git a/dist/.keep b/pact_test/config/__init__.py
similarity index 100%
rename from dist/.keep
rename to pact_test/config/__init__.py
diff --git a/pact_test/config/config_builder.py b/pact_test/config/config_builder.py
new file mode 100644
index 0000000..571d783
--- /dev/null
+++ b/pact_test/config/config_builder.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+
+class Config(object):
+ pact_broker_uri = 'http://localhost:9292/'
+ consumer_tests_path = 'tests/service_consumers'
+ provider_tests_path = 'tests/service_providers'
+ pacts_path = 'pacts'
+ CONFIGURATION_FILE = '.pact.json'
+
+ def __init__(self):
+ user_config_file = self.path_to_user_config_file()
+ if os.path.isfile(user_config_file):
+ with open(user_config_file) as f:
+ self.load_user_settings(f)
+
+ def load_user_settings(self, user_settings_file):
+ settings = json.loads(user_settings_file.read())
+ [self.custom_or_default(settings, key) for key in settings.keys()]
+
+ def path_to_user_config_file(self):
+ return os.path.join(os.getcwd(), self.CONFIGURATION_FILE)
+
+ def custom_or_default(self, user_settings, key):
+ setattr(self, key, user_settings.get(key, getattr(self, key)))
diff --git a/pact_test/constants/__init__.py b/pact_test/constants/__init__.py
new file mode 100644
index 0000000..86af1a2
--- /dev/null
+++ b/pact_test/constants/__init__.py
@@ -0,0 +1,21 @@
+REQUEST = 'request'
+TEXT = 'text'
+JSON = 'application/json'
+PASSED = 'PASSED'
+FAILED = 'FAILED'
+PROVIDER_STATE = 'providerState'
+TEST_PARENT = 'ServiceConsumerTest'
+PROVIDER_TEST_PARENT = 'ServiceProviderTest'
+MISSING_STATE = 'Missing implementation for state '
+MISSING_PACT_HELPER = 'Missing "pact_helper.py" at "'
+MISSING_PACT_URI = 'Missing setup for "pact_uri" at '
+MISSING_TESTS = 'There are no consumer tests to verify.'
+MISSING_PROVIDER_TESTS = 'There are no provider tests to verify.'
+MISSING_SETUP = 'Missing "setup" method in "pact_helper.py".'
+MISSING_HAS_PACT_WITH = 'Missing setup for "has_pact_with" at '
+MISSING_SERVICE_CONSUMER = 'Missing setup for "service_consumer" at '
+MISSING_TEAR_DOWN = 'Missing "tear_down" method in "pact_helper.py".'
+MISSING_HONOURS_PACT_WITH = 'Missing setup for "honours_pact_with" at '
+EXTEND_PACT_HELPER = 'Pact Helper class must extend pact_test.PactHelper'
+MISSING_REQUEST = 'Missing request. Please add @with_request to your method.'
+MISSING_RESPONSE = 'Missing response. Please add @will_respond_with to your method'
diff --git a/pact_test/either/__init__.py b/pact_test/either/__init__.py
new file mode 100644
index 0000000..b82d132
--- /dev/null
+++ b/pact_test/either/__init__.py
@@ -0,0 +1,26 @@
+__all__ = ['Either', 'Left', 'Right']
+
+
+class Either(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __rshift__(self, f):
+ if type(self) is Right:
+ return f(self.value)
+ else:
+ return self
+
+ def concat(self, f, *args):
+ if type(self) is Right:
+ return f(self.value, *args)
+ else:
+ return self
+
+
+class Left(Either):
+ pass
+
+
+class Right(Either):
+ pass
diff --git a/pact_test/matchers/__init__.py b/pact_test/matchers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/matchers/matcher.py b/pact_test/matchers/matcher.py
new file mode 100644
index 0000000..0acaadd
--- /dev/null
+++ b/pact_test/matchers/matcher.py
@@ -0,0 +1,63 @@
+from pact_test.utils.logger import *
+from pact_test.constants import FAILED
+
+
+def is_subset(expected, actual):
+ actual_items = actual.items() if actual else {}
+ expected_items = expected.items() if expected else {}
+
+ stripped_actual_items = map(strip_whitespaces_after_commas, actual_items)
+ stripped_expected_items = map(strip_whitespaces_after_commas, expected_items)
+
+ return all(item in stripped_actual_items for item in stripped_expected_items)
+
+
+def strip_whitespaces_after_commas(t):
+ k = t[0]
+ v = t[1].replace(', ', ',') if type(t[1]) is str else t[1]
+
+ return k, v
+
+
+def build_error_message(section, expected, actual):
+ return {
+ 'actual': actual,
+ 'status': FAILED,
+ 'expected': expected,
+ 'message': section.capitalize() + ' is incorrect'
+ }
+
+
+def is_string(text):
+ try:
+ return True if (type(text) is str or type(text) is unicode) else False
+ except NameError:
+ return True if type(text) is str else False
+
+
+def match_dicts_all_keys_and_values(d1, d2):
+ d1_keys = d1.keys()
+ d2_keys = d2.keys()
+
+ delete_extra_keys(d1, d2)
+
+ all_keys = set(d2_keys).issubset(set(d1_keys))
+ all_values = match_dicts_all_values(d1, d2)
+
+ return all_keys and all_values
+
+
+def match_dicts_all_values(d1, d2):
+ all_values = True
+ for (k1, v1), (k2, v2) in zip(sorted(d2.items()), sorted(d1.items())):
+ all_values = all_values and (v1 == v2)
+ return all_values
+
+
+def delete_extra_keys(d1, d2):
+ extra_keys = list(set(d2.keys()) - set(d1.keys()))
+ for extra_key in extra_keys:
+ d2.pop(extra_key, None)
+ for (k1, v1), (k2, v2) in zip(sorted(d1.items()), sorted(d2.items())):
+ if type(v1) is dict and type(v2) is dict:
+ delete_extra_keys(v1, v2)
diff --git a/pact_test/matchers/request_matcher.py b/pact_test/matchers/request_matcher.py
new file mode 100644
index 0000000..d44abcb
--- /dev/null
+++ b/pact_test/matchers/request_matcher.py
@@ -0,0 +1,77 @@
+from pact_test.either import *
+from pact_test.utils.logger import *
+from pact_test.matchers.matcher import *
+try: # pragma: no cover
+ import urllib.parse as parse # pragma: no cover
+except ImportError: # pragma: no cover
+ import urllib as parse # pragma: no cover
+
+
+def match(actual, expected):
+ return _match_path(actual, expected)\
+ .concat(_match_query, expected)\
+ .concat(_match_method, expected)\
+ .concat(_match_headers, expected)\
+ .concat(_match_body, expected)
+
+
+def _match_body(actual, expected):
+ expected_body = expected.body
+ actual_body = actual.body
+
+ if expected_body is None and actual_body is None:
+ return Right(actual)
+
+ if is_string(expected_body) and is_string(actual_body) and expected_body == actual_body:
+ return Right(actual)
+
+ if type(expected_body) is dict \
+ and type(actual_body) is dict \
+ and _match_dicts_all_keys_and_values(expected_body.copy(), actual_body.copy()):
+ return Right(actual)
+
+ return Left(build_error_message('body', expected_body, actual_body))
+
+
+def _match_dicts_all_keys_and_values(d1, d2):
+ d1_keys = d1.keys()
+ d2_keys = d2.keys()
+
+ all_keys = set(d2_keys).issubset(set(d1_keys))
+ all_values = match_dicts_all_values(d1, d2)
+
+ return all_keys and all_values
+
+
+def _match_headers(actual, expected):
+ actual_dict = dict(pair for d in actual.headers for pair in sorted(d.items()))
+ expected_dict = dict(pair for d in expected.headers for pair in sorted(d.items()))
+
+ insensitive_actual = {k.upper(): v for (k, v) in sorted(actual_dict.items())}
+ insensitive_expected = {k.upper(): v for (k, v) in sorted(expected_dict.items())}
+
+ if is_subset(insensitive_expected, insensitive_actual):
+ return Right(actual)
+ return Left(build_error_message('headers', expected.headers, actual.headers))
+
+
+def _match_path(actual, expected):
+ if actual.path == expected.path:
+ return Right(actual)
+ return Left(build_error_message('path', expected.path, actual.path))
+
+
+def _match_query(actual, expected):
+ if _encode(actual.query) == _encode(expected.query):
+ return Right(actual)
+ return Left(build_error_message('query', expected.query, actual.query))
+
+
+def _encode(str):
+ return parse.quote(str, safe="%3D")
+
+
+def _match_method(actual, expected):
+ if actual.method.upper() == expected.method.upper():
+ return Right(actual)
+ return Left(build_error_message('method', expected.method.upper(), actual.method.upper()))
diff --git a/pact_test/matchers/response_matcher.py b/pact_test/matchers/response_matcher.py
new file mode 100644
index 0000000..2b2e873
--- /dev/null
+++ b/pact_test/matchers/response_matcher.py
@@ -0,0 +1,52 @@
+from pact_test.either import *
+from pact_test.matchers.matcher import *
+
+
+def match(interaction, pact_response):
+ return _match_status(interaction, pact_response)\
+ .concat(_match_headers, pact_response)\
+ .concat(_match_body, pact_response)
+
+
+def _match_status(interaction, pact_response):
+ expected = interaction['response'].get('status')
+ actual = pact_response.status
+
+ if expected is None or actual == expected:
+ return Right(interaction)
+ return Left(build_error_message('status', expected, actual))
+
+
+def _match_headers(interaction, pact_response):
+ expected = interaction['response'].get('headers', {})
+ actual = _to_dict(pact_response.headers)
+
+ insensitive_expected = {k.upper(): v for (k, v) in expected.items()}
+ insensitive_actual = {k.upper(): v for (k, v) in actual.items()}
+
+ if is_subset(insensitive_expected, insensitive_actual):
+ return Right(interaction)
+ return Left(build_error_message('headers', expected, actual))
+
+
+def _match_body(interaction, pact_response):
+ expected = interaction['response'].get('body')
+ actual = pact_response.body
+
+ if expected is None and actual is None:
+ return Right(interaction)
+
+ if is_string(expected) and is_string(actual) and expected == actual:
+ return Right(interaction)
+
+ if type(expected) is dict and type(actual) is dict and match_dicts_all_keys_and_values(expected, actual):
+ return Right(interaction)
+
+ return Left(build_error_message('body', expected, actual))
+
+
+def _to_dict(headers):
+ d = {}
+ for h in headers:
+ d[h[0]] = h[1]
+ return d
diff --git a/pact_test/models/__init__.py b/pact_test/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/models/pact_helper.py b/pact_test/models/pact_helper.py
new file mode 100644
index 0000000..64f442b
--- /dev/null
+++ b/pact_test/models/pact_helper.py
@@ -0,0 +1,7 @@
+MISSING_SETUP = 'Missing implementation of "setup" in PactHelper'
+MISSING_TEAR_DOWN = 'Missing implementation of "tear_down" in PactHelper'
+
+
+class PactHelper(object):
+ test_url = 'localhost'
+ test_port = 9999
diff --git a/pact_test/models/request.py b/pact_test/models/request.py
new file mode 100644
index 0000000..8660ccc
--- /dev/null
+++ b/pact_test/models/request.py
@@ -0,0 +1,13 @@
+class PactRequest(object):
+ path = ''
+ query = ''
+ body = None
+ headers = []
+ method = 'GET'
+
+ def __init__(self, method=None, body=None, headers=None, path=None, query=None):
+ self.body = body or self.body
+ self.path = path or self.path
+ self.query = query or self.query
+ self.method = method or self.method
+ self.headers = headers or self.headers
diff --git a/pact_test/models/response.py b/pact_test/models/response.py
new file mode 100644
index 0000000..ba8c0df
--- /dev/null
+++ b/pact_test/models/response.py
@@ -0,0 +1,9 @@
+class PactResponse(object):
+ body = None
+ headers = []
+ status = 500
+
+ def __init__(self, status=None, body=None, headers=None):
+ self.body = body or self.body
+ self.status = status or self.status
+ self.headers = headers or self.headers
diff --git a/pact_test/models/service_consumer_test.py b/pact_test/models/service_consumer_test.py
new file mode 100644
index 0000000..7b3a397
--- /dev/null
+++ b/pact_test/models/service_consumer_test.py
@@ -0,0 +1,54 @@
+from pact_test.either import *
+from pact_test.constants import *
+
+
+class ServiceConsumerTest(object):
+ pact_uri = None
+ honours_pact_with = None
+
+ @property
+ def states(self):
+ for attr in dir(self):
+ obj = getattr(self, attr)
+ if callable(obj) and hasattr(obj, 'state'):
+ yield obj
+
+ def is_valid(self):
+ if self.pact_uri is None:
+ msg = MISSING_PACT_URI + __file__
+ return Left(msg)
+ if self.honours_pact_with is None:
+ msg = MISSING_HONOURS_PACT_WITH + __file__
+ return Left(msg)
+ return Right(True)
+
+
+def pact_uri(pact_uri_value):
+ def wrapper(calling_class):
+ setattr(calling_class, 'set_pact_uri',
+ eval('set_pact_uri(calling_class, "' + pact_uri_value + '")'))
+ return calling_class
+ return wrapper
+
+
+def set_pact_uri(self, pact_uri_value):
+ self.pact_uri = pact_uri_value
+
+
+def honours_pact_with(honours_pact_with_value):
+ def wrapper(calling_class):
+ setattr(calling_class, 'set_honours_pact_with',
+ eval('set_honours_pact_with(calling_class, "' + honours_pact_with_value + '")'))
+ return calling_class
+ return wrapper
+
+
+def set_honours_pact_with(self, honours_pact_with_value):
+ self.honours_pact_with = honours_pact_with_value
+
+
+def state(state_value):
+ def decorate(function):
+ function.state = state_value
+ return function
+ return decorate
diff --git a/pact_test/models/service_provider_test.py b/pact_test/models/service_provider_test.py
new file mode 100644
index 0000000..6b0fc71
--- /dev/null
+++ b/pact_test/models/service_provider_test.py
@@ -0,0 +1,82 @@
+from pact_test.either import *
+from pact_test.constants import *
+
+
+class ServiceProviderTest(object):
+ service_consumer = None
+ has_pact_with = None
+
+ @property
+ def decorated_methods(self):
+ for attr in dir(self):
+ obj = getattr(self, attr)
+ check = (
+ callable(obj)
+ and hasattr(obj, 'given')
+ and hasattr(obj, 'upon_receiving')
+ and hasattr(obj, 'with_request')
+ and hasattr(obj, 'will_respond_with')
+ )
+ if check:
+ yield obj
+
+ def is_valid(self):
+ if self.service_consumer is None:
+ msg = MISSING_SERVICE_CONSUMER + __file__
+ return Left(msg)
+ if self.has_pact_with is None:
+ msg = MISSING_HAS_PACT_WITH + __file__
+ return Left(msg)
+ return Right(True)
+
+
+def service_consumer(service_consumer_value):
+ def wrapper(calling_class):
+ setattr(calling_class, 'set_service_consumer',
+ eval('set_service_consumer(calling_class, "' + service_consumer_value + '")'))
+ return calling_class
+ return wrapper
+
+
+def set_service_consumer(self, service_consumer_value):
+ self.service_consumer = service_consumer_value
+
+
+def has_pact_with(has_pact_with_value):
+ def wrapper(calling_class):
+ setattr(calling_class, 'set_has_pact_with',
+ eval('set_has_pact_with(calling_class, "' + has_pact_with_value + '")'))
+ return calling_class
+ return wrapper
+
+
+def set_has_pact_with(self, has_pact_with_value):
+ self.has_pact_with = has_pact_with_value
+
+
+def given(given_value):
+ def decorate(function):
+ function.given = given_value
+ return function
+ return decorate
+
+
+def upon_receiving(upon_receiving_value):
+ def decorate(function):
+ function.upon_receiving = upon_receiving_value
+ return function
+ return decorate
+
+
+def with_request(with_request_value):
+ def decorate(function):
+ function.with_request = with_request_value
+ return function
+ return decorate
+
+
+def will_respond_with(will_respond_with_value):
+ def decorate(function):
+ function.will_respond_with = will_respond_with_value
+ return function
+ return decorate
diff --git a/pact_test/repositories/__init__.py b/pact_test/repositories/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/repositories/pact_broker.py b/pact_test/repositories/pact_broker.py
new file mode 100644
index 0000000..ad899dd
--- /dev/null
+++ b/pact_test/repositories/pact_broker.py
@@ -0,0 +1,43 @@
+import json
+import requests
+from pact_test.either import *
+from pact_test.utils.logger import *
+from pact_test.utils.pact_helper_utils import format_headers
+
+
+PACT_BROKER_URL = 'http://localhost:9292/'
+
+
+def upload_pact(provider_name, consumer_name, pact, base_url=PACT_BROKER_URL):
+ pact = format_headers(pact)
+ current_version = get_latest_version(consumer_name, base_url)
+ if type(current_version) is Right:
+ v = next_version(current_version.value)
+ try:
+ url = base_url + 'pacts/provider/' + provider_name + '/consumer/' + consumer_name + '/version/' + v
+ payload = json.dumps(pact)
+ headers = {'content-type': 'application/json'}
+ response = requests.put(url, data=payload, headers=headers)
+ return Right(response.json())
+ except requests.exceptions.ConnectionError as e:
+ msg = 'Failed to establish a new connection with ' + base_url
+ return Left(msg)
+ return current_version
+
+
+def get_latest_version(consumer_name, base_url=PACT_BROKER_URL):
+ try:
+ url = base_url + 'pacticipants/' + consumer_name + '/versions/'
+ response = requests.get(url)
+ if response.status_code is not 200:
+ return Right('1.0.0')
+ return Right(response.json()['_embedded']['versions'][0]['number'])
+ except requests.exceptions.ConnectionError as e:
+ msg = 'Failed to establish a new connection with ' + base_url
+ return Left(msg)
+
+
+def next_version(current_version='1.0.0'):
+ versions = current_version.split('.')
+ next_minor = str(1 + int(versions[-1]))
+ return '.'.join([versions[0], versions[1], next_minor])
diff --git a/pact_test/runners/__init__.py b/pact_test/runners/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/runners/pact_tests_runner.py b/pact_test/runners/pact_tests_runner.py
new file mode 100644
index 0000000..faa7e7b
--- /dev/null
+++ b/pact_test/runners/pact_tests_runner.py
@@ -0,0 +1,60 @@
+import os
+import json
+import copy
+from pact_test.either import *
+from pact_test.utils.logger import *
+from pact_test.config.config_builder import Config
+from pact_test.repositories.pact_broker import upload_pact
+from pact_test.utils.pact_helper_utils import format_headers
+from pact_test.utils.logger import log_consumers_test_results
+from pact_test.utils.logger import log_providers_test_results
+from pact_test.runners.service_consumers.test_suite import ServiceConsumerTestSuiteRunner
+from pact_test.runners.service_providers.test_suite import ServiceProviderTestSuiteRunner
+
+
+def verify(verify_consumers=False, verify_providers=False):
+ config = Config()
+
+ if verify_consumers:
+ run_consumer_tests(config)
+ if verify_providers:
+ run_provider_tests(config)
+
+
+def run_consumer_tests(config):
+ test_results = ServiceConsumerTestSuiteRunner(config).verify()
+ log_consumers_test_results(test_results)
+
+
+def run_provider_tests(config):
+ test_results = ServiceProviderTestSuiteRunner(config).verify()
+ log_providers_test_results(test_results)
+ if type(test_results) is Right:
+ write_pact_files(config, copy.deepcopy(test_results.value))
+ upload_pacts(config, copy.deepcopy(test_results.value))
+
+
+def upload_pacts(config, pacts):
+ for pact in pacts:
+ provider = pact['provider']['name']
+ consumer = pact['consumer']['name']
+ is_uploadable = all(interaction['status'] == 'PASSED' for interaction in pact['interactions'])
+ if is_uploadable:
+ ack = upload_pact(provider, consumer, pact, base_url=config.pact_broker_uri)
+ msg = 'Pact between ' + pact['consumer']['name'] + ' and ' + pact['provider']['name']
+ error(ack.value) if type(ack) is Left else info(msg + ' successfully uploaded')
+
+
+def write_pact_files(config, pacts):
+ pacts_directory = os.path.join(os.getcwd(), config.pacts_path)
+ if not os.path.exists(pacts_directory):
+ info('Creating Pacts directory at: ' + str(pacts_directory))
+ os.makedirs(pacts_directory)
+
+ for pact in pacts:
+ pact_payload = format_headers(copy.deepcopy(pact))
+ filename = pact_payload['consumer']['name'] + '_' + pact_payload['provider']['name'] + '.json'
+ filename = filename.replace(' ', '_').lower()
+ info('Writing pact to: ' + filename)
+ with open(os.path.join(pacts_directory, filename), 'w+') as file:
+ file.write(json.dumps(pact_payload, indent=2))
diff --git a/pact_test/runners/service_consumers/__init__.py b/pact_test/runners/service_consumers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/runners/service_consumers/state_test.py b/pact_test/runners/service_consumers/state_test.py
new file mode 100644
index 0000000..80ca77f
--- /dev/null
+++ b/pact_test/runners/service_consumers/state_test.py
@@ -0,0 +1,47 @@
+from pact_test.either import *
+from pact_test.constants import *
+from pact_test.utils.logger import error
+from pact_test.matchers.response_matcher import match
+from pact_test.clients.http_client import execute_interaction_request
+
+
+def verify_state(interaction, pact_helper, test_instance):
+ state = find_state(interaction, interaction['description'], test_instance)
+ if type(state) is Right:
+ state.value()
+ output = _execute_request(pact_helper, interaction)
+ if type(output) is Right:
+ response_verification = match(interaction, output.value)
+ output = _build_state_response(state, interaction['description'], response_verification)
+ return output
+ error(output.value)
+ return output
+ error(state.value)
+ return state
+
+
+def _execute_request(pact_helper, interaction):
+ url = pact_helper.test_url
+ port = pact_helper.test_port
+ return execute_interaction_request(url, port, interaction)
+
+
+def _build_state_response(state, description, response_verification):
+ if type(response_verification) is Right:
+ return Right(_format_message(state.value.state, description, PASSED, []))
+ else:
+ errors = [response_verification.value]
+ return Left(_format_message(state.value.state, description, FAILED, errors))
+
+
+def find_state(interaction, description, test_instance):
+ state = interaction[PROVIDER_STATE]
+ for s in test_instance.states:
+ if s.state == state:
+ return Right(s)
+ message = 'Missing state implementation for "' + state + '"'
+ return Left(_format_message(state, description, FAILED, [message]))
+
+
+def _format_message(state, description, status, errors):
+ return {'state': state, 'description': description, 'status': status, 'errors': errors}
diff --git a/pact_test/runners/service_consumers/test_suite.py b/pact_test/runners/service_consumers/test_suite.py
new file mode 100644
index 0000000..9abe39d
--- /dev/null
+++ b/pact_test/runners/service_consumers/test_suite.py
@@ -0,0 +1,81 @@
+import os
+import imp
+import inspect
+from pact_test.either import *
+from pact_test.constants import *
+from pact_test.utils.logger import *
+from pact_test.utils.pact_utils import get_pact
+from pact_test.utils.pact_helper_utils import load_pact_helper
+from pact_test.runners.service_consumers.state_test import verify_state
+
+
+class ServiceConsumerTestSuiteRunner(object):
+ pact_helper = None
+
+ def __init__(self, config):
+ self.config = config
+
+ def verify(self):
+ print('')
+ debug('Verify consumers: START')
+ pact_helper = load_pact_helper(self.config.consumer_tests_path)
+ if type(pact_helper) is Right:
+ self.pact_helper = pact_helper.value
+ tests = self.collect_tests()
+ if type(tests) is Right:
+ debug(str(len(tests.value)) + ' test(s) found.')
+ debug('Execute Pact Helper setup: START')
+ self.pact_helper.setup()
+ debug('Execute Pact Helper setup: DONE')
+ test_results = Right(list(map(self.verify_test, tests.value)))
+ debug('Execute Pact Helper tear down: START')
+ self.pact_helper.tear_down()
+ debug('Execute Pact Helper tear down: DONE')
+ debug('Verify consumers: DONE')
+ return test_results
+ error('Verify consumers: EXIT WITH ERRORS:')
+ error(tests.value)
+ return tests
+ error('Verify consumers: EXIT WITH ERRORS:')
+ error(pact_helper.value)
+ return pact_helper
+
+ def verify_test(self, test):
+ validity_check = test.is_valid()
+ if type(validity_check) is Right:
+ pact = get_pact(test.pact_uri)
+ if type(pact) is Right:
+ interactions = pact.value.get('interactions', {})
+ debug(str(len(interactions)) + ' interaction(s) found')
+ test_results = [verify_state(i, self.pact_helper, test) for i in interactions]
+ return Right({'test': test.__class__.__name__, 'results': test_results})
+ error(pact.value)
+ return pact
+ error(validity_check.value)
+ return validity_check
+
+ def collect_tests(self):
+ root = self.config.consumer_tests_path
+ files = list(filter(filter_rule, self.all_files()))
+ files = list(map(lambda f: os.path.join(root, f), files))
+ tests = []
+ for idx, filename in enumerate(files):
+ test = imp.load_source('test' + str(idx), filename)
+ for name, obj in inspect.getmembers(test):
+ if inspect.isclass(obj) and len(inspect.getmro(obj)) > 2:
+ test_parent = inspect.getmro(obj)[1].__name__
+ if test_parent == TEST_PARENT:
+ tests.append(obj())
+
+ if not files:
+ return Left(MISSING_TESTS)
+ return Right(tests)
+
+ def all_files(self):
+ return os.listdir(self.config.consumer_tests_path)
+
+
+def filter_rule(filename):
+ return (filename != '__init__.py' and
+ filename.endswith('.py') and
+ filename.endswith('pact_helper.py') is False)
diff --git a/pact_test/runners/service_providers/__init__.py b/pact_test/runners/service_providers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/runners/service_providers/request_test.py b/pact_test/runners/service_providers/request_test.py
new file mode 100644
index 0000000..0a8619d
--- /dev/null
+++ b/pact_test/runners/service_providers/request_test.py
@@ -0,0 +1,86 @@
+import json
+from pact_test.either import *
+from pact_test.utils.logger import *
+from pact_test.models.request import PactRequest
+from pact_test.models.response import PactResponse
+from pact_test.servers.mock_server import MockServer
+from pact_test.matchers.request_matcher import match
+
+
+def verify_request(decorated_method, port=9999):
+ mock_response = build_expected_response(decorated_method)
+ expected_request = build_expected_request(decorated_method)
+
+ mock_server = MockServer(mock_response=mock_response, port=port)
+ mock_server.start()
+ decorated_method()
+ mock_server.shutdown()
+ report = mock_server.report()
+
+ if len(report) is 0:
+ out = {
+ 'status': 'FAILED',
+ 'providerState': decorated_method.given,
+ 'description': decorated_method.upon_receiving,
+ 'message': 'Missing request(s) for "' + format_message(decorated_method) + '"',
+ 'expected': expected_request.__dict__,
+ 'actual': None
+ }
+ return Left(out)
+
+ actual_request = build_actual_request(report[0])
+ matching_result = match(actual_request, expected_request)
+
+ if type(matching_result) is Right:
+ resp = mock_response.__dict__
+ resp['body'] = mock_response.__dict__['body']
+ out = {
+ 'status': 'PASSED',
+ 'providerState': decorated_method.given,
+ 'description': decorated_method.upon_receiving,
+ 'request': actual_request.__dict__,
+ 'response': resp
+ }
+ return Right(out)
+ else:
+ reason = matching_result.value.copy()
+ reason.update({
+ 'providerState': decorated_method.given,
+ 'description': decorated_method.upon_receiving
+ })
+ return Left(reason)
+
+
+def build_expected_response(decorated_method):
+ return PactResponse(
+ body=decorated_method.will_respond_with.get('body'),
+ status=decorated_method.will_respond_with.get('status'),
+ headers=decorated_method.will_respond_with.get('headers')
+ )
+
+
+def build_expected_request(decorated_method):
+ return PactRequest(
+ method=decorated_method.with_request.get('method'),
+ body=decorated_method.with_request.get('body'),
+ headers=decorated_method.with_request.get('headers'),
+ path=decorated_method.with_request.get('path'),
+ query=decorated_method.with_request.get('query')
+ )
+
+
+def build_actual_request(request):
+ return PactRequest(
+ path=request.get('path'),
+ query=request.get('query'),
+ method=request.get('method'),
+ body=request.get('body'),
+ headers=request.get('headers')
+ )
+
+
+def format_message(decorated_method):
+ return 'given ' + \
+ decorated_method.given + \
+ ', upon receiving ' + \
+ decorated_method.upon_receiving
diff --git a/pact_test/runners/service_providers/test_suite.py b/pact_test/runners/service_providers/test_suite.py
new file mode 100644
index 0000000..cc93fc9
--- /dev/null
+++ b/pact_test/runners/service_providers/test_suite.py
@@ -0,0 +1,85 @@
+import os
+import imp
+import inspect
+from pact_test.either import *
+from pact_test.constants import *
+from pact_test.utils.logger import error
+from pact_test.utils.logger import debug
+from pact_test.runners.service_providers.request_test import verify_request
+
+
+class ServiceProviderTestSuiteRunner(object):
+ def __init__(self, config):
+ self.config = config
+
+ def verify(self):
+ debug('Verify providers: START')
+ tests = self.collect_tests()
+ if type(tests) is Right:
+ debug(str(len(tests.value)) + ' test(s) found.')
+ pacts = []
+ for test in tests.value:
+ debug('Start: ' + test.service_consumer + ' has Pact with ' + test.has_pact_with)
+ test_verification = test.is_valid()
+ if type(test_verification) is Right:
+ pact = self.create_pact(test)
+ if len(pact['interactions']) == 0:
+ error('Verify providers: EXIT WITH ERRORS:')
+ return Left('No pact-test methods available in test class')
+ pacts.append(pact)
+ else:
+ error('Verify providers: EXIT WITH ERRORS:')
+ error(test_verification.value)
+ return test_verification
+ return Right(pacts)
+ error('Verify providers: EXIT WITH ERRORS:')
+ error(tests.value)
+ return tests
+
+ @staticmethod
+ def create_pact(test):
+ interactions = []
+ for decorated_method in test.decorated_methods:
+ debug(' Verify interaction: ' + decorated_method.__name__)
+ interactions.append(verify_request(decorated_method).value)
+ pact = {
+ 'interactions': interactions,
+ 'provider': {
+ 'name': test.has_pact_with
+ },
+ 'consumer': {
+ 'name': test.service_consumer
+ }
+ }
+ return pact
+
+ def collect_tests(self):
+ root = self.config.provider_tests_path
+ debug('Root for Provider Tests: ' + str(root))
+ all_files = self.all_files()
+ if type(all_files) is Right:
+ files = list(filter(self.filter_rule, all_files.value))
+ files = list(map(lambda f: os.path.join(root, f), files))
+ tests = []
+ for idx, filename in enumerate(files):
+ test = imp.load_source('test' + str(idx), filename)
+ for name, obj in inspect.getmembers(test):
+ if inspect.isclass(obj) and len(inspect.getmro(obj)) > 2:
+ test_parent = inspect.getmro(obj)[1].__name__
+ if test_parent == PROVIDER_TEST_PARENT:
+ tests.append(obj())
+
+ if not files:
+ return Left(MISSING_PROVIDER_TESTS)
+ return Right(tests)
+ return all_files
+
+ @staticmethod
+ def filter_rule(filename):
+ return filename != '__init__.py' and filename.endswith('.py')
+
+ def all_files(self):
+ try:
+ return Right(os.listdir(self.config.provider_tests_path))
+ except Exception as e:
+ return Left(str(e))
diff --git a/pact_test/servers/__init__.py b/pact_test/servers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/servers/mock_server.py b/pact_test/servers/mock_server.py
new file mode 100644
index 0000000..7ac9b9a
--- /dev/null
+++ b/pact_test/servers/mock_server.py
@@ -0,0 +1,98 @@
+import json
+from threading import Thread
+from pact_test.models.response import PactResponse
+try: # pragma: no cover
+ import socketserver as SocketServer # pragma: no cover
+ import http.server as SimpleHTTPServer # pragma: no cover
+except ImportError: # pragma: no cover
+ import SocketServer # pragma: no cover
+ import SimpleHTTPServer # pragma: no cover
+
+
+ARCHIVE = []
+
+
+def build_proxy(mock_response=PactResponse()):
+ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ def __init__(self, *args, **kwargs):
+ SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
+ self.mock_response = mock_response
+
+ def do_GET(self):
+ data = self.read_request_data(self)
+ self.handle_request('GET', data)
+
+ def do_POST(self):
+ data = self.read_request_data(self)
+ self.handle_request('POST', data)
+
+ def do_PUT(self):
+ data = self.read_request_data(self)
+ self.handle_request('PUT', data)
+
+ def do_DELETE(self):
+ data = self.read_request_data(self)
+ self.handle_request('DELETE', data)
+
+ @staticmethod
+ def read_request_data(other_self):
+ header_value = other_self.headers.get('Content-Length')
+ data_length = int(header_value) if header_value is not None else None
+ return other_self.rfile.read(data_length) if data_length is not None else None
+
+ def format_request(self, http_method, data):
+ path_and_query = self.path.split('?')
+ path = path_and_query[0]
+ query = '?' + path_and_query[1] if len(path_and_query) == 2 else ''
+
+ return {
+ 'method': http_method,
+ 'path': path,
+ 'query': query,
+ 'body': json.loads(data.decode('utf-8')) if data is not None else data,
+ 'headers': list(dict([(key, value)]) for key, value in self.headers.items())
+ }
+
+ def handle_request(self, http_method, data):
+ info = self.format_request(http_method, data)
+ ARCHIVE.append(info)
+ self.respond()
+
+ def respond(self):
+ self.send_response(int(mock_response.status))
+ for header in mock_response.headers:
+ for key, value in header.items():
+ self.send_header(key, value)
+ self.end_headers()
+ self.wfile.write(str(mock_response.body).replace("'", '"').encode())
+
+ return Proxy
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+ def __init__(self, server_address, RequestHandlerClass):
+ self.allow_reuse_address = True
+ SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
+
+
+class MockServer(object):
+
+ def __init__(self, mock_response=PactResponse(), base_url='0.0.0.0', port=1234):
+ self.port = port
+ self.base_url = base_url
+ self.server = ThreadedTCPServer((self.base_url, self.port), build_proxy(mock_response))
+ self.server_thread = Thread(target=self.server.serve_forever)
+ self.server_thread.daemon = True
+ global ARCHIVE
+ ARCHIVE = []
+
+ def start(self):
+ self.server_thread.start()
+
+ def shutdown(self):
+ self.server.shutdown()
+ self.server.server_close()
+
+ @staticmethod
+ def report():
+ return ARCHIVE
diff --git a/pact_test/utils/__init__.py b/pact_test/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pact_test/utils/http_utils.py b/pact_test/utils/http_utils.py
new file mode 100644
index 0000000..20d5cb2
--- /dev/null
+++ b/pact_test/utils/http_utils.py
@@ -0,0 +1,36 @@
+from pact_test.models.request import PactRequest
+from pact_test.models.response import PactResponse
+
+
+REQUEST = 'request'
+RESPONSE = 'response'
+
+
+def build_request_from_interaction(interaction):
+ request = interaction[REQUEST]
+
+ return PactRequest(
+ body=request.get('body', None),
+ path=request.get('path', None),
+ query=request.get('query', None),
+ method=request.get('method', None),
+ headers=_build_headers_from_pact(interaction, REQUEST)
+ )
+
+
+def build_response_from_interaction(interaction):
+ response = interaction[RESPONSE]
+
+ return PactResponse(
+ body=response.get('body', None),
+ status=response.get('status', None),
+ headers=_build_headers_from_pact(interaction, RESPONSE)
+ )
+
+
+def _build_headers_from_pact(pact, request_or_response):
+ headers = []
+ pact_headers = pact[request_or_response].get('headers', None)
+ for h in (h for h in (pact_headers if pact_headers else [])):
+ headers.append((h, pact_headers[h]))
+ return headers
diff --git a/pact_test/utils/logger.py b/pact_test/utils/logger.py
new file mode 100644
index 0000000..9a06b01
--- /dev/null
+++ b/pact_test/utils/logger.py
@@ -0,0 +1,79 @@
+from pact_test.either import *
+
+PREFIX = '[Pact Test for Python] - ' # pragma: no cover
+
+
+def info(message): # pragma: no cover
+ print('\033[92m' + PREFIX + str(message) + '\033[0m') # pragma: no cover
+
+
+def error(message): # pragma: no cover
+ print('\033[91m' + PREFIX + str(message) + '\033[0m') # pragma: no cover
+
+
+def debug(message): # pragma: no cover
+ print('\033[93m' + PREFIX + str(message) + '\033[0m') # pragma: no cover
+
+
+def log_consumers_test_results(test_results):
+ if type(test_results) is Left:
+ error(test_results.value)
+ else:
+ if type(test_results.value) is Left:
+ error(test_results.value.value)
+ else:
+ for test_result in test_results.value:
+ print('')
+ info('Test: ' + test_result.value['test'])
+ for result in test_result.value['results']:
+ if type(result.value) is dict:
+ info(' GIVEN ' + result.value['state'] + ' UPON RECEIVING ' + result.value['description'])
+ info(' status: ' + result.value['status'])
+ for test_error in result.value['errors']:
+ if type(test_error) is dict:
+ error(' expected: ' + str(test_error['expected']))
+ error(' actual: ' + str(test_error['actual']))
+ error(' message: ' + str(test_error['message']))
+ else:
+ error(' message: ' + str(test_error))
+ else:
+ error(' ' + str(result.value))
+ info('')
+ info('Goodbye!')
+ print('')
+
+
+def log_providers_test_results(test_results):
+ if type(test_results) is Left:
+ error(test_results.value)
+ else:
+ for r in test_results.value:
+ print('')
+ info('A pact between ' + r['consumer']['name'] + ' and ' + r['provider']['name'])
+ for i in r['interactions']:
+ if i['status'] == 'FAILED':
+ error(' Given ' + i['providerState'] + ', upon receiving ' + i['description'] + ' from ' + r['consumer']['name'])
+ error(' Status: ' + i['status'])
+ error(' Message: ' + i['message'])
+ error(' Expected: ' + str(i.get('expected')))
+ error(' Actual: ' + str(i.get('actual')))
+ else:
+ info('')
+ info(' Given ' + i['providerState'] + ', upon receiving ' + i['description'] + ' from ' + r['consumer']['name'] + ' with:')
+ info('')
+ info(' {')
+ info(' "method": ' + str(i['request']['method']) + ',')
+ info(' "path": ' + str(i['request']['path']) + ',')
+ info(' "query": ' + str(i['request']['query']) + ',')
+ info(' "headers": ' + str(i['request']['headers']) + ',')
+ info(' "body": ' + str(i['request']['body']))
+ info(' }')
+ info('')
+ info(' ' + r['provider']['name'] + ' will respond with: ')
+ info('')
+ info(' {')
+ info(' "status": ' + str(i['response']['status']) + ',')
+ info(' "body": ' + str(i['response']['body']) + ',')
+ info(' "headers": ' + str(i['response']['headers']))
+ info(' }')
+ info('')
diff --git a/pact_test/utils/pact_helper_utils.py b/pact_test/utils/pact_helper_utils.py
new file mode 100644
index 0000000..96417ba
--- /dev/null
+++ b/pact_test/utils/pact_helper_utils.py
@@ -0,0 +1,65 @@
+import os
+import imp
+import inspect
+from pact_test.either import *
+from pact_test import PactHelper
+from pact_test.constants import MISSING_SETUP
+from pact_test.constants import MISSING_TEAR_DOWN
+from pact_test.constants import EXTEND_PACT_HELPER
+from pact_test.constants import MISSING_PACT_HELPER
+
+
+def load_pact_helper(consumer_tests_path):
+ return _path_to_pact_helper(consumer_tests_path)\
+ .concat(_load_module, 'pact_helper') \
+ >> _load_user_class
+
+
+def _load_user_class(user_module):
+ user_class = None
+
+ for name, obj in inspect.getmembers(user_module):
+ if inspect.isclass(obj) and len(inspect.getmro(obj)) > 2 and issubclass(obj, PactHelper):
+ user_class = obj()
+
+ if user_class is None:
+ return Left(EXTEND_PACT_HELPER)
+ if hasattr(user_class, 'setup') is False:
+ return Left(MISSING_SETUP)
+ if hasattr(user_class, 'tear_down') is False:
+ return Left(MISSING_TEAR_DOWN)
+
+ return Right(user_class)
+
+
+def _load_module(path, module_name):
+ try:
+ return Right(imp.load_source(module_name, path))
+ except Exception:
+ return Left(MISSING_PACT_HELPER + path + '".')
+
+
+def _path_to_pact_helper(consumer_tests_path):
+ path = os.path.join(consumer_tests_path, 'pact_helper.py')
+ if os.path.isfile(path) is False:
+ msg = MISSING_PACT_HELPER + consumer_tests_path + '".'
+ return Left(msg)
+ return Right(path)
+
+
+def format_headers(pact):
+ for interaction in pact.get('interactions', []):
+ if interaction.get('request') is not None:
+ req_headers = interaction.get('request').get('headers')
+ fixed_req_headers = {}
+ for h in req_headers:
+ fixed_req_headers.update(h)
+ interaction['request']['headers'] = fixed_req_headers
+
+ if interaction.get('response') is not None:
+ res_headers = interaction.get('response').get('headers')
+ fixes_req_headers = {}
+ for h in res_headers:
+ fixes_req_headers.update(h)
+ interaction['response']['headers'] = fixes_req_headers
+ return pact
diff --git a/pact_test/utils/pact_utils.py b/pact_test/utils/pact_utils.py
new file mode 100644
index 0000000..12df1ea
--- /dev/null
+++ b/pact_test/utils/pact_utils.py
@@ -0,0 +1,24 @@
+import json
+import requests
+from pact_test.either import *
+
+
+def get_pact(location):
+ if location.startswith(('http', 'https')):
+ return __get_pact_from_url(location)
+ return __get_pact_from_file(location)
+
+
+def __get_pact_from_file(filename):
+ try:
+ with open(filename) as file_content:
+ return Right(json.loads(file_content.read()))
+ except Exception as e:
+ return Left(str(e))
+
+
+def __get_pact_from_url(url):
+ try:
+ return Right(requests.get(url).json())
+ except Exception as e:
+ return Left(str(e))
diff --git a/pytest_pact/PyPact.py b/pytest_pact/PyPact.py
deleted file mode 100644
index f23d6da..0000000
--- a/pytest_pact/PyPact.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import pytest
-
-
-state = pytest.mark.state
-given = pytest.mark.given
-base_uri = pytest.mark.base_uri
-pact_uri = pytest.mark.pact_uri
-with_request = pytest.mark.with_request
-has_pact_with = pytest.mark.has_pact_with
-upon_receiving = pytest.mark.upon_receiving
-will_respond_with = pytest.mark.will_respond_with
-service_consumer = pytest.mark.service_consumer
-honours_pact_with = pytest.mark.honours_pact_with
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_pyfunc_call(pyfuncitem):
- # print(describe_consumer_pact(pyfuncitem))
-
- # pypact = PyPact()
- # pypact.executor(pyfuncitem)
-
-
-
- outcome = yield
- # outcome.excinfo may be None or a (cls, val, tb) tuple
- print(outcome)
-
- res = outcome.get_result() # will raise if outcome was exception
- # postprocess result
- print(res)
-
-
-def consumer_or_provider(pyfuncitem):
- pass
-
-class PyPactConsumer(object):
- pass
-
-class PyPactProvider(object):
- pass
-
-
-def describe_consumer_pact(pyfuncitem):
- s = ''
- s += 'Given ' + read_marker(pyfuncitem, 'given') + ', '
- s += 'upon receiving ' + read_marker(pyfuncitem, 'upon_receiving') + ' '
- s += 'from ' + read_marker(pyfuncitem, 'service_consumer') + ' '
- s += 'with:\n\n' + read_marker(pyfuncitem, 'with_request') + '\n\n'
- s += read_marker(pyfuncitem, 'has_pact_with') + ' will respond with:\n\n'
- s += read_marker(pyfuncitem, 'will_respond_with')
- return s
-
-def read_marker(pyfuncitem, marker_name):
- marker = pyfuncitem.get_marker(marker_name)
- return str(marker.args[0]) if marker else ''
diff --git a/requirements.txt b/requirements.txt
index aa815b2..d9d4b8c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,7 @@
-pytest>=3.0
\ No newline at end of file
+requests
+pytest>=3.0
+pytest-pep8
+pytest-cov
+pytest-mock
+pytest-sugar
+pytest-runner
diff --git a/setup.cfg b/setup.cfg
index b7e4789..6dd0a4b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,11 @@
+[bdist_wheel]
+universal=1
+
[aliases]
test=pytest
+
+[tool:pytest]
+addopts=--pep8 --maxfail=1 -rf --cov-report term-missing --cov=pact_test tests/
+testpaths=tests
+python_files=*.py
+norecursedirs=tests/resources
diff --git a/setup.py b/setup.py
index 7bda334..9f94e74 100644
--- a/setup.py
+++ b/setup.py
@@ -2,21 +2,34 @@
from setuptools import find_packages
setup(
- name='pytest-pact',
- version='0.1.0',
+ name='pact-test',
+ version='1.0.3',
author='Guido Barbaglia',
author_email='guido.barbaglia@gmail.com',
packages=find_packages(),
- license='LICENSE.txt',
- long_description=open('README.md').read(),
+ license='LICENSE',
+ long_description=open('README.rst').read(),
description='Python implementation for Pact (http://pact.io/)',
- install_requires=[],
+ install_requires=['requests'],
setup_requires=['pytest-runner'],
- tests_require=['pytest', 'pytest-sugar'],
- url='https://github.com/Kalimaha/pytest-pact/',
- entry_points = {
- 'pytest11': [
- 'pytest-pact = pytest_pact.PyPact',
- ]
- }
+ tests_require=[
+ 'pytest>=3.0',
+ 'pytest-pep8',
+ 'pytest-sugar',
+ 'pytest-mock',
+ 'pytest-cov',
+ 'pytest-runner'
+ ],
+ url='https://github.com/Kalimaha/pact-test/',
+ scripts=['bin/pact-test'],
+ classifiers=[
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Topic :: Software Development :: Testing'
+ ]
)
diff --git a/tests/acceptance/__init__.py b/tests/acceptance/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/acceptance_test_loader.py b/tests/acceptance/acceptance_test_loader.py
new file mode 100644
index 0000000..57b232d
--- /dev/null
+++ b/tests/acceptance/acceptance_test_loader.py
@@ -0,0 +1,14 @@
+import os
+import json
+
+
+def load_acceptance_test(path_to_file):
+ acceptance_test_filename = os.path.basename(path_to_file).split('.py')[0]
+ acceptance_test_filename += '.json'
+ dir_name = os.path.dirname(path_to_file)
+
+ path = os.path.join(dir_name, acceptance_test_filename)
+ with open(path) as f:
+ data = json.load(f)
+
+ return data
diff --git a/tests/acceptance/version_1/testcases/request/body/__init__.py b/tests/acceptance/version_1/testcases/request/body/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/version_1/testcases/request/body/array_in_different_order.json b/tests/acceptance/version_1/testcases/request/body/array_in_different_order.json
new file mode 100755
index 0000000..11d2c01
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/array_in_different_order.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Favourite colours in wrong order",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["blue", "red"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/array_in_different_order.py b/tests/acceptance/version_1/testcases/request/body/array_in_different_order.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/array_in_different_order.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.json b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.json
new file mode 100755
index 0000000..4fc0c10
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Incorrect favourite colour",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","taupe"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.py b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_index.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.json b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.json
new file mode 100755
index 0000000..12216ff
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Incorrect value at alligator name",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Fred"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.py b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/different_value_found_at_key.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/matches.json b/tests/acceptance/version_1/testcases/request/body/matches.json
new file mode 100755
index 0000000..9f66c60
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/matches.json
@@ -0,0 +1,30 @@
+{
+ "match": true,
+ "comment": "Requests match",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "feet": 4,
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4,
+ "name": "Mary",
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/matches.py b/tests/acceptance/version_1/testcases/request/body/matches.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/body/missing_index.json b/tests/acceptance/version_1/testcases/request/body/missing_index.json
new file mode 100755
index 0000000..fe1c897
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/missing_index.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Missing favorite colour",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator": {
+ "favouriteColours": ["red"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/missing_index.py b/tests/acceptance/version_1/testcases/request/body/missing_index.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/missing_index.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/missing_key.json b/tests/acceptance/version_1/testcases/request/body/missing_key.json
new file mode 100755
index 0000000..59c93a4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/missing_key.json
@@ -0,0 +1,27 @@
+{
+ "match": false,
+ "comment": "Missing key alligator name",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "age": 3
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator": {
+ "age": 3
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/missing_key.py b/tests/acceptance/version_1/testcases/request/body/missing_key.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/missing_key.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.json b/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.json
new file mode 100755
index 0000000..82c040f
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Name should be null",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": null
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Fred"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.py b/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/not_null_found_at_key_when_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.json b/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.json
new file mode 100755
index 0000000..871bf76
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Favourite colours expected to contain null, but not null found",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",null,"3"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.py b/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/not_null_found_in_array_when_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.json b/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.json
new file mode 100755
index 0000000..94b80fb
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Name should be null",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.py b/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/null_found_at_key_where_not_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.json b/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.json
new file mode 100755
index 0000000..42f9e4d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Favourite colours expected to be strings found a null",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",null,"3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.py b/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/null_found_in_array_when_not_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.json b/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.json
new file mode 100755
index 0000000..957dfc1
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Number of feet expected to be string but was number",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": "4"
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.py b/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/number_found_at_key_when_string_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.json b/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.json
new file mode 100755
index 0000000..15664c1
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Favourite colours expected to be strings found a number",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",2,"3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.py b/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/number_found_in_array_when_string_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.json b/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.json
new file mode 100755
index 0000000..08af3fa
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Plain text that does not match",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named fred"
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.py b/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.py
new file mode 100644
index 0000000..bd5ee83
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/plain_text_that_does_not_match.py
@@ -0,0 +1,17 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ actual.headers = [{'Content-Type': 'text/plain'}]
+ expected.headers = [{'Content-Type': 'text/plain'}]
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.json b/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.json
new file mode 100755
index 0000000..2afa70c
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Plain text that matches",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.py b/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.py
new file mode 100644
index 0000000..5a6ec44
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/plain_text_that_matches.py
@@ -0,0 +1,17 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ actual.headers = [{'Content-Type': 'text/plain'}]
+ expected.headers = [{'Content-Type': 'text/plain'}]
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.json b/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.json
new file mode 100755
index 0000000..9eaa5d1
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Number of feet expected to be number but was string",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": "4"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.py b/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/string_found_at_key_when_number_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.json b/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.json
new file mode 100755
index 0000000..9948ad4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Favourite Numbers expected to be numbers, but 2 is a string",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": [1,2,3]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": [1,"2",3]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.py b/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/string_found_in_array_when_number_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.json b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.json
new file mode 100755
index 0000000..3e68632
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Unexpected favourite colour",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue","taupe"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.py b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_not_null_value.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.json b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.json
new file mode 100755
index 0000000..ca21b96
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.json
@@ -0,0 +1,26 @@
+{
+ "match": false,
+ "comment": "Unexpected favourite colour with null value",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue", null]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.py b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_index_with_null_value.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.json b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.json
new file mode 100755
index 0000000..73a50f9
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.json
@@ -0,0 +1,27 @@
+{
+ "match": false,
+ "comment": "Unexpected phone number",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "phoneNumber": "12345678"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.py b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_not_null_value.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.json b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.json
new file mode 100755
index 0000000..68c6501
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.json
@@ -0,0 +1,27 @@
+{
+ "match": false,
+ "comment": "Unexpected phone number with null value",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "phoneNumber": null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.py b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/body/unexpected_key_with_null_value.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/headers/__init__.py b/tests/acceptance/version_1/testcases/request/headers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/version_1/testcases/request/headers/empty_headers.json b/tests/acceptance/version_1/testcases/request/headers/empty_headers.json
new file mode 100755
index 0000000..cb6ca4a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/empty_headers.json
@@ -0,0 +1,17 @@
+{
+ "match": true,
+ "comment": "Empty headers match",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {}
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/empty_headers.py b/tests/acceptance/version_1/testcases/request/headers/empty_headers.py
new file mode 100644
index 0000000..f0fc310
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/empty_headers.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(headers=[])
+ expected = PactRequest(headers=[])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.json b/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.json
new file mode 100755
index 0000000..76f1de0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.json
@@ -0,0 +1,20 @@
+{
+ "match": true,
+ "comment": "Header name is case insensitive",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators"
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "ACCEPT": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.py b/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.py
new file mode 100644
index 0000000..503be41
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/header_name_is_different_case.py
@@ -0,0 +1,18 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'ACCEPT': 'alligators'}]
+ )
+ expected = PactRequest(
+ headers=[{'Accept': 'alligators'}]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.json b/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.json
new file mode 100755
index 0000000..9b10a7b
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Headers values are case sensitive",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators"
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "Alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.py b/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.py
new file mode 100644
index 0000000..e05f505
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/header_value_is_different_case.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'Accept': 'Alligators'}]
+ )
+ expected = PactRequest(
+ headers=[{'Accept': 'alligators'}]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/headers/matches.json b/tests/acceptance/version_1/testcases/request/headers/matches.json
new file mode 100755
index 0000000..7a175c1
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/matches.json
@@ -0,0 +1,22 @@
+{
+ "match": true,
+ "comment": "Headers match",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators",
+ "Content-Type": "hippos"
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Content-Type": "hippos",
+ "Accept": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/matches.py b/tests/acceptance/version_1/testcases/request/headers/matches.py
new file mode 100644
index 0000000..9c0315e
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/matches.py
@@ -0,0 +1,18 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'Content-Type': 'hippos'}, {'Accept': 'alligators'}]
+ )
+ expected = PactRequest(
+ headers=[{'Accept': 'alligators'}, {'Content-Type': 'hippos'}]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.json b/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.json
new file mode 100755
index 0000000..be6ffc4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Comma separated headers out of order, order can matter http://tools.ietf.org/html/rfc2616",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators, hippos"
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "hippos, alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.py b/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.py
new file mode 100644
index 0000000..716b33b
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/order_of_comma_separated_header_values_different.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'Accept': 'hippos, alligators'}]
+ )
+ expected = PactRequest(
+ headers=[{'Accept': 'alligators, hippos'}]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.json b/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.json
new file mode 100755
index 0000000..d4b910d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Extra headers allowed",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {}
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.py b/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.py
new file mode 100644
index 0000000..ae80b72
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/unexpected_header_found.py
@@ -0,0 +1,18 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'Accept': 'alligators'}]
+ )
+ expected = PactRequest(
+ headers=[]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.json b/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.json
new file mode 100755
index 0000000..64ce2f0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.json
@@ -0,0 +1,20 @@
+{
+ "match": true,
+ "comment": "Whitespace between comma separated headers does not matter",
+ "expected" : {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators,hippos"
+ }
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path",
+ "query": "",
+ "headers": {
+ "Accept": "alligators, hippos"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.py b/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.py
new file mode 100644
index 0000000..69162e1
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/headers/whitespace_after_comma_different.py
@@ -0,0 +1,18 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(
+ headers=[{'Accept': 'alligators, hippos'}]
+ )
+ expected = PactRequest(
+ headers=[{'Accept': 'alligators,hippos'}]
+ )
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/method/__init__.py b/tests/acceptance/version_1/testcases/request/method/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/version_1/testcases/request/method/different_method.json b/tests/acceptance/version_1/testcases/request/method/different_method.json
new file mode 100755
index 0000000..fe600d8
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/different_method.json
@@ -0,0 +1,17 @@
+{
+ "match": false,
+ "comment": "Methods is incorrect",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/method/different_method.py b/tests/acceptance/version_1/testcases/request/method/different_method.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/different_method.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/method/matches.json b/tests/acceptance/version_1/testcases/request/method/matches.json
new file mode 100755
index 0000000..58c993f
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/matches.json
@@ -0,0 +1,16 @@
+{
+ "match": true,
+ "comment": "Methods match",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+ }
+}
diff --git a/tests/acceptance/version_1/testcases/request/method/matches.py b/tests/acceptance/version_1/testcases/request/method/matches.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/method/method_is_different_case.json b/tests/acceptance/version_1/testcases/request/method/method_is_different_case.json
new file mode 100755
index 0000000..135c86d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/method_is_different_case.json
@@ -0,0 +1,17 @@
+{
+ "match": true,
+ "comment": "Methods case does not matter",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+ },
+ "actual": {
+ "method": "post",
+ "path": "/",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/method/method_is_different_case.py b/tests/acceptance/version_1/testcases/request/method/method_is_different_case.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/method/method_is_different_case.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/path/__init__.py b/tests/acceptance/version_1/testcases/request/path/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.json b/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.json
new file mode 100755
index 0000000..11d0abf
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Empty path found when forward slash expected",
+ "expected" : {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.py b/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/empty_path_found_when_forward_slash_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.json b/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.json
new file mode 100755
index 0000000..ab7a7cb
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Foward slash found when empty path expected",
+ "expected" : {
+ "method": "POST",
+ "path": "",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.py b/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/forward_slash_found_when_empty_path_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/path/incorrect_path.json b/tests/acceptance/version_1/testcases/request/path/incorrect_path.json
new file mode 100755
index 0000000..c968c72
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/incorrect_path.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Paths do not match",
+ "expected" : {
+ "method": "POST",
+ "path": "/path/to/something",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path/to/something/else",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/incorrect_path.py b/tests/acceptance/version_1/testcases/request/path/incorrect_path.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/incorrect_path.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/path/matches.json b/tests/acceptance/version_1/testcases/request/path/matches.json
new file mode 100755
index 0000000..e0880df
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/matches.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Paths match",
+ "expected" : {
+ "method": "POST",
+ "path": "/path/to/something",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path/to/something",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/matches.py b/tests/acceptance/version_1/testcases/request/path/matches.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.json b/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.json
new file mode 100755
index 0000000..cfd72d4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Path is missing trailing slash, trailing slashes can matter",
+ "expected" : {
+ "method": "POST",
+ "path": "/path/to/something/",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path/to/something",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.py b/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/missing_trailing_slash_in_path.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.json b/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.json
new file mode 100755
index 0000000..e6b4434
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Path has unexpected trailing slash, trailing slashes can matter",
+ "expected" : {
+ "method": "POST",
+ "path": "/path/to/something",
+ "query": "",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "POST",
+ "path": "/path/to/something/",
+ "query": "",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.py b/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/path/unexpected_trailing_slash_in_path.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/query/__init__.py b/tests/acceptance/version_1/testcases/request/query/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/acceptance/version_1/testcases/request/query/different_param_order.json b/tests/acceptance/version_1/testcases/request/query/different_param_order.json
new file mode 100755
index 0000000..eb6f185
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/different_param_order.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Query strings are matched using basic string equality, these are not equal.",
+ "expected" : {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/path",
+ "query": "hippo=John&alligator=Mary",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/query/different_param_order.py b/tests/acceptance/version_1/testcases/request/query/different_param_order.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/different_param_order.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/query/different_param_values.json b/tests/acceptance/version_1/testcases/request/query/different_param_values.json
new file mode 100755
index 0000000..6dbc909
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/different_param_values.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Queries are not the same - hippo is Fred instead of John",
+ "expected" : {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=Fred",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/query/different_param_values.py b/tests/acceptance/version_1/testcases/request/query/different_param_values.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/different_param_values.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/request/query/matches.json b/tests/acceptance/version_1/testcases/request/query/matches.json
new file mode 100755
index 0000000..79289ee
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/matches.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Queries are the same",
+ "expected" : {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/query/matches.py b/tests/acceptance/version_1/testcases/request/query/matches.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.json b/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.json
new file mode 100755
index 0000000..21a47c4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Queries are equivalent",
+ "expected" : {
+ "method": "GET",
+ "path": "/path",
+ "query": "options=delete.topic.enable=true&broker=1",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/path",
+ "query": "options=delete.topic.enable%3Dtrue&broker=1",
+ "headers": {}
+
+ }
+}
diff --git a/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.py b/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.py
new file mode 100644
index 0000000..2ef0bea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/matches_with_equals_in_the_query_value.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.json b/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.json
new file mode 100755
index 0000000..eafffb0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.json
@@ -0,0 +1,18 @@
+{
+ "match": false,
+ "comment": "Query strings are matched using basic string equality, these are not equal.",
+ "expected" : {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John",
+ "headers": {}
+
+ },
+ "actual": {
+ "method": "GET",
+ "path": "/path",
+ "query": "alligator=Mary&hippo=John&",
+ "headers": {}
+
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.py b/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.py
new file mode 100644
index 0000000..b37da56
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/request/query/trailing_ampersand.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.request import PactRequest
+from pact_test.matchers.request_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test():
+ data = load_acceptance_test(__file__)
+
+ actual = PactRequest(**data['actual'])
+ expected = PactRequest(**data['expected'])
+
+ test_result = match(actual, expected)
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/array_in_different_order.json b/tests/acceptance/version_1/testcases/response/body/array_in_different_order.json
new file mode 100755
index 0000000..ea5f3c9
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/array_in_different_order.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Favourite colours in wrong order",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["blue", "red"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/array_in_different_order.py b/tests/acceptance/version_1/testcases/response/body/array_in_different_order.py
new file mode 100644
index 0000000..a4c4774
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/array_in_different_order.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_array_in_different_order():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteColours': ['blue', 'red']
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.json b/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.json
new file mode 100755
index 0000000..47c2394
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.json
@@ -0,0 +1,42 @@
+{
+ "match": true,
+ "comment": "Comparisons should work even on nested objects",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "object1": {
+ "object2": {
+ "object4": {
+ "object5": {
+ "name": "Mary",
+ "friends": ["Fred", "John"]
+ },
+ "object6": {
+ "phoneNumber": 1234567890
+ }
+ }
+ }
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "object1":{
+ "object2": {
+ "object4":{
+ "object5": {
+ "name": "Mary",
+ "friends": ["Fred", "John"],
+ "gender": "F"
+ },
+ "object6": {
+ "phoneNumber": 1234567890
+ }
+ }
+ },
+ "color": "red"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.py b/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.py
new file mode 100644
index 0000000..0ad849c
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/deeply_nested_objects.py
@@ -0,0 +1,30 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_nested_objects():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'object1': {
+ 'object2': {
+ 'object4': {
+ 'object5': {
+ 'name': 'Mary',
+ 'friends': ['Fred', 'John'],
+ 'gender': 'F'
+ },
+ 'object6': {
+ 'phoneNumber': 1234567890
+ }
+ }
+ },
+ 'color': 'red'
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.json b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.json
new file mode 100755
index 0000000..774c5f5
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Incorrect favourite colour",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","taupe"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.py b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.py
new file mode 100644
index 0000000..1b1c44c
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_index.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteColours': ['red', 'taupe']
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.json b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.json
new file mode 100755
index 0000000..72a035d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Incorrect value at alligator name",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Fred"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.py b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.py
new file mode 100644
index 0000000..b60ede6
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/different_value_found_at_key.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_value_at_key():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'name': 'Fred'}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.json b/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.json
new file mode 100755
index 0000000..3901cbe
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.json
@@ -0,0 +1,18 @@
+{
+ "match": true,
+ "comment": "Favourite number and favourite colours out of order",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "favouriteNumber": 7,
+ "favouriteColours": ["red","blue"]
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "favouriteColours": ["red","blue"],
+ "favouriteNumber": 7
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.py b/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.py
new file mode 100644
index 0000000..d7544e0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/keys_out_of_order_match.py
@@ -0,0 +1,17 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'favouriteColours': ['red', 'blue'],
+ 'favouriteNumber': 7
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/body/matches.json b/tests/acceptance/version_1/testcases/response/body/matches.json
new file mode 100755
index 0000000..c36e8f3
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/matches.json
@@ -0,0 +1,24 @@
+{
+ "match": true,
+ "comment": "Responses match",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "feet": 4,
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4,
+ "name": "Mary",
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/matches.py b/tests/acceptance/version_1/testcases/response/body/matches.py
new file mode 100644
index 0000000..f835bec
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/matches.py
@@ -0,0 +1,23 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_matches():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'feet': 4,
+ 'name': 'Mary',
+ 'favouriteColours': [
+ 'red',
+ 'blue'
+ ]
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/body/missing_index.json b/tests/acceptance/version_1/testcases/response/body/missing_index.json
new file mode 100755
index 0000000..3c08100
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/missing_index.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Missing favorite colour",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator": {
+ "favouriteColours": ["red"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/missing_index.py b/tests/acceptance/version_1/testcases/response/body/missing_index.py
new file mode 100644
index 0000000..ee909af
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/missing_index.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_missing_index():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'favouriteColours': ['red']}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/missing_key.json b/tests/acceptance/version_1/testcases/response/body/missing_key.json
new file mode 100755
index 0000000..fbfbb7a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/missing_key.json
@@ -0,0 +1,21 @@
+{
+ "match": false,
+ "comment": "Missing key alligator name",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "age": 3
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator": {
+ "age": 3
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/missing_key.py b/tests/acceptance/version_1/testcases/response/body/missing_key.py
new file mode 100644
index 0000000..bec3c2e
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/missing_key.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_missing_key():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'age': 3}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.json b/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.json
new file mode 100755
index 0000000..54aa2a4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Name should be null",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": null
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Fred"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.py b/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.py
new file mode 100644
index 0000000..ece9b18
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/not_null_found_at_key_when_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_not_null():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'name': 'Fred'}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.json b/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.json
new file mode 100755
index 0000000..3058dc7
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Favourite numbers expected to contain null, but not null found",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",null,"3"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.py b/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.py
new file mode 100644
index 0000000..5a06d8f
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/not_null_found_in_array_when_null_expected.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'name': None
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.json b/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.json
new file mode 100755
index 0000000..46cef2a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Name should not be null",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.py b/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.py
new file mode 100644
index 0000000..77df2fd
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/null_found_at_key_where_not_null_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_not_null():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'name': None}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.json b/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.json
new file mode 100755
index 0000000..250647a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Favourite numbers expected to be strings found a null",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",null,"3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.py b/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.py
new file mode 100644
index 0000000..901b56a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/null_found_in_array_when_not_null_expected.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteNumbers': ['1', None, '3']
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.json b/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.json
new file mode 100755
index 0000000..cb47ca8
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Number of feet expected to be string but was number",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": "4"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.py b/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.py
new file mode 100644
index 0000000..d74c8d0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/number_found_at_key_when_string_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_unexpected_number():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'feet': 4}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.json b/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.json
new file mode 100755
index 0000000..daea5c7
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Favourite numbers expected to be strings found a number",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1","2","3"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": ["1",2,"3"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.py b/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.py
new file mode 100644
index 0000000..1d490ea
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/number_found_in_array_when_string_expected.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteNumbers': ['1', 2, '3']
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.json b/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.json
new file mode 100755
index 0000000..334a74e
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.json
@@ -0,0 +1,19 @@
+{
+ "match": false,
+ "comment": "Properties match but unexpected element recieved",
+ "expected" : {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "red"}
+ ]
+ },
+ "actual": {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "red",
+ "favouriteNumber": 2},
+ {"favouriteColor": "blue",
+ "favouriteNumber": 2}
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.py b/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.py
new file mode 100644
index 0000000..6a16de2
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_first_matches.py
@@ -0,0 +1,23 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body=[
+ {
+ 'favouriteColor': 'red',
+ 'favouriteNumber': 2
+ },
+ {
+ 'favouriteColor': 'blue',
+ 'favouriteNumber': 2
+ }
+ ])
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.json b/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.json
new file mode 100755
index 0000000..c5eb3b7
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Array of objects, properties match on incorrect objects",
+ "expected" : {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "red"},
+ {"favouriteNumber": 2}
+ ]
+ },
+ "actual": {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "blue",
+ "favouriteNumber": 4},
+ {"favouriteColor": "red",
+ "favouriteNumber": 2}
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.py b/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.py
new file mode 100644
index 0000000..09dd4d7
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_no_matches.py
@@ -0,0 +1,23 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body=[
+ {
+ 'favouriteColor': 'blue',
+ 'favouriteNumber': 4
+ },
+ {
+ 'favouriteColor': 'red',
+ 'favouriteNumber': 2
+ }
+ ])
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.json b/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.json
new file mode 100755
index 0000000..dc87a61
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.json
@@ -0,0 +1,19 @@
+{
+ "match": false,
+ "comment": "Property of second object matches, but unexpected element recieved",
+ "expected" : {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "red"}
+ ]
+ },
+ "actual": {
+ "headers": {},
+ "body": [
+ {"favouriteColor": "blue",
+ "favouriteNumber": 4},
+ {"favouriteColor": "red",
+ "favouriteNumber": 2}
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.py b/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.py
new file mode 100644
index 0000000..09dd4d7
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/objects_in_array_second_matches.py
@@ -0,0 +1,23 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_values():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body=[
+ {
+ 'favouriteColor': 'blue',
+ 'favouriteNumber': 4
+ },
+ {
+ 'favouriteColor': 'red',
+ 'favouriteNumber': 2
+ }
+ ])
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.json b/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.json
new file mode 100755
index 0000000..937e2f0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.json
@@ -0,0 +1,12 @@
+{
+ "match": false,
+ "comment": "Plain text that does not match",
+ "expected" : {
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ },
+ "actual": {
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named fred"
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.py b/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.py
new file mode 100644
index 0000000..8e151c9
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/plain_text_that_does_not_match.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_non_matching_plain_text():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body='alligator named fred')
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.json b/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.json
new file mode 100644
index 0000000..961f36b
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.json
@@ -0,0 +1,12 @@
+{
+ "match": true,
+ "comment": "Plain text that matches",
+ "expected" : {
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ },
+ "actual": {
+ "headers": { "Content-Type": "text/plain" },
+ "body": "alligator named mary"
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.py b/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.py
new file mode 100644
index 0000000..17c906c
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/plain_text_that_matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_matching_plain_text():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body='alligator named mary')
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.json b/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.json
new file mode 100755
index 0000000..b4ff784
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Property names on objects are case sensitive",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "FavouriteColour": "red"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouritecolour": "red"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.py b/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.py
new file mode 100644
index 0000000..822fead
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/property_name_is_different_case.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'favouritecolour': 'red'}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.json b/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.json
new file mode 100755
index 0000000..194d397
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Number of feet expected to be number but was string",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": 4
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "feet": "4"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.py b/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.py
new file mode 100644
index 0000000..df66986
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/string_found_at_key_when_number_expected.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={'alligator': {'feet': '4'}})
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.json b/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.json
new file mode 100755
index 0000000..bec0a6b
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Favourite Numbers expected to be numbers, but 2 is a string",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": [1,2,3]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteNumbers": [1,"2",3]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.py b/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.py
new file mode 100644
index 0000000..7ae8c4e
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/string_found_in_array_when_number_expected.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteNumbers': [1, '2', 3]
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.json b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.json
new file mode 100755
index 0000000..9064245
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Unexpected favourite colour",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue","taupe"]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.py b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.py
new file mode 100644
index 0000000..5f5a730
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_not_null_value.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteColours': ['red', 'blue', 'taupe']
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.json b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.json
new file mode 100755
index 0000000..fbb3b91
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.json
@@ -0,0 +1,20 @@
+{
+ "match": false,
+ "comment": "Unexpected favourite colour with null value",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue"]
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "favouriteColours": ["red","blue", null]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.py b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.py
new file mode 100644
index 0000000..1e3b679
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_index_with_null_value.py
@@ -0,0 +1,18 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'favouriteColours': ['red', 'blue', None]
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.json b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.json
new file mode 100755
index 0000000..487baa9
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.json
@@ -0,0 +1,21 @@
+{
+ "match": true,
+ "comment": "Unexpected phone number",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "phoneNumber": "12345678"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.py b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.py
new file mode 100644
index 0000000..9e81eb0
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_not_null_value.py
@@ -0,0 +1,19 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'name': 'Mary',
+ 'phoneNumber': '12345678'
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.json b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.json
new file mode 100755
index 0000000..202954c
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.json
@@ -0,0 +1,21 @@
+{
+ "match": true,
+ "comment": "Unexpected phone number with null value",
+ "expected" : {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary"
+ }
+ }
+ },
+ "actual": {
+ "headers": {},
+ "body": {
+ "alligator":{
+ "name": "Mary",
+ "phoneNumber": null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.py b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.py
new file mode 100644
index 0000000..3b936de
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/body/unexpected_key_with_null_value.py
@@ -0,0 +1,19 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(body={
+ 'alligator': {
+ 'name': 'Mary',
+ 'phoneNumber': None
+ }
+ })
+ interaction = {'response': {'body': data['expected']['body']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/headers/empty_headers.json b/tests/acceptance/version_1/testcases/response/headers/empty_headers.json
new file mode 100755
index 0000000..c10c032
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/empty_headers.json
@@ -0,0 +1,10 @@
+{
+ "match": true,
+ "comment": "Empty headers match",
+ "expected" : {
+ "headers": {}
+ },
+ "actual": {
+ "headers": {}
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/empty_headers.py b/tests/acceptance/version_1/testcases/response/headers/empty_headers.py
new file mode 100644
index 0000000..5ef0fbd
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/empty_headers.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_empty_headers():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.json b/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.json
new file mode 100755
index 0000000..2c2e50d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.json
@@ -0,0 +1,14 @@
+{
+ "match": true,
+ "comment": "Header name is case insensitive",
+ "expected" : {
+ "headers": {
+ "Accept": "alligators"
+ }
+ },
+ "actual": {
+ "headers": {
+ "ACCEPT": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.py b/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.py
new file mode 100644
index 0000000..f9651ff
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/header_name_is_different_case.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_name_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[('ACCEPT', 'alligators')])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.json b/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.json
new file mode 100755
index 0000000..1bc8d03
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.json
@@ -0,0 +1,14 @@
+{
+ "match": false,
+ "comment": "Headers values are case sensitive",
+ "expected" : {
+ "headers": {
+ "Accept": "alligators"
+ }
+ },
+ "actual": {
+ "headers": {
+ "Accept": "Alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.py b/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.py
new file mode 100644
index 0000000..b54be8f
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/header_value_is_different_case.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_value_case():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[('Accept', 'Alligators')])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/headers/headers_match.json b/tests/acceptance/version_1/testcases/response/headers/headers_match.json
new file mode 100755
index 0000000..36b4b8b
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/headers_match.json
@@ -0,0 +1,16 @@
+{
+ "match": true,
+ "comment": "Headers match",
+ "expected" : {
+ "headers": {
+ "Accept": "alligators",
+ "Content-Type": "hippos"
+ }
+ },
+ "actual": {
+ "headers": {
+ "Content-Type": "hippos",
+ "Accept": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/headers_match.py b/tests/acceptance/version_1/testcases/response/headers/headers_match.py
new file mode 100644
index 0000000..304db14
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/headers_match.py
@@ -0,0 +1,17 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_headers_match():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[
+ ('Accept', 'alligators'),
+ ('Content-Type', 'hippos')
+ ])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.json b/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.json
new file mode 100755
index 0000000..fd4edc8
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.json
@@ -0,0 +1,14 @@
+{
+ "match": false,
+ "comment": "Comma separated headers out of order, order can matter http://tools.ietf.org/html/rfc2616",
+ "expected" : {
+ "headers": {
+ "Accept": "alligators, hippos"
+ }
+ },
+ "actual": {
+ "headers": {
+ "Accept": "hippos, alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.py b/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.py
new file mode 100644
index 0000000..d0e4539
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/order_of_comma_separated_header_values_different.py
@@ -0,0 +1,14 @@
+from pact_test.either import Left
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_order_of_headers():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[('Accept', 'hippos, alligators')])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Left
diff --git a/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.json b/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.json
new file mode 100755
index 0000000..74849ef
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.json
@@ -0,0 +1,12 @@
+{
+ "match": true,
+ "comment": "Extra headers allowed",
+ "expected" : {
+ "headers": {}
+ },
+ "actual": {
+ "headers": {
+ "Accept": "alligators"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.py b/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.py
new file mode 100644
index 0000000..ce3e81d
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/unexpected_header_found.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_unexpected_header():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[('Accept', 'alligators')])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.json b/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.json
new file mode 100755
index 0000000..6f85a84
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.json
@@ -0,0 +1,14 @@
+{
+ "match": true,
+ "comment": "Whitespace between comma separated headers does not matter",
+ "expected" : {
+ "headers": {
+ "Accept": "alligators,hippos"
+ }
+ },
+ "actual": {
+ "headers": {
+ "Accept": "alligators, hippos"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.py b/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.py
new file mode 100644
index 0000000..789ed96
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/headers/whitespace_after_comma_different.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_whitespaces_after_comma():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(headers=[('Accept', 'alligators, hippos')])
+ interaction = {'response': {'headers': data['expected']['headers']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/acceptance/version_1/testcases/response/status/different_status.json b/tests/acceptance/version_1/testcases/response/status/different_status.json
new file mode 100644
index 0000000..0f978c4
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/status/different_status.json
@@ -0,0 +1,10 @@
+{
+ "match": false,
+ "comment": "Status is incorrect",
+ "expected" : {
+ "status" : 202
+ },
+ "actual" : {
+ "status" : 400
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/status/different_status.py b/tests/acceptance/version_1/testcases/response/status/different_status.py
new file mode 100644
index 0000000..ec2b60a
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/status/different_status.py
@@ -0,0 +1,19 @@
+from pact_test.constants import *
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_status():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(status=400)
+ interaction = {'response': {'status': data['expected']['status']}}
+ test_result = match(interaction, response).value
+
+ assert test_result == {
+ 'status': FAILED,
+ 'message': data['comment'],
+ 'expected': data['expected']['status'],
+ 'actual': 400
+ }
diff --git a/tests/acceptance/version_1/testcases/response/status/status_matches.json b/tests/acceptance/version_1/testcases/response/status/status_matches.json
new file mode 100644
index 0000000..a754762
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/status/status_matches.json
@@ -0,0 +1,10 @@
+{
+ "match": true,
+ "comment": "Status matches",
+ "expected": {
+ "status": 202
+ },
+ "actual": {
+ "status": 202
+ }
+}
\ No newline at end of file
diff --git a/tests/acceptance/version_1/testcases/response/status/status_matches.py b/tests/acceptance/version_1/testcases/response/status/status_matches.py
new file mode 100644
index 0000000..050402f
--- /dev/null
+++ b/tests/acceptance/version_1/testcases/response/status/status_matches.py
@@ -0,0 +1,14 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+from tests.acceptance.acceptance_test_loader import load_acceptance_test
+
+
+def test_different_status():
+ data = load_acceptance_test(__file__)
+
+ response = PactResponse(status=202)
+ interaction = {'response': {'status': data['expected']['status']}}
+ test_result = match(interaction, response)
+
+ assert type(test_result) is Right
diff --git a/tests/clients/http_client.py b/tests/clients/http_client.py
new file mode 100644
index 0000000..e599149
--- /dev/null
+++ b/tests/clients/http_client.py
@@ -0,0 +1,89 @@
+import requests
+from pact_test.clients.http_client import _build_url
+from pact_test.clients.http_client import _parse_headers
+from pact_test.clients.http_client import execute_interaction_request
+
+
+def test_http_error(mocker):
+ def boom(_, **kwargs):
+ raise Exception('Boom!')
+
+ mocker.patch.object(requests, 'request', new=boom)
+
+ interaction = {'request': {'path': '/spam/eggs'}}
+ response = execute_interaction_request('', None, interaction).value
+ assert response == 'Boom!'
+
+
+def test_execute_interaction_request(mocker):
+ class Response(object):
+ status_code = 200
+ headers = {'Content-Type': 'application/json', 'Date': '12-06-2017'}
+
+ def json(self):
+ return {'spam': 'eggs'}
+
+ mocker.patch.object(requests, 'request', lambda x, **kwargs: Response())
+ url = 'montypython.com'
+ port = None
+ interaction = {'request': {'path': '/spam/eggs'}}
+ response = execute_interaction_request(url, port, interaction).value
+
+ assert response.status == 200
+ assert len(response.headers) > 1
+ assert response.body == {'spam': 'eggs'}
+
+
+def test_execute_interaction_request_text(mocker):
+ class Response(object):
+ status_code = 200
+ headers = {'Date': '12-06-2017'}
+
+ def text(self):
+ return 'Spam & Eggs'
+
+ mocker.patch.object(requests, 'request', lambda x, **kwargs: Response())
+ url = 'montypython.com'
+ port = None
+ interaction = {'request': {'path': '/spam/eggs'}}
+ response = execute_interaction_request(url, port, interaction).value
+
+ assert response.status == 200
+ assert response.headers == [('Date', '12-06-2017')]
+
+
+def test_parse_headers():
+ headers = [('Content-Type', 'spam')]
+ server_headers = {'Content-Type': 'spam'}
+
+ class Response(object):
+ headers = server_headers
+
+ assert _parse_headers(Response()) == headers
+
+
+def test_build_url():
+ url = 'montypython.com'
+ port = None
+ interaction = {'request': {}}
+ url = _build_url(url, port, interaction)
+
+ assert url == 'http://montypython.com:80'
+
+
+def test_build_url_with_path():
+ url = 'montypython.com'
+ port = None
+ interaction = {'request': {'path': '/spam/eggs'}}
+ url = _build_url(url, port, interaction)
+
+ assert url == 'http://montypython.com:80/spam/eggs'
+
+
+def test_build_url_with_query():
+ url = 'montypython.com'
+ port = None
+ interaction = {'request': {'query': '?spam=eggs'}}
+ url = _build_url(url, port, interaction)
+
+ assert url == 'http://montypython.com:80?spam=eggs'
diff --git a/tests/config/config_builder.py b/tests/config/config_builder.py
new file mode 100644
index 0000000..19f2210
--- /dev/null
+++ b/tests/config/config_builder.py
@@ -0,0 +1,69 @@
+import os
+from pact_test.config.config_builder import Config
+
+
+def test_default_consumer_tests_path():
+ config = Config()
+ assert config.consumer_tests_path == 'tests/service_consumers'
+
+
+def test_default_provider_tests_path():
+ config = Config()
+ assert config.provider_tests_path == 'tests/service_providers'
+
+
+def test_default_pact_broker_uri():
+ config = Config()
+ assert config.pact_broker_uri == 'http://localhost:9292/'
+
+
+def test_custom_consumer_tests_path():
+ class TestConfig(Config):
+ def path_to_user_config_file(self):
+ return os.path.join(os.getcwd(), 'tests',
+ 'resources', 'config',
+ 'consumer_only.json')
+
+ config = TestConfig()
+ assert config.pact_broker_uri == 'http://localhost:9292/'
+ assert config.consumer_tests_path == 'mypath/mytests'
+ assert config.provider_tests_path == 'tests/service_providers'
+
+
+def test_custom_provider_tests_path():
+ class TestConfig(Config):
+ def path_to_user_config_file(self):
+ return os.path.join(os.getcwd(), 'tests',
+ 'resources', 'config',
+ 'provider_only.json')
+
+ config = TestConfig()
+ assert config.pact_broker_uri == 'http://localhost:9292/'
+ assert config.provider_tests_path == 'mypath/mytests'
+ assert config.consumer_tests_path == 'tests/service_consumers'
+
+
+def test_custom_pact_broker_uri():
+ class TestConfig(Config):
+ def path_to_user_config_file(self):
+ return os.path.join(os.getcwd(), 'tests',
+ 'resources', 'config',
+ 'pact_broker_only.json')
+
+ config = TestConfig()
+ assert config.pact_broker_uri == 'mypath/mytests'
+ assert config.provider_tests_path == 'tests/service_providers'
+ assert config.consumer_tests_path == 'tests/service_consumers'
+
+
+def test_user_settings():
+ class TestConfig(Config):
+ def path_to_user_config_file(self):
+ return os.path.join(os.getcwd(), 'tests',
+ 'resources', 'config',
+ '.pact.json')
+
+ config = TestConfig()
+ assert config.pact_broker_uri == 'mypath/mybroker'
+ assert config.provider_tests_path == 'mypath/myprovider'
+ assert config.consumer_tests_path == 'mypath/myconsumer'
diff --git a/tests/either/either.py b/tests/either/either.py
new file mode 100644
index 0000000..6e4e270
--- /dev/null
+++ b/tests/either/either.py
@@ -0,0 +1,52 @@
+from pact_test.either import *
+
+
+def test_left_value():
+ l = Left(42)
+ assert l.value == 42
+
+
+def test_right_value():
+ r = Right(42)
+ assert r.value == 42
+
+
+def test_concat_right():
+ out = minus_one(45) >> minus_one >> minus_one
+ assert out.value == 42
+
+
+def test_concat_left():
+ out = minus_one(1) >> one_divided_by
+ assert out.value == "Division by zero."
+
+
+def test_break_the_chain():
+ out = minus_one(1) >> one_divided_by >> minus_one
+ assert out.value == "Division by zero."
+
+
+def test_multiple_parameters():
+ out = minus_one(9).concat(my_sum, 5)
+ assert out.value == 13
+
+
+def test_multiple_parameters_left():
+ out = one_divided_by(0).concat(my_sum, 5)
+ assert out.value == "Division by zero."
+
+
+def minus_one(value):
+ return Right(value - 1)
+
+
+def one_divided_by(value):
+ try:
+ result = Right(1 / value)
+ except ZeroDivisionError:
+ result = Left("Division by zero.")
+ return result
+
+
+def my_sum(a, b):
+ return Right(a + b)
diff --git a/tests/matchers/response_matcher.py b/tests/matchers/response_matcher.py
new file mode 100644
index 0000000..fa48912
--- /dev/null
+++ b/tests/matchers/response_matcher.py
@@ -0,0 +1,65 @@
+from pact_test.either import Right
+from pact_test.models.response import PactResponse
+from pact_test.matchers.response_matcher import match
+
+
+interaction = {
+ 'response': {
+ 'status': 418,
+ 'headers': {'Content-Type': 'spam'},
+ 'body': {'spam': 'eggs'}
+ }
+}
+
+
+def valid_response():
+ return PactResponse(
+ status=418,
+ headers=[('Content-Type', 'spam')],
+ body={'spam': 'eggs'}
+ )
+
+
+def test_non_matching_status():
+ pact_response = PactResponse(status=200)
+ msg = {
+ 'status': 'FAILED',
+ 'message': 'Status is incorrect',
+ 'expected': 418,
+ 'actual': 200
+ }
+
+ assert match(interaction, pact_response).value == msg
+
+
+def test_non_matching_headers():
+ pact_response = PactResponse(status=418, headers=[('Date', '12-06-2017')])
+ msg = {
+ 'status': 'FAILED',
+ 'message': 'Headers is incorrect',
+ 'expected': {'Content-Type': 'spam'},
+ 'actual': {'Date': '12-06-2017'}
+ }
+
+ assert match(interaction, pact_response).value == msg
+
+
+def test_non_matching_body():
+ pact_response = PactResponse(
+ status=418,
+ headers=[('Content-Type', 'spam')],
+ body={'spam': 'spam'}
+ )
+ msg = {
+ 'status': 'FAILED',
+ 'message': 'Body is incorrect',
+ 'expected': {'spam': 'eggs'},
+ 'actual': {'spam': 'spam'}
+ }
+ assert match(interaction, pact_response).value == msg
+
+
+def test_matching_response():
+ pact_response = valid_response()
+
+ assert type(match(interaction, pact_response)) is Right
diff --git a/tests/models/pact_helper.py b/tests/models/pact_helper.py
new file mode 100644
index 0000000..ca3b9de
--- /dev/null
+++ b/tests/models/pact_helper.py
@@ -0,0 +1,12 @@
+from pact_test import PactHelper
+
+
+pact_helper = PactHelper()
+
+
+def test_default_url():
+ assert pact_helper.test_url == 'localhost'
+
+
+def test_default_port():
+ assert pact_helper.test_port == 9999
diff --git a/tests/models/request.py b/tests/models/request.py
new file mode 100644
index 0000000..f77a02b
--- /dev/null
+++ b/tests/models/request.py
@@ -0,0 +1,70 @@
+from pact_test.models.request import PactRequest
+
+
+def test_default_body():
+ r = PactRequest()
+ assert r.body is None
+
+
+def test_default_headers():
+ r = PactRequest()
+ assert r.headers == []
+
+
+def test_default_method():
+ r = PactRequest()
+ assert r.method == 'GET'
+
+
+def test_default_path():
+ r = PactRequest()
+ assert r.path == ''
+
+
+def test_default_query():
+ r = PactRequest()
+ assert r.query == ''
+
+
+def test_custom_query():
+ r = PactRequest(query='?type=spam')
+ assert r.query == '?type=spam'
+
+ r.query = '?type=eggs'
+ assert r.query == '?type=eggs'
+
+
+def test_custom_path():
+ r = PactRequest(path='/spam/')
+ assert r.path == '/spam/'
+
+ r.path = '/eggs/'
+ assert r.path == '/eggs/'
+
+
+def test_custom_method():
+ r = PactRequest(method='POST')
+ assert r.method == 'POST'
+
+ r.method = 'PUT'
+ assert r.method == 'PUT'
+
+
+def test_custom_headers():
+ headers = [('Content-Type', 'text')]
+ r = PactRequest(headers=headers)
+ assert r.headers == headers
+
+ headers = [('Content-Type', 'application/json')]
+ r.headers = headers
+ assert r.headers == headers
+
+
+def test_custom_body():
+ body = {'spam': 'eggs'}
+ r = PactRequest(body=body)
+ assert r.body == body
+
+ body = {'spam': 'spam'}
+ r.body = body
+ assert r.body == body
diff --git a/tests/models/response.py b/tests/models/response.py
new file mode 100644
index 0000000..c46cc8a
--- /dev/null
+++ b/tests/models/response.py
@@ -0,0 +1,44 @@
+from pact_test.models.response import PactResponse
+
+
+def test_default_body():
+ r = PactResponse()
+ assert r.body is None
+
+
+def test_default_headers():
+ r = PactResponse()
+ assert r.headers == []
+
+
+def test_default_status():
+ r = PactResponse()
+ assert r.status == 500
+
+
+def test_custom_body():
+ body = {'spam': 'eggs'}
+ r = PactResponse(body=body)
+ assert r.body == body
+
+ body = {'spam': 'spam'}
+ r.body = body
+ assert r.body == body
+
+
+def test_custom_headers():
+ headers = [('Content-Type', 'text')]
+ r = PactResponse(headers=headers)
+ assert r.headers == headers
+
+ headers = [('Content-Type', 'application/json')]
+ r.headers = headers
+ assert r.headers == headers
+
+
+def test_custom_status():
+ r = PactResponse(status=200)
+ assert r.status == 200
+
+ r.status = 201
+ assert r.status == 201
diff --git a/tests/models/service_consumer_test.py b/tests/models/service_consumer_test.py
new file mode 100644
index 0000000..03c3621
--- /dev/null
+++ b/tests/models/service_consumer_test.py
@@ -0,0 +1,79 @@
+from pact_test import state
+from pact_test import pact_uri
+from pact_test import honours_pact_with
+from pact_test import ServiceConsumerTest
+
+
+def test_default_pact_uri():
+ t = ServiceConsumerTest()
+ assert t.pact_uri is None
+
+
+def test_pact_uri_decorator():
+ @pact_uri('http://montypython.com/')
+ class MyTest(ServiceConsumerTest):
+ pass
+
+ t = MyTest()
+ assert t.pact_uri == 'http://montypython.com/'
+
+
+def test_default_honours_pact_with():
+ t = ServiceConsumerTest()
+ assert t.honours_pact_with is None
+
+
+def test_honours_pact_with_decorator():
+ @honours_pact_with('Library App')
+ class MyTest(ServiceConsumerTest):
+ pass
+
+ t = MyTest()
+ assert t.honours_pact_with == 'Library App'
+
+
+def test_decorators():
+ @honours_pact_with('Library App')
+ @pact_uri('http://montypython.com/')
+ class MyTest(ServiceConsumerTest):
+ pass
+
+ t = MyTest()
+ assert t.honours_pact_with == 'Library App'
+ assert t.pact_uri == 'http://montypython.com/'
+
+
+def test_states():
+ class MyTest(ServiceConsumerTest):
+ @state('Default state')
+ def setup(self):
+ return 42
+
+ t = MyTest()
+ default_state = next(t.states)
+
+ assert default_state is not None
+ assert default_state.state == 'Default state'
+ assert default_state() == 42
+
+
+def test_missing_pact_uri():
+ @honours_pact_with('Restaurant Customer')
+ class MyTest(ServiceConsumerTest):
+ @state('the breakfast is available')
+ def setup(self):
+ return 42
+
+ msg = 'Missing setup for "pact_uri"'
+ assert MyTest().is_valid().value.startswith(msg)
+
+
+def test_missing_honours_pact_with():
+ @pact_uri('http://montypython.com/')
+ class MyTest(ServiceConsumerTest):
+ @state('the breakfast is available')
+ def setup(self):
+ return 42
+
+ msg = 'Missing setup for "honours_pact_with"'
+ assert MyTest().is_valid().value.startswith(msg)
diff --git a/tests/models/service_provider_test.py b/tests/models/service_provider_test.py
new file mode 100644
index 0000000..67b10ce
--- /dev/null
+++ b/tests/models/service_provider_test.py
@@ -0,0 +1,108 @@
+from pact_test import given
+from pact_test import with_request
+from pact_test import has_pact_with
+from pact_test import upon_receiving
+from pact_test import service_consumer
+from pact_test import will_respond_with
+from pact_test import ServiceProviderTest
+from pact_test.either import *
+
+
+def test_default_service_consumer_value():
+ t = ServiceProviderTest()
+ assert t.service_consumer is None
+
+
+def test_decorator_service_consumer_value():
+ @service_consumer('Library App')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ t = MyTest()
+ assert t.service_consumer == 'Library App'
+
+
+def test_default_has_pact_with_value():
+ t = ServiceProviderTest()
+ assert t.has_pact_with is None
+
+
+def test_decorator_has_pact_with_value():
+ @has_pact_with('Books Service')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ t = MyTest()
+ assert t.has_pact_with == 'Books Service'
+
+
+def test_class_decorators():
+ @service_consumer('Library App')
+ @has_pact_with('Books Service')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ t = MyTest()
+ assert t.service_consumer == 'Library App'
+ assert t.has_pact_with == 'Books Service'
+
+
+def test_method_decorators():
+ class MyTest(ServiceProviderTest):
+ @given('the breakfast menu is available')
+ @upon_receiving('a request for a breakfast')
+ @with_request('I don\'t like spam')
+ @will_respond_with('Spam & Eggs')
+ def make_me_breakfast(self):
+ return 'Spam & Eggs'
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+
+ assert decorated_method is not None
+ assert decorated_method.given == 'the breakfast menu is available'
+ assert decorated_method.upon_receiving == 'a request for a breakfast'
+ assert decorated_method.with_request == 'I don\'t like spam'
+ assert decorated_method.will_respond_with == 'Spam & Eggs'
+ assert decorated_method() == 'Spam & Eggs'
+
+
+def test_missing_method_decorators():
+ class MyTest(ServiceProviderTest):
+ @given('the breakfast menu is available')
+ def make_me_breakfast(self):
+ return 'Spam & Eggs'
+
+ t = MyTest()
+ try:
+ next(t.decorated_methods)
+ assert False
+ except StopIteration:
+ assert True
+
+
+def test_missing_service_consumer():
+ @has_pact_with('Restaurant Customer')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ msg = 'Missing setup for "service_consumer"'
+ assert MyTest().is_valid().value.startswith(msg)
+
+
+def test_missing_has_pact_with():
+ @service_consumer('Restaurant Customer')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ msg = 'Missing setup for "has_pact_with"'
+ assert MyTest().is_valid().value.startswith(msg)
+
+
+def test_valid_test():
+ @service_consumer('Spam')
+ @has_pact_with('Eggs')
+ class MyTest(ServiceProviderTest):
+ pass
+
+ assert type(MyTest().is_valid()) is Right
diff --git a/tests/repositories/pact_broker.py b/tests/repositories/pact_broker.py
new file mode 100644
index 0000000..81cccbf
--- /dev/null
+++ b/tests/repositories/pact_broker.py
@@ -0,0 +1,137 @@
+import requests
+from pact_test.either import *
+from pact_test.repositories.pact_broker import upload_pact
+from pact_test.repositories.pact_broker import next_version
+from pact_test.repositories.pact_broker import format_headers
+from pact_test.repositories.pact_broker import get_latest_version
+
+
+def test_current_version_error(mocker):
+
+ class GetResponse(object):
+ status_code = 200
+
+ def json(self):
+ raise requests.exceptions.ConnectionError('Boom!')
+
+ mocker.patch.object(requests, 'put', lambda x, **kwargs:
+ PutResponse())
+ mocker.patch.object(requests, 'get', lambda x, **kwargs:
+ GetResponse())
+
+ out = upload_pact('provider', 'consumer', {})
+ assert type(out) is Left
+
+
+def test_connection_error(mocker):
+
+ class GetResponse(object):
+ status_code = 200
+
+ def json(self):
+ return {'_embedded': {'versions': [{'number': '1.0.41'}]}}
+
+ class PutResponse(object):
+ status_code = 200
+
+ def json(self):
+ raise requests.exceptions.ConnectionError('Boom!')
+
+ mocker.patch.object(requests, 'put', lambda x, **kwargs:
+ PutResponse())
+ mocker.patch.object(requests, 'get', lambda x, **kwargs:
+ GetResponse())
+
+ out = upload_pact('provider', 'consumer', {})
+ assert type(out) is Left
+
+
+def test_upload_pact(mocker):
+ class GetResponse(object):
+ status_code = 200
+
+ def json(self):
+ return {'_embedded': {'versions': [{'number': '1.0.41'}]}}
+
+ class PutResponse(object):
+ status_code = 200
+
+ def json(self):
+ return {}
+
+ mocker.patch.object(requests, 'put', lambda x, **kwargs: PutResponse())
+ mocker.patch.object(requests, 'get', lambda x, **kwargs: GetResponse())
+
+ out = upload_pact('provider', 'consumer', {})
+ assert type(out) is Right
+
+
+def test_next_version():
+ assert next_version('1.0.41') == '1.0.42'
+
+
+def test_get_latest_version(mocker):
+ class Response(object):
+ status_code = 200
+
+ def json(self):
+ return {'_embedded': {'versions': [{'number': 42}]}}
+
+ mocker.patch.object(requests, 'get', lambda x, **kwargs: Response())
+
+ latest_version = get_latest_version('eggs')
+ assert type(latest_version) is Right
+ assert latest_version.value == 42
+
+
+def test_missing_latest_version(mocker):
+ class Response(object):
+ status_code = 404
+
+ mocker.patch.object(requests, 'get', lambda x, **kwargs: Response())
+
+ latest_version = get_latest_version('eggs')
+ assert type(latest_version) is Right
+ assert latest_version.value == '1.0.0'
+
+
+def test_wrong_url():
+ latest_version = get_latest_version('eggs', base_url='http://host:9999/')
+ msg = 'Failed to establish a new connection with http://host:9999/'
+
+ assert type(latest_version) is Left
+ assert latest_version.value == msg
+
+
+def test_format_headers():
+ pact = {
+ "interactions": [
+ {
+ "request": {
+ "headers": [
+ {'spam': 'eggs'},
+ {'Content-Type': 'application/json'}
+ ]
+ },
+ "response": {
+ "headers": [
+ {'spam': 'eggs'},
+ {'Content-Type': 'application/json'}
+ ]
+ }
+ }
+ ]
+ }
+ new_pact = format_headers(pact)
+ expected_request_headers = {
+ 'spam': 'eggs',
+ 'Content-Type': 'application/json'
+ }
+ expected_response_headers = {
+ 'spam': 'eggs',
+ 'Content-Type': 'application/json'
+ }
+ request_headers = new_pact['interactions'][0]['request']['headers']
+ response_headers = new_pact['interactions'][0]['response']['headers']
+ assert request_headers == expected_request_headers
+ assert response_headers == expected_response_headers
diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/resources/config/.pact.json b/tests/resources/config/.pact.json
new file mode 100644
index 0000000..c767aa8
--- /dev/null
+++ b/tests/resources/config/.pact.json
@@ -0,0 +1,5 @@
+{
+ "consumer_tests_path": "mypath/myconsumer",
+ "provider_tests_path": "mypath/myprovider",
+ "pact_broker_uri": "mypath/mybroker"
+}
diff --git a/tests/resources/config/consumer_only.json b/tests/resources/config/consumer_only.json
new file mode 100644
index 0000000..d2c6f39
--- /dev/null
+++ b/tests/resources/config/consumer_only.json
@@ -0,0 +1,3 @@
+{
+ "consumer_tests_path": "mypath/mytests"
+}
diff --git a/tests/resources/config/pact_broker_only.json b/tests/resources/config/pact_broker_only.json
new file mode 100644
index 0000000..4ec32e1
--- /dev/null
+++ b/tests/resources/config/pact_broker_only.json
@@ -0,0 +1,3 @@
+{
+ "pact_broker_uri": "mypath/mytests"
+}
diff --git a/tests/resources/config/provider_only.json b/tests/resources/config/provider_only.json
new file mode 100644
index 0000000..c61c50a
--- /dev/null
+++ b/tests/resources/config/provider_only.json
@@ -0,0 +1,3 @@
+{
+ "provider_tests_path": "mypath/mytests"
+}
diff --git a/tests/resources/invalid_service_consumer/customer.py b/tests/resources/invalid_service_consumer/customer.py
new file mode 100644
index 0000000..cbc48f8
--- /dev/null
+++ b/tests/resources/invalid_service_consumer/customer.py
@@ -0,0 +1,9 @@
+from pact_test.models.service_consumer_test import *
+
+
+@pact_uri('http://google.com/')
+class TestRestaurantCustomer(ServiceConsumerTest):
+
+ @state('the breakfast is available')
+ def test_get_breakfast(self):
+ return 'Spam & Eggs'
diff --git a/tests/resources/invalid_service_provider/missing_service_consumer.py b/tests/resources/invalid_service_provider/missing_service_consumer.py
new file mode 100644
index 0000000..53b8e24
--- /dev/null
+++ b/tests/resources/invalid_service_provider/missing_service_consumer.py
@@ -0,0 +1,11 @@
+from pact_test.models.service_provider_test import *
+
+
+@has_pact_with('Books Service')
+class SimpleTest(ServiceProviderTest):
+ @given('a book exists')
+ @upon_receiving('a request for a book')
+ @with_request({'method': 'get', 'path': '/books/42'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ pass
diff --git a/tests/resources/pact_files/file.json b/tests/resources/pact_files/file.json
new file mode 100644
index 0000000..f0620a2
--- /dev/null
+++ b/tests/resources/pact_files/file.json
@@ -0,0 +1,3 @@
+{
+ "spam": "eggs"
+}
\ No newline at end of file
diff --git a/tests/resources/pact_files/simple.json b/tests/resources/pact_files/simple.json
new file mode 100644
index 0000000..dc49e93
--- /dev/null
+++ b/tests/resources/pact_files/simple.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Restaurant"
+ },
+ "consumer": {
+ "name": "Customer"
+ },
+ "interactions": [
+ {
+ "providerState" : "the breakfast is available",
+ "description": "a request to get a breakfast item",
+ "request": {
+ "method": "GET",
+ "path": "/breakfast/42",
+ "query": ""
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application-json/html"
+ },
+ "body": {
+ "id": 42,
+ "name": "Spam & Eggs"
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "1.0.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/resources/pact_helper/pact_helper.py b/tests/resources/pact_helper/pact_helper.py
new file mode 100644
index 0000000..06f61cd
--- /dev/null
+++ b/tests/resources/pact_helper/pact_helper.py
@@ -0,0 +1,9 @@
+from pact_test import PactHelper
+
+
+class MyPactHelper(PactHelper):
+ def setup(self):
+ pass
+
+ def tear_down(self):
+ pass
diff --git a/tests/resources/pact_helper_no_setup/pact_helper.py b/tests/resources/pact_helper_no_setup/pact_helper.py
new file mode 100644
index 0000000..545e180
--- /dev/null
+++ b/tests/resources/pact_helper_no_setup/pact_helper.py
@@ -0,0 +1,6 @@
+from pact_test import PactHelper
+
+
+class MyPactHelper(PactHelper):
+ def tear_down(self):
+ pass
diff --git a/tests/resources/pact_helper_no_super_class/pact_helper.py b/tests/resources/pact_helper_no_super_class/pact_helper.py
new file mode 100644
index 0000000..fd8a6c6
--- /dev/null
+++ b/tests/resources/pact_helper_no_super_class/pact_helper.py
@@ -0,0 +1,3 @@
+class MyPactHelper(object):
+ def setup(self):
+ pass
diff --git a/tests/resources/pact_helper_no_tear_down/pact_helper.py b/tests/resources/pact_helper_no_tear_down/pact_helper.py
new file mode 100644
index 0000000..d3089a1
--- /dev/null
+++ b/tests/resources/pact_helper_no_tear_down/pact_helper.py
@@ -0,0 +1,6 @@
+from pact_test import PactHelper
+
+
+class MyPactHelper(PactHelper):
+ def setup(self):
+ pass
diff --git a/tests/resources/service_consumers/pact_helper.py b/tests/resources/service_consumers/pact_helper.py
new file mode 100644
index 0000000..06f61cd
--- /dev/null
+++ b/tests/resources/service_consumers/pact_helper.py
@@ -0,0 +1,9 @@
+from pact_test import PactHelper
+
+
+class MyPactHelper(PactHelper):
+ def setup(self):
+ pass
+
+ def tear_down(self):
+ pass
diff --git a/tests/resources/service_consumers/test_restaurant_customer.py b/tests/resources/service_consumers/test_restaurant_customer.py
new file mode 100644
index 0000000..04a6004
--- /dev/null
+++ b/tests/resources/service_consumers/test_restaurant_customer.py
@@ -0,0 +1,10 @@
+from pact_test.models.service_consumer_test import *
+
+
+@honours_pact_with('Restaurant')
+@pact_uri('tests/resources/pact_files/simple.json')
+class TestRestaurantCustomer(ServiceConsumerTest):
+
+ @state('the breakfast is available')
+ def test_get_breakfast(self):
+ return 'Spam & Eggs'
diff --git a/tests/resources/service_consumers_bad_pact/pact_helper.py b/tests/resources/service_consumers_bad_pact/pact_helper.py
new file mode 100644
index 0000000..06f61cd
--- /dev/null
+++ b/tests/resources/service_consumers_bad_pact/pact_helper.py
@@ -0,0 +1,9 @@
+from pact_test import PactHelper
+
+
+class MyPactHelper(PactHelper):
+ def setup(self):
+ pass
+
+ def tear_down(self):
+ pass
diff --git a/tests/resources/service_consumers_bad_pact/test_restaurant_customer.py b/tests/resources/service_consumers_bad_pact/test_restaurant_customer.py
new file mode 100644
index 0000000..9911672
--- /dev/null
+++ b/tests/resources/service_consumers_bad_pact/test_restaurant_customer.py
@@ -0,0 +1,10 @@
+from pact_test.models.service_consumer_test import *
+
+
+@honours_pact_with('Restaurant')
+@pact_uri('http://google.com/')
+class TestRestaurantCustomer(ServiceConsumerTest):
+
+ @state('the breakfast is available')
+ def test_get_breakfast(self):
+ return 'Spam & Eggs'
diff --git a/tests/resources/service_providers/simple_test.py b/tests/resources/service_providers/simple_test.py
new file mode 100644
index 0000000..af720d5
--- /dev/null
+++ b/tests/resources/service_providers/simple_test.py
@@ -0,0 +1,20 @@
+from pact_test.models.service_provider_test import *
+
+
+@service_consumer('Library App')
+@has_pact_with('Books Service')
+class SimpleTest(ServiceProviderTest):
+
+ @given('a book exists')
+ @upon_receiving('a request for a book')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ pass
+
+ @given('several books exist')
+ @upon_receiving('a request for a book')
+ @with_request({'method': 'get', 'path': '/books/'})
+ @will_respond_with({'status': 200})
+ def test_get_books(self):
+ pass
diff --git a/tests/resources/service_providers/simple_test_without_requests.py b/tests/resources/service_providers/simple_test_without_requests.py
new file mode 100644
index 0000000..dd5e502
--- /dev/null
+++ b/tests/resources/service_providers/simple_test_without_requests.py
@@ -0,0 +1,13 @@
+from pact_test.models.service_provider_test import *
+
+
+@service_consumer('Library App')
+@has_pact_with('Books Service')
+class SimpleTest(ServiceProviderTest):
+
+ @given('a book exists')
+ @upon_receiving('a request for a book')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ pass
diff --git a/tests/resources/service_providers_valid/simple_test.py b/tests/resources/service_providers_valid/simple_test.py
new file mode 100644
index 0000000..23e6ae6
--- /dev/null
+++ b/tests/resources/service_providers_valid/simple_test.py
@@ -0,0 +1,14 @@
+import requests
+from pact_test.models.service_provider_test import *
+
+
+@service_consumer('Library App')
+@has_pact_with('Books Service')
+class SimpleTest(ServiceProviderTest):
+
+ @given('a book exists')
+ @upon_receiving('a request for a book')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.get('http://localhost:9999/books/42')
diff --git a/tests/resources/simple_pact.json b/tests/resources/simple_pact.json
new file mode 100644
index 0000000..d3f236a
--- /dev/null
+++ b/tests/resources/simple_pact.json
@@ -0,0 +1,38 @@
+{
+ "provider": {
+ "name": "Restaurant"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "providerState" : "a menu exists",
+ "description": "a request for an item on the menu",
+ "request": {
+ "method": "GET",
+ "path": "/menu/42",
+ "query": ""
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json",
+ "Pragma": "no-cache"
+ },
+ "body": {
+ "name": "Spam, Eggs and Bacon",
+ "ingredients": ["spam", "eggs", "bacon"]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "1.0.0"
+ },
+ "pact-jvm": {
+ "version": "1.0.0"
+ }
+ }
+}
diff --git a/tests/runners/pact_tests_runner.py b/tests/runners/pact_tests_runner.py
new file mode 100644
index 0000000..031238d
--- /dev/null
+++ b/tests/runners/pact_tests_runner.py
@@ -0,0 +1,21 @@
+from pact_test.runners import pact_tests_runner
+
+
+def test_consumer_tests(mocker):
+ mocker.spy(pact_tests_runner, 'run_consumer_tests')
+ pact_tests_runner.verify(verify_consumers=True)
+ assert pact_tests_runner.run_consumer_tests.call_count == 1
+
+
+def test_provider_tests(mocker):
+ mocker.spy(pact_tests_runner, 'run_provider_tests')
+ pact_tests_runner.verify(verify_providers=True)
+ assert pact_tests_runner.run_provider_tests.call_count == 1
+
+
+def test_default_setup(mocker):
+ mocker.spy(pact_tests_runner, 'run_consumer_tests')
+ mocker.spy(pact_tests_runner, 'run_provider_tests')
+ pact_tests_runner.verify()
+ assert pact_tests_runner.run_consumer_tests.call_count == 0
+ assert pact_tests_runner.run_provider_tests.call_count == 0
diff --git a/tests/runners/service_consumers/__init__.py b/tests/runners/service_consumers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/runners/service_consumers/state_test.py b/tests/runners/service_consumers/state_test.py
new file mode 100644
index 0000000..e232838
--- /dev/null
+++ b/tests/runners/service_consumers/state_test.py
@@ -0,0 +1,130 @@
+import requests
+from pact_test import state
+from pact_test import PactHelper
+from pact_test import ServiceConsumerTest
+from pact_test.runners.service_consumers.state_test import find_state
+from pact_test.runners.service_consumers.state_test import verify_state
+
+
+class MyPactHelper(PactHelper):
+ def setup(self):
+ pass
+
+ def tear_down(self):
+ pass
+
+
+class TestLibraryApp(ServiceConsumerTest):
+ @state('some books exist')
+ def test_get_book(self):
+ pass
+
+test_instance = TestLibraryApp()
+pact_helper = MyPactHelper()
+
+
+def test_find_state():
+ response = find_state(interaction, '', test_instance).value
+ assert type(response).__name__.endswith('method')
+
+
+def test_missing_state():
+ i = interaction.copy()
+ i['providerState'] = 'Catch me if you can'
+
+ test_instance = TestLibraryApp()
+ pact_helper = MyPactHelper()
+
+ response = verify_state(i, pact_helper, test_instance).value
+ expected_response = {
+ 'state': 'Catch me if you can',
+ 'description': 'Description',
+ 'status': 'FAILED',
+ 'errors': ['Missing state implementation for "Catch me if you can"']
+ }
+
+ assert response == expected_response
+
+
+def test_find_state_missing():
+ class BadTest(ServiceConsumerTest):
+ pass
+
+ bad_test_instance = BadTest()
+ expected_response = {
+ 'state': 'some books exist',
+ 'description': 'Description',
+ 'status': 'FAILED',
+ 'errors': [
+ 'Missing state implementation for "some books exist"'
+ ]
+ }
+ response = find_state(interaction, 'Description', bad_test_instance).value
+ assert response == expected_response
+
+
+def test_verify_state(mocker):
+ class Response(object):
+ status_code = 200
+ headers = {'Content-Type': 'application/json'}
+
+ def json(self):
+ return {
+ 'id': 42,
+ 'title': 'The Hitchhicker\'s Guide to the Galaxy'
+ }
+
+ mocker.patch.object(requests, 'request', lambda x, **kwargs: Response())
+
+ test_instance = TestLibraryApp()
+ pact_helper = MyPactHelper()
+
+ mocker.spy(pact_helper, 'setup')
+ mocker.spy(pact_helper, 'tear_down')
+
+ response = verify_state(interaction, pact_helper, test_instance).value
+ expected_response = {
+ 'state': 'some books exist',
+ 'description': 'Description',
+ 'status': 'PASSED',
+ 'errors': []
+ }
+
+ assert response == expected_response
+
+
+interaction = {
+ 'providerState': 'some books exist',
+ 'description': 'Description',
+ 'request': {
+ 'method': 'GET',
+ 'path': '',
+ 'query': '',
+ 'headers': {
+ 'Content-type': 'application/json'
+ },
+ 'body': {
+ 'title': 'The Hitchhicker\'s Guide to the Galaxy'
+ }
+ },
+ 'response': {
+ 'status': 200,
+ 'body': {
+ 'id': 42,
+ 'title': 'The Hitchhicker\'s Guide to the Galaxy'
+ },
+ 'headers': {
+ 'Content-Type': 'application/json'
+ }
+ }
+}
+
+
+class TestLibraryApp(ServiceConsumerTest):
+ @state('some books exist')
+ def test_get_book(self):
+ pass
+
+ @state('no books exist')
+ def test_no_book(self):
+ pass
diff --git a/tests/runners/service_consumers/test_suite.py b/tests/runners/service_consumers/test_suite.py
new file mode 100644
index 0000000..59131fd
--- /dev/null
+++ b/tests/runners/service_consumers/test_suite.py
@@ -0,0 +1,132 @@
+import os
+import sys
+import imp
+import requests
+from pact_test.either import Left
+from pact_test.config.config_builder import Config
+from pact_test.runners.service_consumers.test_suite import ServiceConsumerTestSuiteRunner # nopep8
+
+
+def test_verify(monkeypatch):
+ config = Config()
+ config.consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'service_consumers')
+ sct = ServiceConsumerTestSuiteRunner(config)
+
+ class Response(object):
+ status_code = 200
+ headers = {'Content-Type': 'application/json', 'Date': '12-06-2017'}
+
+ def json(self):
+ return {'spam': 'eggs'}
+
+ def connect(_, **kwargs):
+ return Response()
+ monkeypatch.setattr(requests, 'request', connect)
+
+ actual_response = sct.verify().value
+
+ assert len(actual_response) == 1
+ assert actual_response[0].value['test'] == 'TestRestaurantCustomer'
+ assert len(actual_response[0].value['results']) == 1
+
+ test_outcome = actual_response[0].value['results'][0].value
+ assert test_outcome['state'] == 'the breakfast is available'
+ assert test_outcome['status'] == 'FAILED'
+ assert len(test_outcome['errors']) == 1
+ assert test_outcome['errors'][0]['message'] == 'Headers is incorrect'
+
+
+def test_verify_bad_pact():
+ config = Config()
+ config.consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'service_consumers_bad_pact')
+ sct = ServiceConsumerTestSuiteRunner(config)
+ actual_response = sct.verify().value
+ assert type(actual_response[0]) is Left
+
+
+def test_missing_pact_helper():
+ config = Config()
+ t = ServiceConsumerTestSuiteRunner(config)
+ msg = 'Missing "pact_helper.py" at "tests/service_consumers".'
+
+ assert t.verify().value == msg
+
+
+def test_missing_setup_method():
+ remove_pact_helper()
+
+ config = Config()
+ test_pact_helper_path = os.path.join(os.getcwd(), 'tests',
+ 'resources', 'pact_helper_no_setup')
+ config.consumer_tests_path = test_pact_helper_path
+ t = ServiceConsumerTestSuiteRunner(config)
+ msg = 'Missing "setup" method in "pact_helper.py".'
+ assert t.verify().value == msg
+
+
+def test_missing_tear_down_method():
+ remove_pact_helper()
+
+ config = Config()
+ test_pact_helper_path = os.path.join(os.getcwd(), 'tests', 'resources',
+ 'pact_helper_no_tear_down')
+ config.consumer_tests_path = test_pact_helper_path
+ t = ServiceConsumerTestSuiteRunner(config)
+ msg = 'Missing "tear_down" method in "pact_helper.py".'
+ assert t.verify().value == msg
+
+
+def test_empty_tests_list(monkeypatch):
+ config = Config()
+ test_pact_helper_path = os.path.join(os.getcwd(), 'tests', 'resources',
+ 'service_consumers')
+ config.consumer_tests_path = test_pact_helper_path
+
+ def empty_list(_):
+ return []
+ monkeypatch.setattr(os, 'listdir', empty_list)
+
+ t = ServiceConsumerTestSuiteRunner(config)
+ msg = 'There are no consumer tests to verify.'
+ assert t.collect_tests().value == msg
+
+
+def test_collect_tests():
+ config = Config()
+ test_pact_helper_path = os.path.join(os.getcwd(), 'tests', 'resources',
+ 'service_consumers')
+ config.consumer_tests_path = test_pact_helper_path
+ t = ServiceConsumerTestSuiteRunner(config)
+
+ tests = t.collect_tests().value
+ assert len(tests) == 1
+
+ test = tests[0]
+ assert test.pact_uri == 'tests/resources/pact_files/simple.json'
+ assert test.honours_pact_with == 'Restaurant'
+
+ state = next(test.states)
+ assert state.state == 'the breakfast is available'
+ assert state() == 'Spam & Eggs'
+
+
+def test_invalid_test():
+ path = os.path.join(os.getcwd(), 'tests', 'resources',
+ 'invalid_service_consumer', 'customer.py')
+ module = imp.load_source('invalid_test', path)
+ test = module.TestRestaurantCustomer()
+
+ t = ServiceConsumerTestSuiteRunner(None)
+ msg = 'Missing setup for "honours_pact_with"'
+ assert t.verify_test(test).value.startswith(msg)
+
+
+def remove_pact_helper():
+ try:
+ del sys.modules['pact_helper']
+ except KeyError:
+ pass
diff --git a/tests/runners/service_providers/__init__.py b/tests/runners/service_providers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/runners/service_providers/request_test.py b/tests/runners/service_providers/request_test.py
new file mode 100644
index 0000000..cb4ce3c
--- /dev/null
+++ b/tests/runners/service_providers/request_test.py
@@ -0,0 +1,156 @@
+import requests
+from pact_test.models.service_provider_test import *
+from pact_test.runners.service_providers.request_test import verify_request
+
+
+def test_missing_request():
+ port = 9998
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ pass
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+
+ assert type(test_result) is Left
+ assert test_result.value['status'] == 'FAILED'
+
+
+def test_non_matching_http_method():
+ port = 9997
+ url = 'http://localhost:' + str(port) + '/books/42/'
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.post(url, data='{}')
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+ expected_error_message = {
+ 'actual': 'POST',
+ 'expected': 'GET',
+ 'message': 'Method is incorrect',
+ 'status': 'FAILED',
+ 'description': 'eggs',
+ 'providerState': 'spam'
+ }
+
+ assert type(test_result) is Left
+ assert test_result.value == expected_error_message
+
+
+def test_non_matching_path():
+ port = 9996
+ url = 'http://localhost:' + str(port) + '/books/4242/'
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'get', 'path': '/books/42/'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.get(url)
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+ expected_error_message = {
+ 'actual': '/books/4242/',
+ 'expected': '/books/42/',
+ 'message': 'Path is incorrect',
+ 'status': 'FAILED',
+ 'description': 'eggs',
+ 'providerState': 'spam'
+ }
+
+ assert type(test_result) is Left
+ assert test_result.value == expected_error_message
+
+
+def test_non_matching_query():
+ port = 9995
+ url = 'http://localhost:' + str(port) + '/q?spam=eggs'
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'get', 'path': '/q', 'query': '?eggs=bacon'})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.get(url)
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+ expected_error_message = {
+ 'actual': '?spam=eggs',
+ 'expected': '?eggs=bacon',
+ 'message': 'Query is incorrect',
+ 'status': 'FAILED',
+ 'description': 'eggs',
+ 'providerState': 'spam'
+ }
+
+ assert type(test_result) is Left
+ assert test_result.value == expected_error_message
+
+
+def test_non_matching_headers():
+ port = 9994
+ url = 'http://localhost:' + str(port) + '/'
+ headers = [{'Content-Type': 'application/json'}]
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'get', 'path': '/', 'headers': headers})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.get(url, headers={'spam': 'eggs'})
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+
+ assert type(test_result) is Left
+ assert test_result.value['status'] == 'FAILED'
+
+
+def test_non_matching_body():
+ port = 9993
+ url = 'http://localhost:' + str(port) + '/'
+ body = '{"spam": "eggs"}'
+
+ class MyTest(ServiceProviderTest):
+ @given('spam')
+ @upon_receiving('eggs')
+ @with_request({'method': 'post', 'path': '/', 'body': body})
+ @will_respond_with({'status': 200})
+ def test_get_book(self):
+ requests.post(url, data='{"eggs": "bacon"}')
+
+ t = MyTest()
+ decorated_method = next(t.decorated_methods)
+ test_result = verify_request(decorated_method, port)
+ expected_error_message = {
+ 'actual': {'eggs': 'bacon'},
+ 'expected': '{"spam": "eggs"}',
+ 'message': 'Body is incorrect',
+ 'status': 'FAILED',
+ 'description': 'eggs',
+ 'providerState': 'spam'
+ }
+
+ assert type(test_result) is Left
+ assert test_result.value == expected_error_message
diff --git a/tests/runners/service_providers/test_suite.py b/tests/runners/service_providers/test_suite.py
new file mode 100644
index 0000000..cd07270
--- /dev/null
+++ b/tests/runners/service_providers/test_suite.py
@@ -0,0 +1,39 @@
+import os
+from pact_test.either import *
+from pact_test.config.config_builder import Config
+from pact_test.runners.service_providers.test_suite import ServiceProviderTestSuiteRunner # nopep8
+
+
+def test_empty_tests_list(monkeypatch):
+ config = Config()
+ config.provider_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'service_providers')
+
+ def empty_list(_):
+ return []
+ monkeypatch.setattr(os, 'listdir', empty_list)
+
+ t = ServiceProviderTestSuiteRunner(config)
+ msg = 'There are no provider tests to verify.'
+ assert t.collect_tests().value == msg
+
+
+def test_collect_tests():
+ config = Config()
+ config.provider_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'service_providers')
+ t = ServiceProviderTestSuiteRunner(config)
+
+ tests = t.collect_tests().value
+ assert len(tests) == 2
+
+
+def test_missing_test_directory():
+ config = Config()
+ config.provider_tests_path = os.path.join(os.getcwd(), 'spam')
+ t = ServiceProviderTestSuiteRunner(config)
+ result = t.verify()
+ assert type(result) is Left
+ assert result.value.startswith("[Errno 2] No such file or directory:")
diff --git a/tests/servers/mock_server.py b/tests/servers/mock_server.py
new file mode 100644
index 0000000..5c1af2e
--- /dev/null
+++ b/tests/servers/mock_server.py
@@ -0,0 +1,68 @@
+import requests
+from pact_test.models.response import PactResponse
+from pact_test.servers.mock_server import MockServer
+
+
+def test_response_with_headers():
+ r = PactResponse(headers=[{'spam': 'eggs'}])
+ s = MockServer(port=1234, mock_response=r)
+ s.start()
+ response = requests.get('http://localhost:1234/')
+ s.shutdown()
+ stored_request = s.report()[0]
+ custom_header = False
+ for h in response.headers:
+ if 'spam' in h:
+ custom_header = True
+ assert custom_header is True
+
+
+def test_no_requests():
+ s = MockServer()
+ s.start()
+ s.shutdown()
+ assert s.report() == []
+
+
+def test_get_request():
+ s = MockServer(port=1235)
+ s.start()
+ requests.get('http://localhost:1235/')
+ s.shutdown()
+ stored_request = s.report()[0]
+ assert stored_request['method'] == 'GET'
+ assert stored_request['path'] == '/'
+ assert stored_request['body'] is None
+
+
+def test_post_request():
+ s = MockServer(port=1236)
+ s.start()
+ requests.post('http://localhost:1236/', data='{"spam": "eggs"}')
+ s.shutdown()
+ stored_request = s.report()[0]
+ assert stored_request['method'] == 'POST'
+ assert stored_request['path'] == '/'
+ assert stored_request['body'] == {'spam': 'eggs'}
+
+
+def test_put_request():
+ s = MockServer(port=1237)
+ s.start()
+ requests.put('http://localhost:1237/', data='{"spam": "eggs"}')
+ s.shutdown()
+ stored_request = s.report()[0]
+ assert stored_request['method'] == 'PUT'
+ assert stored_request['path'] == '/'
+ assert stored_request['body'] == {'spam': 'eggs'}
+
+
+def test_delete_request():
+ s = MockServer(port=1238)
+ s.start()
+ requests.delete('http://localhost:1238/', data='{"spam": "eggs"}')
+ s.shutdown()
+ stored_request = s.report()[0]
+ assert stored_request['method'] == 'DELETE'
+ assert stored_request['path'] == '/'
+ assert stored_request['body'] == {'spam': 'eggs'}
diff --git a/tests/test_books_service.py b/tests/test_books_service.py
deleted file mode 100644
index fc4b6ee..0000000
--- a/tests/test_books_service.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from pytest_pact.PyPact import *
-
-
-@base_uri('localhost:1234')
-@service_consumer('Library App')
-@has_pact_with('Books Service')
-class TestBooksService():
-
- expected_response = {
- 'status': 200,
- 'headers': {'Content-Type': 'application/json'},
- 'body': {
- 'id': '123',
- 'title': 'A Fortune-Teller Told Me'
- }
- }
-
- @given('some books exist')
- @upon_receiving('a request for a book')
- @with_request({'method': 'get', 'path': '/books/123'})
- @will_respond_with(expected_response)
- def test_get_book(self):
- pass
diff --git a/tests/test_pypact.py b/tests/test_pypact.py
deleted file mode 100644
index 5046f60..0000000
--- a/tests/test_pypact.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from pytest_pact.PyPact import *
-
-
-def test_read_existing_marker():
- expected_marker = 'some books exist'
- class FakeMarker(object):
- args = [expected_marker]
- class FakePyFuncItem(object):
- def get_marker(self, marker_name):
- return FakeMarker()
- pyfuncitem = FakePyFuncItem()
- marker_name = 'given'
- assert read_marker(pyfuncitem, marker_name) == expected_marker
-
-
-def test_read_non_existing_marker():
- class FakePyFuncItem(object):
- def get_marker(self, marker_name):
- return None
- pyfuncitem = FakePyFuncItem()
- marker_name = 'given'
- assert read_marker(pyfuncitem, marker_name) == ''
diff --git a/tests/utils/http_utils.py b/tests/utils/http_utils.py
new file mode 100644
index 0000000..b1aa699
--- /dev/null
+++ b/tests/utils/http_utils.py
@@ -0,0 +1,118 @@
+from pact_test.utils.http_utils import build_request_from_interaction
+from pact_test.utils.http_utils import build_response_from_interaction
+
+
+def test_response_status():
+ interaction = {'response': {'status': 200}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.status == 200
+
+
+def test_response_missing_status():
+ interaction = {'response': {}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.status == 500
+
+
+def test_response_body():
+ body = {'spam': 'eggs'}
+ interaction = {'response': {'body': body}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.body == body
+
+
+def test_response_missing_body():
+ interaction = {'response': {}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.body is None
+
+
+def test_response_headers():
+ headers = {'Content-Type': 'spam'}
+ interaction = {'response': {'headers': headers}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.headers == [('Content-Type', 'spam')]
+
+
+def test_response_missing_headers():
+ interaction = {'response': {}}
+ response = build_response_from_interaction(interaction)
+
+ assert response.headers == []
+
+
+def test_request_method():
+ interaction = {'request': {'method': 'POST'}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.method == 'POST'
+
+
+def test_request_missing_method():
+ interaction = {'request': {}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.method == 'GET'
+
+
+def test_request_path():
+ interaction = {'request': {'path': '/spam'}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.path == '/spam'
+
+
+def test_request_missing_path():
+ interaction = {'request': {}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.path == ''
+
+
+def test_request_query():
+ interaction = {'request': {'query': '?spam=eggs'}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.query == '?spam=eggs'
+
+
+def test_request_missing_query():
+ interaction = {'request': {}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.query == ''
+
+
+def test_request_headers():
+ headers = {'Content-Type': 'application/json'}
+ interaction = {'request': {'headers': headers}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.headers == [('Content-Type', 'application/json')]
+
+
+def test_request_missing_headers():
+ interaction = {'request': {}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.headers == []
+
+
+def test_request_body():
+ body = {'spam': 'eggs'}
+ interaction = {'request': {'body': body}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.body == body
+
+
+def test_request_missing_body():
+ interaction = {'request': {}}
+ request = build_request_from_interaction(interaction)
+
+ assert request.body is None
diff --git a/tests/utils/pact_helper_utils.py b/tests/utils/pact_helper_utils.py
new file mode 100644
index 0000000..3ed2cd5
--- /dev/null
+++ b/tests/utils/pact_helper_utils.py
@@ -0,0 +1,64 @@
+import os
+from pact_test import PactHelper
+from pact_test.utils.pact_helper_utils import load_pact_helper
+
+
+def test_path_to_pact_helper(monkeypatch):
+ monkeypatch.setattr(os.path, 'isfile', lambda _: True)
+
+ consumer_tests_path = '/consumer_tests/'
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ msg = 'Missing "pact_helper.py" at "/consumer_tests/pact_helper.py".'
+ assert pact_helper == msg
+
+
+def test_path_to_pact_helper_missing():
+ consumer_tests_path = '/consumer_tests/'
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ msg = 'Missing "pact_helper.py" at "/consumer_tests/".'
+ assert pact_helper == msg
+
+
+def test_load_module_missing():
+ consumer_tests_path = '/consumer_tests/'
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ assert pact_helper == 'Missing "pact_helper.py" at "/consumer_tests/".'
+
+
+def test_load_user_class_missing_setup():
+ consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources', 'pact_helper_no_setup')
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ error_message = 'Missing "setup" method in "pact_helper.py".'
+ assert pact_helper == error_message
+
+
+def test_load_user_class_missing_tear_down():
+ consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'pact_helper_no_tear_down')
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ error_message = 'Missing "tear_down" method in "pact_helper.py".'
+ assert pact_helper == error_message
+
+
+def test_load_pact_helper():
+ consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources', 'pact_helper')
+ pact_helper = load_pact_helper(consumer_tests_path).value
+ assert issubclass(pact_helper.__class__, PactHelper)
+
+
+def test_not_extending_pact_helper():
+ consumer_tests_path = os.path.join(os.getcwd(), 'tests',
+ 'resources',
+ 'pact_helper_no_super_class')
+ pact_helper = load_pact_helper(consumer_tests_path).value
+
+ error_message = 'Pact Helper class must extend pact_test.PactHelper'
+ assert pact_helper == error_message
diff --git a/tests/utils/pact_utils.py b/tests/utils/pact_utils.py
new file mode 100644
index 0000000..59e1861
--- /dev/null
+++ b/tests/utils/pact_utils.py
@@ -0,0 +1,38 @@
+import os
+import requests
+from pact_test.either import Left
+from pact_test.utils.pact_utils import get_pact
+
+
+def test_get_pact_from_url(mocker):
+ class FakeResponse(object):
+ def json(self):
+ return {'spam': 'eggs'}
+
+ mocker.patch.object(requests, 'get')
+ requests.get.return_value = FakeResponse()
+
+ url = 'http://montyphyton.com/'
+ url_content = {'spam': 'eggs'}
+ assert get_pact(url).value == url_content
+
+
+def test_get_pact_from_url_with_errors(mocker):
+ def bad_url(_):
+ raise Exception('Boom!')
+ mocker.patch.object(requests, 'get', new=bad_url)
+
+ assert get_pact('http://montyphyton.com/').value == 'Boom!'
+
+
+def test_get_pact_from_file():
+ filename = os.path.join(os.getcwd(), 'tests', 'resources',
+ 'pact_files', 'file.json')
+ file_content = {'spam': 'eggs'}
+
+ assert get_pact(filename).value == file_content
+
+
+def test_get_pact_from_file_with_errors():
+ filename = os.path.join(os.getcwd())
+ assert type(get_pact(filename)) is Left