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