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

Provider State Parameters Missing in URL When Service is Both Provider and Consumer #1820

Open
somnath157 opened this issue Aug 22, 2024 · 2 comments
Labels
bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer

Comments

@somnath157
Copy link

When running the mvn test command, the verification test for a provider fails because the URL does not have the path variables loaded from the provider state. This issue occurs when the service acts as both a provider and a consumer for different APIs.

Software versions

Java : 17
Spring boot: 3.2.1
au.com.dius.pact.consumer.junit5: 4.6.9
au.com.dius.pact.provider.spring6: 4.6.9

Expected behaviour

The provider verification test should pass, with the URL including the path variables loaded from the provider state.

Actual behaviour

The provider verification test fails because the URL does not include the path variables from the provider state.

Steps to reproduce

  1. Ensure that the BeneficiaryAPIContractVerificationTest and DocumentServiceApiContractTest classes are correctly set up as shown in the provided code.
  2. Run mvn test to execute the test cases.
  3. Observe that while the consumer test (DocumentServiceApiContractTest) runs successfully, the provider verification test (BeneficiaryAPIContractVerificationTest) fails.

Additional Information:

This issue occurs specifically when both tests are in the same package.
If the tests are in different packages, the issue occurs intermittently depending on the order in which the test classes are executed.

Code Samples:
`package com.statrys.bankaccount.api.contract;

import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslJsonArray;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.V4Pact;
import au.com.dius.pact.core.model.annotations.Pact;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.statrys.bankaccount.api.client.dto.DocumentInfoDTO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;

@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "document-service")
class DocumentServiceApiContractTest {
private static final UUID DOCUMENT_CODE = UUID.fromString("fe7edc38-83fd-4ae0-818c-83b5d0b9845f");
private static final UUID DOCUMENT_CODE_NOT_EXISTS = UUID.fromString("b470fdb7-0544-468e-9b7b-c3069f95f45d");
private static final String EMPTY_DOCUMENT_CODE = "";
private static final String DOCUMENT_URL = "https://www.google.com/";
private static final Map<String, String> RES_HEADERS = Map.of("Content-Type", "application/json");
private static final RestTemplate REST_TEMPLATE = new RestTemplate();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

@BeforeEach
public void setUp() {
    System.setProperty("pact.writer.overwrite", "true");
    System.setProperty("pact.expressions.start", "{");
}

@Pact(consumer = "bank-account-service")
public V4Pact getDocumentList(PactDslWithProvider builder) {
    return builder
            .given("document codes exist")
            .uponReceiving("Request to get document details internally")
            .path("/internal/v1/documents")
            .queryParameterFromProviderState("codes", "codes", DOCUMENT_CODE.toString())
            .method("GET")
            .willRespondWith()
            .status(200)
            .headers(RES_HEADERS)
            .body(new PactDslJsonArray()
                    .object()
                    .uuid("code", DOCUMENT_CODE)
                    .stringType("documentUrl", DOCUMENT_URL)
                    .closeObject()
            )
            .given("document codes not exist")
            .uponReceiving("Request to get document details internally")
            .path("/internal/v1/documents")
            .queryParameterFromProviderState("codes", "codes", DOCUMENT_CODE_NOT_EXISTS.toString())
            .method("GET")
            .willRespondWith()
            .status(200)
            .headers(RES_HEADERS)
            .body(new PactDslJsonArray())
            .given("document codes missing")
            .uponReceiving("Request to get document details internally")
            .path("/internal/v1/documents")
            .queryParameterFromProviderState("codes", "codes", EMPTY_DOCUMENT_CODE.toString())
            .method("GET")
            .willRespondWith()
            .status(422)
            .body(new PactDslJsonBody()
                    .stringType("code")
                    .stringType("message")
                    .stringMatcher("raisedAt", "\\d{13}", "1714983789660")
            )
            .toPact(V4Pact.class);
}

@Test
@PactTestFor(pactMethod = "getDocumentList")
void shouldGetDocumentList(MockServer mockServer) throws JsonProcessingException {
    var baseURL = mockServer.getUrl();
    ResponseEntity<String> responseEntityPresent = REST_TEMPLATE.getForEntity(
            baseURL + "/internal/v1/documents?codes=" + DOCUMENT_CODE,
            String.class);
    assertThat(responseEntityPresent.getStatusCode()).isEqualTo(HttpStatus.OK);
    var body = responseEntityPresent.getBody();
    var documents = OBJECT_MAPPER.readValue(body, new TypeReference<List<DocumentInfoDTO>>() {
    });
    assertThat(documents).hasSize(1);
    assertThat(documents.get(0).getCode()).isEqualTo(DOCUMENT_CODE);
    assertThat(documents.get(0).getDocumentUrl()).isEqualTo(DOCUMENT_URL);
    try {
        REST_TEMPLATE.getForEntity(
                baseURL + "/internal/v1/documents?codes=" + EMPTY_DOCUMENT_CODE,
                String.class);
        fail("should have thrown exception");
    } catch (HttpClientErrorException ex) {
        assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY);
    }
    ResponseEntity<String> responseEntityEmpty = REST_TEMPLATE.getForEntity(
            baseURL + "/internal/v1/documents?codes=" + DOCUMENT_CODE_NOT_EXISTS,
            String.class);
    assertThat(responseEntityEmpty.getStatusCode()).isEqualTo(HttpStatus.OK);
    var emptyBody = responseEntityEmpty.getBody();
    var documentsList = OBJECT_MAPPER.readValue(emptyBody, new TypeReference<List<DocumentInfoDTO>>() {
    });
    assertThat(documentsList).isEmpty();
}

}`

`package com.statrys.bankaccount.api.contract;

import au.com.dius.pact.provider.junit5.PactVerificationContext;
import au.com.dius.pact.provider.junitsupport.Provider;
import au.com.dius.pact.provider.junitsupport.State;
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth;
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerConsumerVersionSelectors;
import au.com.dius.pact.provider.junitsupport.loader.SelectorBuilder;
import au.com.dius.pact.provider.spring.spring6.PactVerificationSpring6Provider;
import au.com.dius.pact.provider.spring.spring6.Spring6MockMvcTestTarget;
import com.statrys.bankaccount.api.contract.mocks.BeneficiaryMocks;
import com.statrys.bankaccount.api.controller.BeneficiaryApiController;
import com.statrys.bankaccount.configuration.ContractConfig;
import com.statrys.bankaccount.service.BeneficiaryService;
import com.statrys.bankaccount.type.BankServiceMessage;
import com.statrys.common.exception.ServiceException;
import com.statrys.common.web.handler.CustomResponseEntityExceptionHandler;
import com.statrys.testing.contract.config.ContractVerificationConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.MessageSource;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

@SpringBootTest(classes = ContractConfig.class, properties = {"aws.paramstore.enabled=false", "aws.secretsmanager.enabled=false"})
@ExtendWith(MockitoExtension.class)
@Provider("bank-account-service")
@PactBroker(url = "${pact.broker.url}", authentication = @PactBrokerAuth(username = "${pact.broker.username}", password = "${pact.broker.password}"))
public class BeneficiaryAPIContractVerificationTest {

private final BeneficiaryMocks mocks = new BeneficiaryMocks();
@Mock
private BeneficiaryService beneficiaryService;
@InjectMocks
private BeneficiaryApiController controller;
@Mock
private MessageSource messageSource;

private CustomResponseEntityExceptionHandler customResponseEntityExceptionHandler;

@PactBrokerConsumerVersionSelectors
public static SelectorBuilder consumerVersionSelectors() {
    ContractVerificationConfig.setUpEnvironment();
    return ContractVerificationConfig.getSelector();
}

@TestTemplate
@ExtendWith(PactVerificationSpring6Provider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
    context.verifyInteraction();
}

@BeforeEach
void before(PactVerificationContext context) {
    customResponseEntityExceptionHandler = new CustomResponseEntityExceptionHandler(messageSource);
    Spring6MockMvcTestTarget testTarget = new Spring6MockMvcTestTarget();
    testTarget.setControllers(controller);
    testTarget.setControllerAdvices(customResponseEntityExceptionHandler);
    testTarget.setPrintRequestResponse(false);
    context.setTarget(testTarget);
}

@State("beneficiary is present")
public Map<String, Object> shouldReturnBeneficiaryDetails() {
    var params = new HashMap<String, Object>();
    String clientCode = UUID.randomUUID().toString();
    String beneficiaryCode = UUID.randomUUID().toString();

    params.put("client-code", clientCode);
    params.put("beneficiary-code", beneficiaryCode);
    when(beneficiaryService.getBeneficiaryWithComplianceDetails(any(), any(), any(), any())).thenReturn(mocks.getBeneficiaryWithComplianceDetailDTO());
    return params;
}

@State("beneficiary is not present")
public Map<String, Object> shouldThrowExceptionWhenBeneficiaryIsNotPresent() {
    var params = new HashMap<String, Object>();
    String clientCode = UUID.randomUUID().toString();
    String beneficiaryCode = UUID.randomUUID().toString();

    params.put("client-code", clientCode);
    params.put("beneficiary-code", beneficiaryCode);
    when(messageSource.getMessage(any(), any(), any())).thenAnswer(invocation -> invocation.getArgument(0));
    doThrow(new ServiceException(BankServiceMessage.INVALID_BENEFICIARY_CODE)).when(beneficiaryService).getBeneficiaryWithComplianceDetails(any(), any(), any(), any());
    return params;
}

}
`

