Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pact throws exception when calling stop_service() and says interaction has already been used #933

Open
3 of 4 tasks
alspires opened this issue Jan 16, 2025 · 4 comments
Open
3 of 4 tasks

Comments

@alspires
Copy link

Have you read the Contributing Guidelines on issues?

Prerequisites

  • I'm using the latest version of pact-python.
  • I have read the console error message carefully (if applicable).

Description

Hello,

I've been suddenly getting the message below after trying to re-run a test that had previously passed.

An interaction with same description ("a request for creating flow 1") and provider state ("Flow 1 is created") but a different response status and response body has already been used. Please use a different description or provider state, or remove any random data in the interaction.

Also, when the Pact file is generated, it contains all the previous validated flows accumulated.

{
  "consumer": {
    "name": "Consumer"
  },
  "provider": {
    "name": "Provider"
  },
  "interactions": [
    {
      "description": "a request for getting flow 2873923847",
      "providerState": "Flow bob is created",
      "request": {
        "method": "post",
        "path": "/api/v1/flows/"
      },
      "response": {
        "status": 200,
        "headers": {
        }
      }
    },
    {
      "description": "a request for creating flow 2873923847",
      "providerState": "Flow 2873923847 is created",
      "request": {
        "method": "post",
        "path": "/api/v1/flows/"
      },
      "response": {
        "status": 200,
        "headers": {
        },
        "body": {
          "id": "bob",
          "user_id": "92382382"
        }
      }
    },
    {
      "description": "a request for creating flow 1",
      "providerState": "Flow 1 is created",
      "request": {
        "method": "post",
        "path": "/api/v1/flows/"
      },
      "response": {
        "status": 200,
        "headers": {
        }
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "2.0.0"
    }
  }
}

Based on previous issues raised in this repo, I saw that this issue could be happening because Pact's mock service isn't properly being stopped after the test finishes running. So I looked in the debug console and found this exception in the stack:

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

    def stop_service(self):
        """Stop the external Mock Service."""
        is_windows = 'windows' in platform.platform().lower()
        if is_windows:
            # Send the signal to ruby.exe, not the *.bat process
            p = psutil.Process(self._process.pid)
            for child in p.children(recursive=True):
                child.terminate()
            p.wait()
            if psutil.pid_exists(self._process.pid):
                raise RuntimeError(
                    'There was an error when stopping the Pact mock service.')
    
        else:
            self._process.terminate()
    
            self._process.communicate()
            if self._process.returncode != 0:
>               raise RuntimeError(
                    'There was an error when stopping the Pact mock service.'
                )
E               RuntimeError: There was an error when stopping the Pact mock service.

.venv/lib/python3.12/site-packages/pact/pact.py:248: RuntimeError

This is my pact config:

@pytest.fixture(scope="module")
def pact() -> Generator[Pact, None, None]:
    pact = Consumer("Consumer").has_pact_with(
        Provider("Provider"),
        pact_dir=Path(Path(__file__).parent / "contract/pacts"),
        # publish_to_broker=True,
        # Mock service configuration
        # host_name=MOCK_URL.host,
        # port=MOCK_URL.port,
        # Broker configuration
        # broker_base_url=str(broker),
        # broker_username=broker.user,
        # broker_password=broker.password,
    )
    
    pact.start_service()
    yield pact
    pact.stop_service()

And this is my test:

def test_create_flow_contract(pact: Pact) -> None:
    headers = None
    flow = {"id": "1", "user_id": "92382382"}

    (
        pact.given("Flow 1 is created")
        .upon_receiving("a request for creating flow 1")
        .with_request("post", "/api/v1/flows/")
        .will_respond_with(200, body=flow)
    )

    with pact:
        response = requests.post(f"{pact.uri}/api/v1/flows/", json=flow, headers=headers)
        assert response.status_code == 200

Then I had a look at the log file to see if there were any more detailed information about this exception, but found nothing. I'll attach the file to this issue. I don't see the service running in any port in VSCode's ports tab, so at this point I'm not even sure if the service is starting properly.

pact-mock-service.log

Reproducible demo

No response

Steps to reproduce

  1. Create a new pact test
  2. Make a successful run so the Pact is stored and the file is generated
  3. Re-run the same test
  4. Check if the service is being properly terminated
  5. Check if it fails for already having used the interaction

Expected behavior

Pact should properly close the mock service after calling stop_service().

Actual behavior

Pact throws RuntimeException when trying to stop the mock service and accumulates pacts in the generated pact file.

Your environment

  • This is a consumer issue
  • Pact Python version used: pact-python>=2.3.0
  • No Pact Broker is being used at the moment, file is being stored locally
  • Windows 11 Enterprise v23H2 - *running project on Ubuntu in a WSL virtual environment:

Distributor ID: Ubuntu
Description: Ubuntu 24.04.1 LTS
Release: 24.04
Codename: noble

Self-service

  • I'd be willing to fix this bug myself.
@alspires
Copy link
Author

Just as a heads up, I've restarted my WSL distro and it's working again, but still, I don't understand why the issue started happening in the first place and I'm afraid it could happen when I deploy these tests to our pipelines.

@JP-Ellis
Copy link
Contributor

I wonder whether this is an issue with the local files perhaps? That is, Pact Python is reading the existing pacts, and trying to add to them when generating the consumer test. This might explain why it worked initially, then stopped, then worked again (especially if the files are stored in a temporary directory).

@alspires
Copy link
Author

I wonder whether this is an issue with the local files perhaps? That is, Pact Python is reading the existing pacts, and trying to add to them when generating the consumer test. This might explain why it worked initially, then stopped, then worked again (especially if the files are stored in a temporary directory).

To be honest, I'm not sure. If you mean the local json file Pact generates, I was deleting it before running the test. I also forgot to mention that the file_write_mode was set to merge before on my config file. I removed this but it made no difference at the time.

@JP-Ellis
Copy link
Contributor

Yeah, that is what I was referring to. When you use file_write_mode, it is meant to merge the results of the latest run with what is already there.

It is not completely clear to me why it thinks it has a different response, as they all response with a 200 and no defined body. As a result, I'm wondering whether the lack of body definition is causing an issue within the underlying CLI. Maybe give that a go?

While I investigate this a bit, have you considered using the pact.v3 module and see if you have the same issue? It is a complete rewrite and using the core library written in Rust (and shared across a number of Pact projects).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants