pact-foundation / pact-python

Python version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
http://pact.io
MIT License
577 stars 137 forks source link

EachLike throwing Ruby traceback when entering the pact context manager #293

Closed joshua-badger closed 2 years ago

joshua-badger commented 2 years ago

I am writing a contract test, but using EachLike gives me what appears to be a ruby traceback. I'm not certain what is causing it.

Traceback from my pytest run. This is the only failure, and other instances of using EachLike work as expected. This failure occurs whether running with or without pytest-xdist enabled.

============================= test session starts ==============================
platform linux -- Python 3.8.2, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /usr/bin/python3
cachedir: .pytest_cache
metadata: {'Python': '3.8.2', 'Platform': 'Linux-5.10.76-linuxkit-x86_64-with-glibc2.27', 'Packages': {'pytest': '6.2.5', 'py': '1.11.0', 'pluggy': '1.0.0'}, 'Plugins': {'xdist': '2.5.0', 'Faker': '13.3.2', 'html': '3.1.1', 'metadata': '1.11.0', 'celery': '4.4.7', 'django-constance': '2.8.0', 'asyncio': '0.11.0', 'django': '4.5.2', 'anyio': '3.5.0', 'forked': '1.4.0', 'cov': '2.12.1', 'hypothesis': '5.49.0'}, 'JAVA_HOME': '/usr/lib/jvm/java-8-openjdk-amd64/', 'GIT_COMMIT': 'myhashnotyours'}
django: settings: my_app.settings.test (from env)
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/my_app_root/.hypothesis/examples')
rootdir: /my_app_root, configfile: pytest.ini
plugins: xdist-2.5.0, Faker-13.3.2, html-3.1.1, metadata-1.11.0, celery-4.4.7, django-constance-2.8.0, asyncio-0.11.0, django-4.5.2,  anyio-3.5.0, forked-1.4.0, cov-2.12.1, hypothesis-5.49.0

...

=================================== FAILURES ===================================
________________________ test_get_data_from_an_endpoint ________________________
[gw1] linux -- Python 3.8.2 /usr/bin/python3

the_pact = <pact.pact.Pact object at 0x7f23633570d0>
api_obj = <my_app.external_services.other_app.api.OtherAppAPI object at 0x7f2362e7b850>
auth_token = 'Test'

    @pytest.mark.contract
    @pytest.mark.asyncio
    async def test_get_data_from_an_endpoint(the_pact, api_obj, auth_token):
        start_date = datetime.date.today()
        end_date = start_date + datetime.timedelta(days=1)
        relative_endpoint = f"/relative/endpoint/{start_date}/{end_date}"

        expected_response = EachLike(
            {"some": "data", "to": "return", "from": ["the", "endpoint"]},
        )

        the_pact.given(
            "A certain provider state"
        ).upon_receiving(
            "a request for data from an endpoint"
        ).with_request(
            "GET", relative_endpoint
        ).will_respond_with(
            200, expected_response
        )

>       with the_pact:

my_app/external_services/other_app/tests/test_api_obj.py:207: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.8/dist-packages/pact/pact.py:365: in __enter__
    self.setup()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pact.pact.Pact object at 0x7f23633570d0>

    def setup(self):
        """Configure the Mock Service to ready it for a test."""
        try:
            # First, check that the interactions are all complete
            for interaction in self._interactions:
                missing_fields = [f for f in self.MANDATORY_FIELDS if f not in interaction]
                if missing_fields:
                    raise PactException(f"Interaction incomplete, missing field(s): {', '.join(missing_fields)}")

            interactions_uri = f"{self.uri}/interactions"
            resp = requests.delete(
                interactions_uri, headers=self.HEADERS, verify=False
            )

            assert resp.status_code == 200, resp.text
            resp = requests.put(
                interactions_uri,
                headers=self.HEADERS,
                verify=False,
                json={"interactions": self._interactions},
            )

>           assert resp.status_code == 200, resp.text
E           AssertionError: {"message":"Error ocurred in mock service: NoMethodError - undefined method `each_pair' for #<Pact::ArrayLike:0x00555853dac248>","backtrace":["/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/headers.rb:9:in `initialize'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/response.rb:42:in `new'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/response.rb:42:in `from_hash'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/interaction_v2_parser.rb:46:in `parse_response'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/interaction_v2_parser.rb:16:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/interaction_parser.rb:16:in `parse_v2_interaction'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/interaction_parser.rb:10:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-support-1.17.0/lib/pact/consumer_contract/interaction.rb:22:in `from_hash'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/mock_service/request_handlers/interactions_put.rb:28:in `block in respond'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/mock_service/request_handlers/interactions_put.rb:28:in `collect'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/mock_service/request_handlers/interactions_put.rb:28:in `respond'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/mock_service/request_handlers/base_request_handler.rb:17:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/rack-2.1.4/lib/rack/cascade.rb:35:in `block in call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/rack-2.1.4/lib/rack/cascade.rb:26:in `each'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/rack-2.1.4/lib/rack/cascade.rb:26:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/consumer/mock_service/cors_origin_header_middleware.rb:11:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/consumer/mock_service/error_handler.rb:13:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/mock_service/app.rb:34:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/pact-mock_service-3.9.0/lib/pact/consumer/mock_service/set_location.rb:14:in `call'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/rack-2.1.4/lib/rack/handler/webrick.rb:88:in `service'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:138:in `service'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:94:in `run'","/usr/local/lib/python3.8/dist-packages/pact/bin/pact/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/server.rb:191:in `block in start_thread'"]}

According to poetry:

pact-python = [
    {file = "pact-python-1.5.1.tar.gz", hash = "sha256:fdbe241b5383a1a79c823b32c50e84c9db1839da843e1ff210e48f446eba135f"},
]
joshua-badger commented 2 years ago

I've also verified this occurs in version 1.4.5, which is the version I started developing with.

joshua-badger commented 2 years ago

False alarm, I forgot the body kwarg on will_respond_with, and so it assumed my expected_response was a dictionary of headers.