Logs:

2024-08-22 16:39:34.735 INFO --- [main] [] [] [] a.c.d.p.p.j.PactVerificationStateChangeExtension$Companion : Invoking state change method 'beneficiary is not present':SETUP

Verifying a pact between transfer-service (1065) and bank-account-service

Notices:
1) The pact at https://contract.test.dev.statrys.com/pacts/provider/bank-account-service/consumer/transfer-service/pact-version/abbcad97a4f0640fc52899af62b9851a43d0837d is being verified because the pact content belongs to the consumer version matching the following criterion:
* latest version tagged 'dev' (1065)

[from Pact Broker https://contract.test.dev.statrys.com/pacts/provider/bank-account-service/consumer/transfer-service/pact-version/abbcad97a4f0640fc52899af62b9851a43d0837d/metadata/c1tdW3RdPWRldiZzW11bbF09dHJ1ZSZzW11bY3ZdPTg0Ng]
Given beneficiary is not present
Request to get beneficiary details internally
2024-08-22 16:39:34.985 INFO --- [main] [] [] [] o.s.mock.web.MockServletContext : Initializing Spring TestDispatcherServlet ''
2024-08-22 16:39:34.985 INFO --- [main] [] [] [] o.s.t.w.s.TestDispatcherServlet : Initializing Servlet ''
2024-08-22 16:39:34.985 INFO --- [main] [] [] [] o.s.t.w.s.TestDispatcherServlet : Completed initialization in 0 ms
2024-08-22 16:39:34.995 WARN --- [main] [] [] [] o.s.web.servlet.PageNotFound : No mapping for GET /internal/v1/clients/beneficiaries/
2024-08-22 16:39:34.999 ERROR --- [main] [] [] [] c.s.c.w.h.CustomResponseEntityExceptionHandler : Un categorised error. Message [No endpoint GET /internal/v1/clients/beneficiaries/.]. See service log file for detail stacktrace
2024-08-22 16:39:34.999 INFO --- [main] [] [] [] c.s.c.w.h.CustomResponseEntityExceptionHandler : Stacktrace of un-categorised error
org.springframework.web.servlet.NoHandlerFoundException: No endpoint GET /internal/v1/clients/beneficiaries/.
at org.springframework.web.servlet.DispatcherServlet.noHandlerFound(DispatcherServlet.java:1304)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)

@somnath157 somnath157 added bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer labels Aug 22, 2024
@YOU54F
Copy link
Member

YOU54F commented Aug 22, 2024

Moving to pact-jvm (this repo is pact-js for javascript based projects).

@YOU54F YOU54F transferred this issue from pact-foundation/pact-js Aug 22, 2024
@somnath157
Copy link
Author

somnath157 commented Aug 26, 2024

I've identified the cause of the issue. The problem was that in the consumer test case, I was setting the property pact.expressions.start to "{" which persisted into the provider verification phase, causing conflicts when resolving dynamic variables. The solution I implemented involves clearing these properties after the consumer test is executed.

@BeforeEach
    public void setUp() {
        System.setProperty("pact.writer.overwrite", "true");
        System.setProperty("pact.expressions.start", "{");
    }
@AfterAll
    public static void cleanUp() {
        System.clearProperty("pact.writer.overwrite");
        System.clearProperty("pact.expressions.start");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer
Projects
None yet
Development

No branches or pull requests

2 participants