From 5f866d78da6d009db16a72fd6cf5826bd7888847 Mon Sep 17 00:00:00 2001 From: Anatolii Karlov Date: Tue, 24 Sep 2024 21:31:44 +0700 Subject: [PATCH] increase test coverage (#13) --- .../dev/vality/disputes/dao/DisputeDao.java | 8 - .../ManualParsingDisputesService.java | 15 +- ...voicePaymentAdjustmentParamsConverter.java | 2 +- .../schedule/service/AdjustmentExtractor.java | 9 +- .../service/CreateAdjustmentsService.java | 2 +- .../external/impl/InvoicingServiceImpl.java | 4 +- .../api/DisputesApiDelegateServiceTest.java | 183 ++++++++++++++++++ .../auth/JwtTokenTestConfiguration.java | 45 +++++ .../disputes/auth/utils/JwtTokenBuilder.java | 65 +++++++ .../config/EmbeddedPostgresWithFlyway.java | 1 - ...beddedPostgresWithFlywayConfiguration.java | 2 +- .../config/WireMockSpringBootITest.java | 27 +++ .../disputes/config/WiremockServerPort.java | 13 ++ .../vality/disputes/dao/DisputeDaoTest.java | 23 +++ .../vality/disputes/dao/FileMetaDaoTest.java | 28 +++ .../disputes/dao/ProviderDisputeDaoTest.java | 23 +++ ...eddedPostgresWithFlywayDisputeDaoTest.java | 2 + ...thZonkyEmbeddedPostgresDisputeDaoTest.java | 2 + .../DebugManualParsingController.java | 0 .../DebugManualParsingControllerTest.java | 1 - .../DebugManualParsingHandlerTest.java | 139 +++++++++++++ .../service/CreateAdjustmentsServiceTest.java | 107 ++++++++++ .../service/CreatedDisputesServiceTest.java | 163 ++++++++++++++++ .../service/PendingDisputesServiceTest.java | 88 +++++++++ .../config/CreatedDisputesTestService.java | 72 +++++++ .../service/config/DisputeApiTestConfig.java | 25 +++ .../service/config/DisputeApiTestService.java | 69 +++++++ .../config/PendingDisputesTestService.java | 44 +++++ .../config/RemoteClientTestConfig.java | 16 ++ .../config/WiremockAddressesHolder.java | 20 ++ .../dev/vality/disputes/util/DamselUtil.java | 33 ++++ .../dev/vality/disputes/util/MockUtil.java | 169 ++++++++++++++++ .../dev/vality/disputes/util/OpenApiUtil.java | 103 ++++++++++ .../vality/disputes/util/TestUrlPaths.java | 14 ++ .../vality/disputes/util/WiremockUtils.java | 27 +++ 35 files changed, 1516 insertions(+), 28 deletions(-) create mode 100644 src/test/java/dev/vality/disputes/api/DisputesApiDelegateServiceTest.java create mode 100644 src/test/java/dev/vality/disputes/auth/JwtTokenTestConfiguration.java create mode 100644 src/test/java/dev/vality/disputes/auth/utils/JwtTokenBuilder.java rename src/test/java/dev/vality/disputes/config/{testconfiguration => }/EmbeddedPostgresWithFlywayConfiguration.java (91%) create mode 100644 src/test/java/dev/vality/disputes/config/WireMockSpringBootITest.java create mode 100644 src/test/java/dev/vality/disputes/config/WiremockServerPort.java create mode 100644 src/test/java/dev/vality/disputes/dao/FileMetaDaoTest.java create mode 100644 src/test/java/dev/vality/disputes/dao/ProviderDisputeDaoTest.java rename src/{main => test}/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java (100%) create mode 100644 src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/CreateAdjustmentsServiceTest.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestConfig.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestService.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/PendingDisputesTestService.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/RemoteClientTestConfig.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/WiremockAddressesHolder.java create mode 100644 src/test/java/dev/vality/disputes/util/DamselUtil.java create mode 100644 src/test/java/dev/vality/disputes/util/MockUtil.java create mode 100644 src/test/java/dev/vality/disputes/util/OpenApiUtil.java create mode 100644 src/test/java/dev/vality/disputes/util/TestUrlPaths.java create mode 100644 src/test/java/dev/vality/disputes/util/WiremockUtils.java diff --git a/src/main/java/dev/vality/disputes/dao/DisputeDao.java b/src/main/java/dev/vality/disputes/dao/DisputeDao.java index a693fc8..a1bb2d0 100644 --- a/src/main/java/dev/vality/disputes/dao/DisputeDao.java +++ b/src/main/java/dev/vality/disputes/dao/DisputeDao.java @@ -69,14 +69,6 @@ public class DisputeDao extends AbstractGenericDao { return Optional.ofNullable(fetchOne(query, disputeRowMapper)); } - public Optional getForUpdateSkipLocked(long disputeId) { - var query = getDslContext().selectFrom(DISPUTE) - .where(DISPUTE.ID.eq(disputeId)) - .forUpdate() - .skipLocked(); - return Optional.ofNullable(fetchOne(query, disputeRowMapper)); - } - public List getDisputesForUpdateSkipLocked(int limit, DisputeStatus disputeStatus) { var query = getDslContext().selectFrom(DISPUTE) .where(DISPUTE.STATUS.eq(disputeStatus) diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java index f2912f2..2b1ba62 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java +++ b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java @@ -42,12 +42,11 @@ public class ManualParsingDisputesService { public void cancelPendingDispute(CancelParams cancelParams) { var disputeId = cancelParams.getDisputeId(); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); - if (disputeOptional.isEmpty()) { + var dispute = disputeDao.getDisputeForUpdateSkipLocked(Long.parseLong(disputeId)); + if (dispute == null) { return; } var cancelReason = cancelParams.getCancelReason().orElse(null); - var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); if (DISPUTE_PENDING.contains(dispute.getStatus())) { // используется не failed, а cancelled чтоб можно было понять, что зафейлен по внешнему вызову @@ -63,11 +62,10 @@ public class ManualParsingDisputesService { public void approvePendingDispute(ApproveParams approveParam) { var disputeId = approveParam.getDisputeId(); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); - if (disputeOptional.isEmpty()) { + var dispute = disputeDao.getDisputeForUpdateSkipLocked(Long.parseLong(disputeId)); + if (dispute == null) { return; } - var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); var skipCallHg = approveParam.isSkipCallHgForCreateAdjustment(); var targetStatus = skipCallHg ? DisputeStatus.succeeded : DisputeStatus.create_adjustment; @@ -92,12 +90,11 @@ public class ManualParsingDisputesService { public void bindCreatedDispute(BindParams bindParam) { var disputeId = bindParam.getDisputeId(); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); - if (disputeOptional.isEmpty()) { + var dispute = disputeDao.getDisputeForUpdateSkipLocked(Long.parseLong(disputeId)); + if (dispute == null) { return; } var providerDisputeId = bindParam.getProviderDisputeId(); - var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); if (dispute.getStatus() == DisputeStatus.manual_created) { // обрабатываем здесь только вручную созданные диспуты, у остальных предполагается, diff --git a/src/main/java/dev/vality/disputes/schedule/converter/InvoicePaymentAdjustmentParamsConverter.java b/src/main/java/dev/vality/disputes/schedule/converter/InvoicePaymentAdjustmentParamsConverter.java index cdaed5e..965863d 100644 --- a/src/main/java/dev/vality/disputes/schedule/converter/InvoicePaymentAdjustmentParamsConverter.java +++ b/src/main/java/dev/vality/disputes/schedule/converter/InvoicePaymentAdjustmentParamsConverter.java @@ -28,7 +28,7 @@ public class InvoicePaymentAdjustmentParamsConverter { return params; } - private String getReason(Dispute dispute) { + public String getReason(Dispute dispute) { return Optional.ofNullable(dispute.getReason()) .map(s -> String.format(DISPUTE_MASK + ", reason=%s", dispute.getId(), s)) .orElse(String.format(DISPUTE_MASK, dispute.getId())); diff --git a/src/main/java/dev/vality/disputes/schedule/service/AdjustmentExtractor.java b/src/main/java/dev/vality/disputes/schedule/service/AdjustmentExtractor.java index bee9edd..e791ef5 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/AdjustmentExtractor.java +++ b/src/main/java/dev/vality/disputes/schedule/service/AdjustmentExtractor.java @@ -6,6 +6,7 @@ import dev.vality.damsel.domain.InvoicePaymentStatus; import dev.vality.damsel.payment_processing.InvoicePayment; import dev.vality.disputes.domain.tables.pojos.Dispute; import jakarta.annotation.Nonnull; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.List; @@ -21,14 +22,14 @@ public class AdjustmentExtractor { public Optional searchAdjustmentByDispute(InvoicePayment invoicePayment, Dispute dispute) { return getInvoicePaymentAdjustmentStream(invoicePayment) .filter(adj -> adj.getReason() != null) - .filter(adj -> isDisputesAdjustment(adj, dispute)) + .filter(adj -> isDisputesAdjustment(adj.getReason(), dispute)) .findFirst() .or(() -> getInvoicePaymentAdjustmentStream(invoicePayment) .filter(s -> s.getState() != null) .filter(s -> s.getState().isSetStatusChange()) .filter(s -> getTargetStatus(s).isSetCaptured()) .filter(s -> getTargetStatus(s).getCaptured().getReason() != null) - .filter(s -> isDisputesAdjustment(s, dispute)) + .filter(s -> isDisputesAdjustment(getTargetStatus(s).getCaptured().getReason(), dispute)) .findFirst()); } @@ -48,7 +49,7 @@ public class AdjustmentExtractor { return s.getState().getStatusChange().getScenario().getTargetStatus(); } - private boolean isDisputesAdjustment(InvoicePaymentAdjustment adj, Dispute dispute) { - return adj.getReason().contains(String.format(DISPUTE_MASK, dispute.getId())); + private boolean isDisputesAdjustment(String reason, Dispute dispute) { + return !StringUtils.isBlank(reason) && reason.contains(String.format(DISPUTE_MASK, dispute.getId())); } } diff --git a/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java b/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java index ab9a1ff..6a90661 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java @@ -72,7 +72,7 @@ public class CreateAdjustmentsService { return; } } catch (InvoicingPaymentStatusPendingException e) { - // в теории 0%, что сюда попдает выполнение кода, но если попадет, то: + // в теории 0%, что сюда попадает выполнение кода, но если попадет, то: // платеж с не финальным статусом будет заблочен для создания корректировок на стороне хелгейта // и тогда диспут будет пулиться, пока платеж не зафиналится, // и тк никакой записи в коде выше нет, то пуллинг не проблема diff --git a/src/main/java/dev/vality/disputes/service/external/impl/InvoicingServiceImpl.java b/src/main/java/dev/vality/disputes/service/external/impl/InvoicingServiceImpl.java index 88b40c0..1f13ad3 100644 --- a/src/main/java/dev/vality/disputes/service/external/impl/InvoicingServiceImpl.java +++ b/src/main/java/dev/vality/disputes/service/external/impl/InvoicingServiceImpl.java @@ -55,9 +55,9 @@ public class InvoicingServiceImpl implements InvoicingService { InvoicePaymentAdjustmentParams params) { try { log.debug("createPaymentAdjustment with id: {}", invoiceId); - var invoice = invoicingClient.createPaymentAdjustment(invoiceId, paymentId, params); + var invoicePaymentAdjustment = invoicingClient.createPaymentAdjustment(invoiceId, paymentId, params); log.debug("Done createPaymentAdjustment with id: {}", invoiceId); - return invoice; + return invoicePaymentAdjustment; } catch (InvoiceNotFound | InvoicePaymentNotFound e) { // закрываем диспут с фейлом если получили не преодолимый отказ внешних шлюзов с ключевыми данными return null; diff --git a/src/test/java/dev/vality/disputes/api/DisputesApiDelegateServiceTest.java b/src/test/java/dev/vality/disputes/api/DisputesApiDelegateServiceTest.java new file mode 100644 index 0000000..a35b72e --- /dev/null +++ b/src/test/java/dev/vality/disputes/api/DisputesApiDelegateServiceTest.java @@ -0,0 +1,183 @@ +package dev.vality.disputes.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.vality.bouncer.decisions.ArbiterSrv; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.auth.utils.JwtTokenBuilder; +import dev.vality.disputes.config.WireMockSpringBootITest; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.config.WiremockAddressesHolder; +import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService; +import dev.vality.disputes.util.MockUtil; +import dev.vality.disputes.util.OpenApiUtil; +import dev.vality.disputes.util.WiremockUtils; +import dev.vality.file.storage.FileStorageSrv; +import dev.vality.swag.disputes.model.Create200Response; +import dev.vality.token.keeper.TokenAuthenticatorSrv; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static dev.vality.disputes.util.MockUtil.*; +import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WireMockSpringBootITest +@SuppressWarnings({"ParameterName", "LineLength"}) +@Import(WiremockAddressesHolder.class) +public class DisputesApiDelegateServiceTest { + + @MockBean + private InvoicingSrv.Iface invoicingClient; + @MockBean + private TokenAuthenticatorSrv.Iface tokenKeeperClient; + @MockBean + private ArbiterSrv.Iface bouncerClient; + @MockBean + private DominantAsyncService dominantAsyncService; + @MockBean + private FileStorageSrv.Iface fileStorageClient; + @Autowired + private MockMvc mvc; + @Autowired + private JwtTokenBuilder tokenBuilder; + @Autowired + private DisputeDao disputeDao; + @Autowired + private WiremockAddressesHolder wiremockAddressesHolder; + private AutoCloseable mocks; + private Object[] preparedMocks; + + @BeforeEach + public void init() { + mocks = MockitoAnnotations.openMocks(this); + preparedMocks = new Object[]{invoicingClient, tokenKeeperClient, bouncerClient, + fileStorageClient, dominantAsyncService}; + } + + @AfterEach + public void clean() throws Exception { + verifyNoMoreInteractions(preparedMocks); + mocks.close(); + } + + @Test + @SneakyThrows + void testFullApiFlowSuccess() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + when(invoicingClient.get(any(), any())) + .thenReturn(MockUtil.createInvoice(invoiceId, paymentId)); + when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData()); + when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed()); + when(dominantAsyncService.getTerminal(any())).thenReturn(createTerminal()); + when(dominantAsyncService.getCurrency(any())).thenReturn(createCurrency()); + when(fileStorageClient.createNewFile(any(), any())).thenReturn(createNewFileResult(wiremockAddressesHolder.getUploadUrl())); + WiremockUtils.mockS3AttachmentUpload(); + var resultActions = mvc.perform(post("/disputes/create") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(OpenApiUtil.getContentCreateRequest(invoiceId, paymentId))) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.disputeId").isNotEmpty()); + var response = new ObjectMapper().readValue(resultActions.andReturn().getResponse().getContentAsString(), Create200Response.class); + verify(invoicingClient, times(1)).get(any(), any()); + verify(tokenKeeperClient, times(1)).authenticate(any(), any()); + verify(bouncerClient, times(1)).judge(any(), any()); + verify(dominantAsyncService, times(1)).getTerminal(any()); + verify(dominantAsyncService, times(1)).getCurrency(any()); + verify(fileStorageClient, times(1)).createNewFile(any(), any()); + mvc.perform(get("/disputes/status") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .params(OpenApiUtil.getStatusRequiredParams(response.getDisputeId(), invoiceId, paymentId)) + .contentType(MediaType.APPLICATION_JSON) + .content("")) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.status").isNotEmpty()); + verify(invoicingClient, times(2)).get(any(), any()); + verify(tokenKeeperClient, times(2)).authenticate(any(), any()); + verify(bouncerClient, times(2)).judge(any(), any()); + // exist + resultActions = mvc.perform(post("/disputes/create") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(OpenApiUtil.getContentCreateRequest(invoiceId, paymentId))) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.disputeId").isNotEmpty()); + assertEquals(response.getDisputeId(), new ObjectMapper().readValue(resultActions.andReturn().getResponse().getContentAsString(), Create200Response.class).getDisputeId()); + verify(invoicingClient, times(3)).get(any(), any()); + verify(tokenKeeperClient, times(3)).authenticate(any(), any()); + verify(bouncerClient, times(3)).judge(any(), any()); + disputeDao.update(Long.parseLong(response.getDisputeId()), DisputeStatus.failed); + // new after failed + when(fileStorageClient.createNewFile(any(), any())).thenReturn(createNewFileResult(wiremockAddressesHolder.getUploadUrl())); + resultActions = mvc.perform(post("/disputes/create") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(OpenApiUtil.getContentCreateRequest(invoiceId, paymentId))) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.disputeId").isNotEmpty()); + assertNotEquals(response.getDisputeId(), new ObjectMapper().readValue(resultActions.andReturn().getResponse().getContentAsString(), Create200Response.class).getDisputeId()); + verify(invoicingClient, times(4)).get(any(), any()); + verify(tokenKeeperClient, times(4)).authenticate(any(), any()); + verify(bouncerClient, times(4)).judge(any(), any()); + verify(dominantAsyncService, times(2)).getTerminal(any()); + verify(dominantAsyncService, times(2)).getCurrency(any()); + verify(fileStorageClient, times(2)).createNewFile(any(), any()); + disputeDao.update(Long.parseLong(response.getDisputeId()), DisputeStatus.failed); + } + + @Test + @SneakyThrows + void testBadRequestWhenInvalidCreateRequest() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + mvc.perform(post("/disputes/create") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(OpenApiUtil.getContentInvalidCreateRequest(paymentId))) + .andExpect(status().is4xxClientError()); + } + + @Test + @SneakyThrows + void testNotFoundWhenUnknownDisputeId() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = String.valueOf(Long.MAX_VALUE); + when(invoicingClient.get(any(), any())) + .thenReturn(MockUtil.createInvoice(invoiceId, paymentId)); + when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData()); + when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed()); + mvc.perform(get("/disputes/status") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .params(OpenApiUtil.getStatusRequiredParams(disputeId, invoiceId, paymentId)) + .contentType(MediaType.APPLICATION_JSON) + .content("")) + .andExpect(status().is4xxClientError()); + verify(invoicingClient, times(1)).get(any(), any()); + verify(tokenKeeperClient, times(1)).authenticate(any(), any()); + verify(bouncerClient, times(1)).judge(any(), any()); + } +} diff --git a/src/test/java/dev/vality/disputes/auth/JwtTokenTestConfiguration.java b/src/test/java/dev/vality/disputes/auth/JwtTokenTestConfiguration.java new file mode 100644 index 0000000..5dc289a --- /dev/null +++ b/src/test/java/dev/vality/disputes/auth/JwtTokenTestConfiguration.java @@ -0,0 +1,45 @@ +package dev.vality.disputes.auth; + +import dev.vality.disputes.auth.utils.JwtTokenBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Properties; + +@Configuration +public class JwtTokenTestConfiguration { + + @Bean + public static PropertySourcesPlaceholderConfigurer properties(KeyPair keyPair) + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + KeyFactory fact = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec spec = fact.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + String publicKey = Base64.getEncoder().encodeToString(spec.getEncoded()); + PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); + Properties properties = new Properties(); + properties.load(new ClassPathResource("application.yml").getInputStream()); + properties.setProperty("keycloak.realm-public-key", publicKey); + pspc.setProperties(properties); + pspc.setLocalOverride(true); + return pspc; + } + + @Bean + public JwtTokenBuilder jwtTokenBuilder(KeyPair keyPair) { + return new JwtTokenBuilder(keyPair.getPrivate()); + } + + @Bean + public KeyPair keyPair() throws GeneralSecurityException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + return keyGen.generateKeyPair(); + } +} diff --git a/src/test/java/dev/vality/disputes/auth/utils/JwtTokenBuilder.java b/src/test/java/dev/vality/disputes/auth/utils/JwtTokenBuilder.java new file mode 100644 index 0000000..5dd851e --- /dev/null +++ b/src/test/java/dev/vality/disputes/auth/utils/JwtTokenBuilder.java @@ -0,0 +1,65 @@ +package dev.vality.disputes.auth.utils; + +import io.jsonwebtoken.Jwts; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.security.PrivateKey; +import java.time.Instant; +import java.util.UUID; + +public class JwtTokenBuilder { + + private static final String DEFAULT_USERNAME = "Darth Vader"; + private static final String DEFAULT_EMAIL = "darkside-the-best@mail.com"; + + private final String userId; + private final String username; + private final String email; + private final PrivateKey privateKey; + + public JwtTokenBuilder(PrivateKey privateKey) { + this(UUID.randomUUID().toString(), DEFAULT_USERNAME, DEFAULT_EMAIL, privateKey); + } + + public JwtTokenBuilder(String userId, String username, String email, PrivateKey privateKey) { + this.userId = userId; + this.username = username; + this.email = email; + this.privateKey = privateKey; + } + + public String generateJwtWithRoles(String... roles) { + long iat = Instant.now().getEpochSecond(); + long exp = iat + 60 * 10; + return generateJwtWithRoles(iat, exp, roles); + } + + public String generateJwtWithRoles(long iat, long exp, String... roles) { + String payload; + try { + payload = new JSONObject() + .put("jti", UUID.randomUUID().toString()) + .put("exp", exp) + .put("nbf", "0") + .put("iat", iat) + .put("aud", "private-api") + .put("sub", userId) + .put("typ", "Bearer") + .put("azp", "private-api") + .put("resource_access", new JSONObject() + .put("common-api", new JSONObject() + .put("roles", new JSONArray(roles)))) + .put("preferred_username", username) + .put("email", email).toString(); + } catch (JSONException e) { + throw new RuntimeException(e); + } + + return Jwts.builder() + .content(payload) + .signWith(privateKey, Jwts.SIG.RS256) + .compact(); + } +} diff --git a/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlyway.java b/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlyway.java index 19a26f9..c907778 100644 --- a/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlyway.java +++ b/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlyway.java @@ -1,6 +1,5 @@ package dev.vality.disputes.config; -import dev.vality.disputes.config.testconfiguration.EmbeddedPostgresWithFlywayConfiguration; import org.springframework.context.annotation.Import; import java.lang.annotation.ElementType; diff --git a/src/test/java/dev/vality/disputes/config/testconfiguration/EmbeddedPostgresWithFlywayConfiguration.java b/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlywayConfiguration.java similarity index 91% rename from src/test/java/dev/vality/disputes/config/testconfiguration/EmbeddedPostgresWithFlywayConfiguration.java rename to src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlywayConfiguration.java index 69f42b9..3c7b2ac 100644 --- a/src/test/java/dev/vality/disputes/config/testconfiguration/EmbeddedPostgresWithFlywayConfiguration.java +++ b/src/test/java/dev/vality/disputes/config/EmbeddedPostgresWithFlywayConfiguration.java @@ -1,4 +1,4 @@ -package dev.vality.disputes.config.testconfiguration; +package dev.vality.disputes.config; import io.zonky.test.db.postgres.embedded.FlywayPreparer; import io.zonky.test.db.postgres.embedded.PreparedDbProvider; diff --git a/src/test/java/dev/vality/disputes/config/WireMockSpringBootITest.java b/src/test/java/dev/vality/disputes/config/WireMockSpringBootITest.java new file mode 100644 index 0000000..237f23b --- /dev/null +++ b/src/test/java/dev/vality/disputes/config/WireMockSpringBootITest.java @@ -0,0 +1,27 @@ +package dev.vality.disputes.config; + +import dev.vality.disputes.DisputesApiApplication; +import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@DisableScheduling +@PostgresqlTestcontainerSingleton +@AutoConfigureMockMvc +@AutoConfigureWireMock(port = 0) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = DisputesApiApplication.class, + properties = { + "wiremock.server.baseUrl=http://localhost:${wiremock.server.port}", + "logging.level.WireMock=WARN"}) +public @interface WireMockSpringBootITest { +} diff --git a/src/test/java/dev/vality/disputes/config/WiremockServerPort.java b/src/test/java/dev/vality/disputes/config/WiremockServerPort.java new file mode 100644 index 0000000..26a14f4 --- /dev/null +++ b/src/test/java/dev/vality/disputes/config/WiremockServerPort.java @@ -0,0 +1,13 @@ +package dev.vality.disputes.config; + +import org.springframework.beans.factory.annotation.Value; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Value("${wiremock.server.port}") +public @interface WiremockServerPort { + +} diff --git a/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java b/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java index 201d128..0f4f690 100644 --- a/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java +++ b/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java @@ -1,5 +1,6 @@ package dev.vality.disputes.dao; +import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.exception.NotFoundException; import org.junit.jupiter.api.Test; @@ -21,6 +22,7 @@ public abstract class DisputeDaoTest { @Test public void testInsertAndFind() { var random = random(Dispute.class); + random.setStatus(DisputeStatus.failed); disputeDao.save(random); assertEquals(random, disputeDao.get(random.getId(), random.getInvoiceId(), random.getPaymentId())); @@ -38,6 +40,7 @@ public abstract class DisputeDaoTest { random.setId(null); random.setInvoiceId("setInvoiceId"); random.setPaymentId("setPaymentId"); + random.setStatus(DisputeStatus.failed); disputeDao.save(random); disputeDao.save(random); disputeDao.save(random); @@ -48,6 +51,7 @@ public abstract class DisputeDaoTest { @Test public void testNextCheckAfter() { var random = random(Dispute.class); + random.setStatus(DisputeStatus.already_exist_created); var createdAt = LocalDateTime.now(ZoneOffset.UTC); random.setCreatedAt(createdAt); random.setPollingBefore(createdAt.plusSeconds(10)); @@ -56,5 +60,24 @@ public abstract class DisputeDaoTest { assertTrue(disputeDao.getDisputesForUpdateSkipLocked(10, random.getStatus()).isEmpty()); disputeDao.update(random.getId(), random.getStatus(), createdAt.plusSeconds(0)); assertFalse(disputeDao.getDisputesForUpdateSkipLocked(10, random.getStatus()).isEmpty()); + disputeDao.update(random.getId(), DisputeStatus.failed); + } + + @Test + public void testGetDisputesForHgCall() { + var random = random(Dispute.class); + random.setId(null); + random.setInvoiceId("setInvoiceId"); + random.setPaymentId("setPaymentId"); + random.setSkipCallHgForCreateAdjustment(true); + random.setStatus(DisputeStatus.create_adjustment); + disputeDao.save(random); + disputeDao.save(random); + random.setSkipCallHgForCreateAdjustment(false); + disputeDao.save(random); + assertEquals(1, disputeDao.getDisputesForHgCall(10).size()); + for (var dispute : disputeDao.get("setInvoiceId", "setPaymentId")) { + disputeDao.update(dispute.getId(), DisputeStatus.failed); + } } } diff --git a/src/test/java/dev/vality/disputes/dao/FileMetaDaoTest.java b/src/test/java/dev/vality/disputes/dao/FileMetaDaoTest.java new file mode 100644 index 0000000..44723fb --- /dev/null +++ b/src/test/java/dev/vality/disputes/dao/FileMetaDaoTest.java @@ -0,0 +1,28 @@ +package dev.vality.disputes.dao; + +import dev.vality.disputes.config.PostgresqlSpringBootITest; +import dev.vality.disputes.domain.tables.pojos.FileMeta; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static dev.vality.testcontainers.annotations.util.RandomBeans.random; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateId; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@PostgresqlSpringBootITest +public class FileMetaDaoTest { + + @Autowired + private FileMetaDao fileMetaDao; + + @Test + public void testInsertAndFind() { + var random = random(FileMeta.class); + random.setFileId(generateId()); + random.setDisputeId(1L); + fileMetaDao.save(random); + random.setFileId(generateId()); + fileMetaDao.save(random); + assertEquals(2, fileMetaDao.getDisputeFiles(1L).size()); + } +} diff --git a/src/test/java/dev/vality/disputes/dao/ProviderDisputeDaoTest.java b/src/test/java/dev/vality/disputes/dao/ProviderDisputeDaoTest.java new file mode 100644 index 0000000..b6971e9 --- /dev/null +++ b/src/test/java/dev/vality/disputes/dao/ProviderDisputeDaoTest.java @@ -0,0 +1,23 @@ +package dev.vality.disputes.dao; + +import dev.vality.disputes.config.PostgresqlSpringBootITest; +import dev.vality.disputes.domain.tables.pojos.ProviderDispute; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static dev.vality.testcontainers.annotations.util.RandomBeans.random; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@PostgresqlSpringBootITest +public class ProviderDisputeDaoTest { + + @Autowired + private ProviderDisputeDao providerDisputeDao; + + @Test + public void testInsertAndFind() { + var random = random(ProviderDispute.class); + providerDisputeDao.save(random); + assertEquals(random, providerDisputeDao.get(random.getDisputeId())); + } +} diff --git a/src/test/java/dev/vality/disputes/dao/disputestartup/WithEmbeddedPostgresWithFlywayDisputeDaoTest.java b/src/test/java/dev/vality/disputes/dao/disputestartup/WithEmbeddedPostgresWithFlywayDisputeDaoTest.java index afa3293..15f4f48 100644 --- a/src/test/java/dev/vality/disputes/dao/disputestartup/WithEmbeddedPostgresWithFlywayDisputeDaoTest.java +++ b/src/test/java/dev/vality/disputes/dao/disputestartup/WithEmbeddedPostgresWithFlywayDisputeDaoTest.java @@ -2,7 +2,9 @@ package dev.vality.disputes.dao.disputestartup; import dev.vality.disputes.config.EmbeddedPostgresWithFlywaySpringBootITest; import dev.vality.disputes.dao.DisputeDaoTest; +import org.junit.jupiter.api.Disabled; +@Disabled @EmbeddedPostgresWithFlywaySpringBootITest public class WithEmbeddedPostgresWithFlywayDisputeDaoTest extends DisputeDaoTest { } diff --git a/src/test/java/dev/vality/disputes/dao/disputestartup/WithZonkyEmbeddedPostgresDisputeDaoTest.java b/src/test/java/dev/vality/disputes/dao/disputestartup/WithZonkyEmbeddedPostgresDisputeDaoTest.java index f06aa81..f838efd 100644 --- a/src/test/java/dev/vality/disputes/dao/disputestartup/WithZonkyEmbeddedPostgresDisputeDaoTest.java +++ b/src/test/java/dev/vality/disputes/dao/disputestartup/WithZonkyEmbeddedPostgresDisputeDaoTest.java @@ -2,7 +2,9 @@ package dev.vality.disputes.dao.disputestartup; import dev.vality.disputes.config.ZonkyEmbeddedPostgresSpringBootITest; import dev.vality.disputes.dao.DisputeDaoTest; +import org.junit.jupiter.api.Disabled; +@Disabled @ZonkyEmbeddedPostgresSpringBootITest public class WithZonkyEmbeddedPostgresDisputeDaoTest extends DisputeDaoTest { } diff --git a/src/main/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java b/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java similarity index 100% rename from src/main/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java rename to src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java diff --git a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java b/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java index ae3abde..0489b6e 100644 --- a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java +++ b/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java @@ -23,7 +23,6 @@ public class DebugManualParsingControllerTest { @MockBean private ManualParsingServiceSrv.Iface manualParsingHandler; - @Autowired private DebugManualParsingController debugManualParsingController; diff --git a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java b/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java new file mode 100644 index 0000000..befd1c6 --- /dev/null +++ b/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java @@ -0,0 +1,139 @@ +package dev.vality.disputes.manualparsing; + +import dev.vality.disputes.config.WireMockSpringBootITest; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.config.CreatedDisputesTestService; +import dev.vality.disputes.schedule.service.config.DisputeApiTestService; +import dev.vality.disputes.schedule.service.config.PendingDisputesTestService; +import dev.vality.disputes.util.WiremockUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +import static dev.vality.disputes.util.OpenApiUtil.*; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateId; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@WireMockSpringBootITest +@Import({PendingDisputesTestService.class}) +public class DebugManualParsingHandlerTest { + + @Autowired + private DisputeDao disputeDao; + @Autowired + private DisputeApiTestService disputeApiTestService; + @Autowired + private CreatedDisputesTestService createdDisputesTestService; + @Autowired + private PendingDisputesTestService pendingDisputesTestService; + @Autowired + private DebugManualParsingController debugManualParsingController; + + @Test + public void testCancelCreateAdjustment() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + debugManualParsingController.cancelPending(getCancelRequest(disputeId)); + assertEquals(DisputeStatus.cancelled, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + public void testCancelPending() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + debugManualParsingController.cancelPending(getCancelRequest(disputeId)); + assertEquals(DisputeStatus.cancelled, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + public void testCancelFailed() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + debugManualParsingController.cancelPending(getCancelRequest(disputeId)); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + public void testApproveCreateAdjustmentWithCallHg() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + debugManualParsingController.approvePending(getApproveRequest(disputeId, false)); + assertEquals(DisputeStatus.create_adjustment, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testApproveCreateAdjustmentWithSkipHg() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + debugManualParsingController.approvePending(getApproveRequest(disputeId, true)); + assertEquals(DisputeStatus.succeeded, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testApprovePendingWithSkipHg() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + debugManualParsingController.approvePending(getApproveRequest(disputeId, true)); + assertEquals(DisputeStatus.succeeded, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testApproveFailed() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + debugManualParsingController.approvePending(getApproveRequest(disputeId, true)); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + public void testBindCreatedCreateAdjustment() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + var providerDisputeId = generateId(); + debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + assertEquals(DisputeStatus.create_adjustment, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testBindCreatedPending() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerDisputeId = generateId(); + debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + assertEquals(DisputeStatus.pending, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testBindCreatedManualCreated() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var providerDisputeId = generateId(); + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.manual_created); + debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + assertEquals(DisputeStatus.manual_pending, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testBindCreatedAlreadyExist() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var providerDisputeId = generateId(); + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.already_exist_created); + debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + assertEquals(DisputeStatus.pending, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testGetDispute() { + WiremockUtils.mockS3AttachmentDownload(); + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + var disputes = debugManualParsingController.getDisputes(getGetDisputeRequest(disputeId, true)); + assertEquals(1, disputes.getDisputes().size()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/CreateAdjustmentsServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/CreateAdjustmentsServiceTest.java new file mode 100644 index 0000000..88fb6f8 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/CreateAdjustmentsServiceTest.java @@ -0,0 +1,107 @@ +package dev.vality.disputes.schedule.service; + +import dev.vality.damsel.domain.InvoicePaymentCaptured; +import dev.vality.damsel.domain.InvoicePaymentStatus; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.config.WireMockSpringBootITest; +import dev.vality.disputes.constant.ErrorReason; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.converter.InvoicePaymentAdjustmentParamsConverter; +import dev.vality.disputes.schedule.service.config.DisputeApiTestService; +import dev.vality.disputes.schedule.service.config.PendingDisputesTestService; +import dev.vality.disputes.util.MockUtil; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +import java.util.List; + +import static dev.vality.disputes.util.MockUtil.getInvoicePaymentAdjustment; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@WireMockSpringBootITest +@Import({PendingDisputesTestService.class}) +@SuppressWarnings({"ParameterName", "LineLength"}) +public class CreateAdjustmentsServiceTest { + + @Autowired + private InvoicingSrv.Iface invoicingClient; + @Autowired + private DisputeDao disputeDao; + @Autowired + private CreateAdjustmentsService createAdjustmentsService; + @Autowired + private DisputeApiTestService disputeApiTestService; + @Autowired + private PendingDisputesTestService pendingDisputesTestService; + @Autowired + private InvoicePaymentAdjustmentParamsConverter invoicePaymentAdjustmentParamsConverter; + + @Test + @SneakyThrows + public void testPaymentNotFound() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.create_adjustment); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createAdjustmentsService.callHgForCreateAdjustment(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + assertEquals(ErrorReason.PAYMENT_NOT_FOUND, disputeDao.get(Long.parseLong(disputeId)).get().getErrorMessage()); + } + + @Test + @SneakyThrows + public void testDisputesAdjustmentExist() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.create_adjustment); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + dispute.get().setReason("test adj"); + var adjustmentId = "adjustmentId"; + var invoicePaymentAdjustment = getInvoicePaymentAdjustment(adjustmentId, invoicePaymentAdjustmentParamsConverter.getReason(dispute.get())); + invoicePayment.setAdjustments(List.of(invoicePaymentAdjustment)); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + createAdjustmentsService.callHgForCreateAdjustment(dispute.get()); + assertEquals(DisputeStatus.succeeded, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testInvoiceNotFound() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + var paymentId = "1"; + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createAdjustmentsService.callHgForCreateAdjustment(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + assertEquals(ErrorReason.INVOICE_NOT_FOUND, disputeDao.get(Long.parseLong(disputeId)).get().getErrorMessage()); + } + + @Test + @SneakyThrows + public void testFullSuccessFlow() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + var paymentId = "1"; + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + dispute.get().setReason("test adj"); + var adjustmentId = "adjustmentId"; + var reason = invoicePaymentAdjustmentParamsConverter.getReason(dispute.get()); + when(invoicingClient.createPaymentAdjustment(any(), any(), any())) + .thenReturn(getInvoicePaymentAdjustment(adjustmentId, reason)); + createAdjustmentsService.callHgForCreateAdjustment(dispute.get()); + assertEquals(DisputeStatus.succeeded, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java new file mode 100644 index 0000000..07c5a2d --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java @@ -0,0 +1,163 @@ +package dev.vality.disputes.schedule.service; + +import dev.vality.damsel.domain.InvoicePaymentCaptured; +import dev.vality.damsel.domain.InvoicePaymentStatus; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.ProviderDisputesServiceSrv; +import dev.vality.disputes.config.WireMockSpringBootITest; +import dev.vality.disputes.constant.ErrorReason; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.config.CreatedDisputesTestService; +import dev.vality.disputes.schedule.service.config.DisputeApiTestService; +import dev.vality.disputes.schedule.service.config.WiremockAddressesHolder; +import dev.vality.disputes.service.external.DominantService; +import dev.vality.disputes.util.MockUtil; +import dev.vality.file.storage.FileStorageSrv; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +import static dev.vality.disputes.util.MockUtil.*; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateId; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@WireMockSpringBootITest +@Import({CreatedDisputesTestService.class}) +public class CreatedDisputesServiceTest { + + @Autowired + private ProviderIfaceBuilder providerIfaceBuilder; + @Autowired + private DominantService dominantService; + @Autowired + private InvoicingSrv.Iface invoicingClient; + @Autowired + private FileStorageSrv.Iface fileStorageClient; + @Autowired + private DisputeDao disputeDao; + @Autowired + private CreatedDisputesService createdDisputesService; + @Autowired + private DisputeApiTestService disputeApiTestService; + @Autowired + private WiremockAddressesHolder wiremockAddressesHolder; + @Autowired + private CreatedDisputesTestService createdDisputesTestService; + + @Test + @SneakyThrows + public void testPaymentNotFound() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + assertEquals(ErrorReason.PAYMENT_NOT_FOUND, disputeDao.get(Long.parseLong(disputeId)).get().getErrorMessage()); + } + + @Test + @SneakyThrows + public void testSkipDisputeWhenPaymentNonFinalStatus() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + when(invoicingClient.getPayment(any(), any())).thenReturn(MockUtil.createInvoicePayment(paymentId)); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.created, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testNoAttachments() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + assertEquals(ErrorReason.NO_ATTACHMENTS, disputeDao.get(Long.parseLong(disputeId)).get().getErrorMessage()); + } + + @Test + @SneakyThrows + public void testManualCreatedWhenIsNotProvidersDisputesApiExist() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + when(dominantService.getTerminal(any())).thenReturn(createTerminal().get()); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_created, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + public void testDisputeCreatedSuccessResult() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testDisputeCreatedFailResult() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var providerDisputeId = generateId(); + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + when(dominantService.getProxy(any())).thenReturn(createProxy().get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.createDispute(any())).thenReturn(createDisputeCreatedFailResult()); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + @SneakyThrows + public void testDisputeCreatedAlreadyExistResult() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var providerDisputeId = generateId(); + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + when(dominantService.getProxy(any())).thenReturn(createProxy().get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.createDispute(any())).thenReturn(createDisputeAlreadyExistResult()); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.already_exist_created, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java new file mode 100644 index 0000000..a28063e --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java @@ -0,0 +1,88 @@ +package dev.vality.disputes.schedule.service; + +import dev.vality.disputes.ProviderDisputesServiceSrv; +import dev.vality.disputes.config.WireMockSpringBootITest; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.config.CreatedDisputesTestService; +import dev.vality.disputes.schedule.service.config.DisputeApiTestService; +import dev.vality.disputes.schedule.service.config.PendingDisputesTestService; +import dev.vality.disputes.service.external.DominantService; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +import static dev.vality.disputes.util.MockUtil.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@WireMockSpringBootITest +@Import({PendingDisputesTestService.class}) +public class PendingDisputesServiceTest { + + @Autowired + private ProviderIfaceBuilder providerIfaceBuilder; + @Autowired + private DominantService dominantService; + @Autowired + private DisputeDao disputeDao; + @Autowired + private PendingDisputesService pendingDisputesService; + @Autowired + private DisputeApiTestService disputeApiTestService; + @Autowired + private CreatedDisputesTestService createdDisputesTestService; + @Autowired + private PendingDisputesTestService pendingDisputesTestService; + + @Test + @SneakyThrows + public void testProviderDisputeNotFound() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.pending); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.created, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testDisputeStatusSuccessResult() { + var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testDisputeStatusFailResult() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusFailResult()); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.failed, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + } + + @Test + @SneakyThrows + public void testDisputeStatusPendingResult() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusPendingResult()); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.pending, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + disputeDao.update(Long.parseLong(disputeId), DisputeStatus.failed); + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java b/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java new file mode 100644 index 0000000..5444644 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java @@ -0,0 +1,72 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.damsel.domain.InvoicePaymentCaptured; +import dev.vality.damsel.domain.InvoicePaymentStatus; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.ProviderDisputesServiceSrv; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.CreatedDisputesService; +import dev.vality.disputes.schedule.service.ProviderIfaceBuilder; +import dev.vality.disputes.service.external.DominantService; +import dev.vality.disputes.util.MockUtil; +import dev.vality.disputes.util.TestUrlPaths; +import dev.vality.file.storage.FileStorageSrv; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.context.annotation.Import; + +import static dev.vality.disputes.util.MockUtil.*; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateId; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@TestComponent +@Import({DisputeApiTestService.class, RemoteClientTestConfig.class}) +@SuppressWarnings({"ParameterName", "LineLength"}) +public class CreatedDisputesTestService { + + @Autowired + private ProviderIfaceBuilder providerIfaceBuilder; + @Autowired + private DominantService dominantService; + @Autowired + private InvoicingSrv.Iface invoicingClient; + @Autowired + private FileStorageSrv.Iface fileStorageClient; + @Autowired + private DisputeDao disputeDao; + @Autowired + private CreatedDisputesService createdDisputesService; + @Autowired + private DisputeApiTestService disputeApiTestService; + @Autowired + private WiremockAddressesHolder wiremockAddressesHolder; + + @SneakyThrows + public String callCreateDisputeRemotely() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var providerDisputeId = generateId(); + var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + when(dominantService.getProxy(any())).thenReturn(createProxy(String.format("http://127.0.0.1:%s%s", 8023, TestUrlPaths.ADAPTER)).get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.createDispute(any())).thenReturn(createDisputeCreatedSuccessResult(providerDisputeId)); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.pending, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + return disputeId; + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestConfig.java b/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestConfig.java new file mode 100644 index 0000000..c7d60d0 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestConfig.java @@ -0,0 +1,25 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.bouncer.decisions.ArbiterSrv; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService; +import dev.vality.file.storage.FileStorageSrv; +import dev.vality.token.keeper.TokenAuthenticatorSrv; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +@TestConfiguration +public class DisputeApiTestConfig { + + @MockBean + private InvoicingSrv.Iface invoicingClient; + @MockBean + private TokenAuthenticatorSrv.Iface tokenKeeperClient; + @MockBean + private ArbiterSrv.Iface bouncerClient; + @MockBean + private DominantAsyncService dominantAsyncService; + @MockBean + private FileStorageSrv.Iface fileStorageClient; + +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestService.java b/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestService.java new file mode 100644 index 0000000..79a5405 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/DisputeApiTestService.java @@ -0,0 +1,69 @@ +package dev.vality.disputes.schedule.service.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.vality.bouncer.decisions.ArbiterSrv; +import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.auth.utils.JwtTokenBuilder; +import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService; +import dev.vality.disputes.util.MockUtil; +import dev.vality.disputes.util.OpenApiUtil; +import dev.vality.disputes.util.WiremockUtils; +import dev.vality.file.storage.FileStorageSrv; +import dev.vality.swag.disputes.model.Create200Response; +import dev.vality.token.keeper.TokenAuthenticatorSrv; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static dev.vality.disputes.util.MockUtil.*; +import static java.util.UUID.randomUUID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestComponent +@Import({DisputeApiTestConfig.class, WiremockAddressesHolder.class}) +@SuppressWarnings({"ParameterName", "LineLength"}) +public class DisputeApiTestService { + + @Autowired + private InvoicingSrv.Iface invoicingClient; + @Autowired + private TokenAuthenticatorSrv.Iface tokenKeeperClient; + @Autowired + private ArbiterSrv.Iface bouncerClient; + @Autowired + private DominantAsyncService dominantAsyncService; + @Autowired + private FileStorageSrv.Iface fileStorageClient; + @Autowired + private JwtTokenBuilder tokenBuilder; + @Autowired + private MockMvc mvc; + @Autowired + private WiremockAddressesHolder wiremockAddressesHolder; + + @SneakyThrows + public Create200Response createDisputeViaApi(String invoiceId, String paymentId) { + when(invoicingClient.get(any(), any())).thenReturn(MockUtil.createInvoice(invoiceId, paymentId)); + when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData()); + when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed()); + when(dominantAsyncService.getTerminal(any())).thenReturn(createTerminal()); + when(dominantAsyncService.getCurrency(any())).thenReturn(createCurrency()); + when(fileStorageClient.createNewFile(any(), any())).thenReturn(createNewFileResult(wiremockAddressesHolder.getUploadUrl())); + WiremockUtils.mockS3AttachmentUpload(); + var resultActions = mvc.perform(post("/disputes/create") + .header("Authorization", "Bearer " + tokenBuilder.generateJwtWithRoles()) + .header("X-Request-ID", randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(OpenApiUtil.getContentCreateRequest(invoiceId, paymentId))) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.disputeId").isNotEmpty()); + return new ObjectMapper().readValue(resultActions.andReturn().getResponse().getContentAsString(), Create200Response.class); + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/PendingDisputesTestService.java b/src/test/java/dev/vality/disputes/schedule/service/config/PendingDisputesTestService.java new file mode 100644 index 0000000..34bbbfd --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/PendingDisputesTestService.java @@ -0,0 +1,44 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.disputes.ProviderDisputesServiceSrv; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.schedule.service.PendingDisputesService; +import dev.vality.disputes.schedule.service.ProviderIfaceBuilder; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.context.annotation.Import; + +import static dev.vality.disputes.util.MockUtil.createDisputeStatusSuccessResult; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@TestComponent +@Import({CreatedDisputesTestService.class}) +@SuppressWarnings({"ParameterName", "LineLength"}) +public class PendingDisputesTestService { + + @Autowired + private ProviderIfaceBuilder providerIfaceBuilder; + @Autowired + private DisputeDao disputeDao; + @Autowired + private PendingDisputesService pendingDisputesService; + @Autowired + private CreatedDisputesTestService createdDisputesTestService; + + @SneakyThrows + public String callPendingDisputeRemotely() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusSuccessResult()); + when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock); + var dispute = disputeDao.get(Long.parseLong(disputeId)); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.create_adjustment, disputeDao.get(Long.parseLong(disputeId)).get().getStatus()); + return disputeId; + } +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/RemoteClientTestConfig.java b/src/test/java/dev/vality/disputes/schedule/service/config/RemoteClientTestConfig.java new file mode 100644 index 0000000..6cd4452 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/RemoteClientTestConfig.java @@ -0,0 +1,16 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.disputes.schedule.service.ProviderIfaceBuilder; +import dev.vality.disputes.service.external.DominantService; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +@TestConfiguration +public class RemoteClientTestConfig { + + @MockBean + private ProviderIfaceBuilder providerIfaceBuilder; + @MockBean + private DominantService dominantService; + +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/WiremockAddressesHolder.java b/src/test/java/dev/vality/disputes/schedule/service/config/WiremockAddressesHolder.java new file mode 100644 index 0000000..c510a86 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/WiremockAddressesHolder.java @@ -0,0 +1,20 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.disputes.config.WiremockServerPort; +import dev.vality.disputes.util.TestUrlPaths; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +public class WiremockAddressesHolder { + + @WiremockServerPort + private int wiremockPort; + + public String getDownloadUrl() { + return String.format("http://127.0.0.1:%s%s%s", wiremockPort, TestUrlPaths.S3_PATH, TestUrlPaths.MOCK_DOWNLOAD); + } + + public String getUploadUrl() { + return String.format("http://127.0.0.1:%s%s%s", wiremockPort, TestUrlPaths.S3_PATH, TestUrlPaths.MOCK_UPLOAD); + } +} diff --git a/src/test/java/dev/vality/disputes/util/DamselUtil.java b/src/test/java/dev/vality/disputes/util/DamselUtil.java new file mode 100644 index 0000000..f940957 --- /dev/null +++ b/src/test/java/dev/vality/disputes/util/DamselUtil.java @@ -0,0 +1,33 @@ +package dev.vality.disputes.util; + +import dev.vality.geck.serializer.kit.mock.FieldHandler; +import dev.vality.geck.serializer.kit.mock.MockMode; +import dev.vality.geck.serializer.kit.mock.MockTBaseProcessor; +import dev.vality.geck.serializer.kit.tbase.TBaseHandler; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.apache.thrift.TBase; + +import java.time.Instant; +import java.util.Map; + +@UtilityClass +public class DamselUtil { + + private static final MockTBaseProcessor mockRequiredTBaseProcessor; + + static { + mockRequiredTBaseProcessor = new MockTBaseProcessor(MockMode.REQUIRED_ONLY, 15, 1); + Map.Entry timeFields = Map.entry( + structHandler -> structHandler.value(Instant.now().toString()), + new String[]{"created_at", "at", "due", "status_changed_at", "invoice_valid_until", "event_created_at", + "held_until", "from_time", "to_time"} + ); + mockRequiredTBaseProcessor.addFieldHandler(timeFields.getKey(), timeFields.getValue()); + } + + @SneakyThrows + public static T fillRequiredTBaseObject(T tbase, Class type) { + return DamselUtil.mockRequiredTBaseProcessor.process(tbase, new TBaseHandler<>(type)); + } +} diff --git a/src/test/java/dev/vality/disputes/util/MockUtil.java b/src/test/java/dev/vality/disputes/util/MockUtil.java new file mode 100644 index 0000000..7a72bde --- /dev/null +++ b/src/test/java/dev/vality/disputes/util/MockUtil.java @@ -0,0 +1,169 @@ +package dev.vality.disputes.util; + +import dev.vality.bouncer.ctx.ContextFragment; +import dev.vality.bouncer.decisions.Judgement; +import dev.vality.bouncer.decisions.Resolution; +import dev.vality.bouncer.decisions.ResolutionAllowed; +import dev.vality.damsel.domain.Cash; +import dev.vality.damsel.domain.*; +import dev.vality.damsel.payment_processing.Invoice; +import dev.vality.damsel.payment_processing.InvoicePayment; +import dev.vality.disputes.*; +import dev.vality.disputes.constant.TerminalOptionsField; +import dev.vality.file.storage.NewFileResult; +import dev.vality.geck.common.util.TypeUtil; +import dev.vality.token.keeper.AuthData; +import dev.vality.token.keeper.AuthDataStatus; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.apache.thrift.TSerializer; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@UtilityClass +public class MockUtil { + + public static Invoice createInvoice(String invoiceId, String paymentId) { + return new Invoice() + .setInvoice(new dev.vality.damsel.domain.Invoice() + .setId(invoiceId) + .setCreatedAt(TypeUtil.temporalToString(LocalDateTime.now())) + .setDue(TypeUtil.temporalToString(LocalDateTime.now().plusDays(1))) + .setDetails(new InvoiceDetails() + .setProduct("test_product")) + .setCost(new Cash().setCurrency(new CurrencyRef().setSymbolicCode("RUB")))) + .setPayments(List.of(createInvoicePayment(paymentId))); + } + + public static InvoicePayment createInvoicePayment(String paymentId) { + return new InvoicePayment() + .setPayment(new dev.vality.damsel.domain.InvoicePayment() + .setId(paymentId) + .setCreatedAt(TypeUtil.temporalToString(LocalDateTime.now())) + .setPayer(Payer.payment_resource(new PaymentResourcePayer() + .setContactInfo(DamselUtil.fillRequiredTBaseObject(new ContactInfo(), + ContactInfo.class)) + .setResource(new DisposablePaymentResource() + .setPaymentTool( + PaymentTool.bank_card(DamselUtil.fillRequiredTBaseObject(new BankCard(), + BankCard.class)))))) + .setCost(new Cash() + .setCurrency(new CurrencyRef().setSymbolicCode("RUB"))) + .setStatus(InvoicePaymentStatus.pending(new InvoicePaymentPending()))) + .setRoute(new PaymentRoute() + .setProvider(DamselUtil.fillRequiredTBaseObject(new ProviderRef(), ProviderRef.class)) + .setTerminal(DamselUtil.fillRequiredTBaseObject(new TerminalRef(), TerminalRef.class))) + .setLastTransactionInfo(new TransactionInfo("trxId", Map.of())); + } + + @SneakyThrows + public static ContextFragment createContextFragment() { + ContextFragment fragment = DamselUtil.fillRequiredTBaseObject(new ContextFragment(), ContextFragment.class); + fragment.setContent(new TSerializer().serialize(new dev.vality.bouncer.context.v1.ContextFragment())); + return fragment; + } + + public static Judgement createJudgementAllowed() { + Resolution resolution = new Resolution(); + resolution.setAllowed(new ResolutionAllowed()); + return new Judgement().setResolution(resolution); + } + + public static CompletableFuture createProvider() { + return CompletableFuture.completedFuture(new Provider() + .setName("propropro") + .setDescription("pepepepe") + .setProxy(new Proxy().setRef(new ProxyRef().setId(1)))); + } + + public static CompletableFuture createProxy() { + return createProxy("http://ya.ru"); + } + + public static CompletableFuture createProxy(String url) { + return CompletableFuture.completedFuture(new ProxyDefinition() + .setName("prprpr") + .setDescription("pepepepe") + .setUrl(url)); + } + + public static CompletableFuture createTerminal() { + return CompletableFuture.completedFuture(new Terminal() + .setName("prprpr") + .setDescription("pepepepe") + .setOptions(new HashMap<>())); + } + + public static Map getOptions() { + Map options = new HashMap<>(); + options.put(TerminalOptionsField.DISPUTE_FLOW_MAX_TIME_POLLING_MIN, "5"); + options.put(TerminalOptionsField.DISPUTE_FLOW_PROVIDERS_API_EXIST, "true"); + return options; + } + + public static CompletableFuture createCurrency() { + return CompletableFuture.completedFuture(new Currency() + .setName("Ruble") + .setSymbolicCode("RUB") + .setExponent((short) 2) + .setNumericCode((short) 643)); + } + + public static AuthData createAuthData() { + return new AuthData() + .setId(UUID.randomUUID().toString()) + .setAuthority(UUID.randomUUID().toString()) + .setToken(UUID.randomUUID().toString()) + .setStatus(AuthDataStatus.active) + .setContext(createContextFragment()); + } + + public static NewFileResult createNewFileResult(String uploadUrl) { + return new NewFileResult(UUID.randomUUID().toString(), uploadUrl); + } + + public static DisputeCreatedResult createDisputeCreatedSuccessResult(String providerDisputeId) { + return DisputeCreatedResult.successResult(new DisputeCreatedSuccessResult(providerDisputeId)); + } + + public static DisputeCreatedResult createDisputeCreatedFailResult() { + return DisputeCreatedResult.failResult(new DisputeCreatedFailResult(createFailure())); + } + + public static DisputeCreatedResult createDisputeAlreadyExistResult() { + return DisputeCreatedResult.alreadyExistResult(new DisputeAlreadyExistResult()); + } + + public static DisputeStatusResult createDisputeStatusSuccessResult() { + return DisputeStatusResult.statusSuccess(new DisputeStatusSuccessResult().setChangedAmount(100)); + } + + public static DisputeStatusResult createDisputeStatusFailResult() { + return DisputeStatusResult.statusFail(new DisputeStatusFailResult(createFailure())); + } + + public static DisputeStatusResult createDisputeStatusPendingResult() { + return DisputeStatusResult.statusPending(new DisputeStatusPendingResult()); + } + + public static InvoicePaymentAdjustment getInvoicePaymentAdjustment(String adjustmentId, String reason) { + return new InvoicePaymentAdjustment() + .setId(adjustmentId) + .setState(InvoicePaymentAdjustmentState.status_change(new InvoicePaymentAdjustmentStatusChangeState() + .setScenario(new InvoicePaymentAdjustmentStatusChange() + .setTargetStatus(new InvoicePaymentStatus(InvoicePaymentStatus.captured( + new InvoicePaymentCaptured() + .setReason(reason))))))); + } + + public static Failure createFailure() { + Failure failure = new Failure("some_error"); + failure.setSub(new SubFailure("some_suberror")); + return failure; + } +} diff --git a/src/test/java/dev/vality/disputes/util/OpenApiUtil.java b/src/test/java/dev/vality/disputes/util/OpenApiUtil.java new file mode 100644 index 0000000..ab904f2 --- /dev/null +++ b/src/test/java/dev/vality/disputes/util/OpenApiUtil.java @@ -0,0 +1,103 @@ +package dev.vality.disputes.util; + +import lombok.experimental.UtilityClass; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +@UtilityClass +@SuppressWarnings({"ParameterName", "LineLength"}) +public class OpenApiUtil { + + public String getContentCreateRequest(String invoiceId, String paymentId) { + return String.format(""" + { + "invoiceId": "%s", + "paymentId": "%s", + "attachments": [ + { + "data": "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", + "mimeType": "image/png" + } + ], + "amount": 100, + "reason": "string" + } + """, invoiceId, paymentId); + } + + public static MultiValueMap getStatusRequiredParams(String disputeId, String invoiceId, String paymentId) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("invoiceId", invoiceId); + params.add("paymentId", paymentId); + params.add("disputeId", disputeId); + return params; + } + + public String getContentInvalidCreateRequest(String paymentId) { + return String.format(""" + { + "paymentId": "%s", + "attachments": [ + { + "data": "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", + "mimeType": "image/png" + } + ], + "amount": 100, + "reason": "string" + } + """, paymentId); + } + + public static String getCancelRequest(String disputeId) { + return String.format(""" + { + "cancelParams": [ + { + "disputeId": "%s", + "cancelReason": "test endpoint" + } + ] + } + """, disputeId); + } + + public static String getApproveRequest(String disputeId, boolean skipHg) { + return String.format(""" + { + "approveParams": [ + { + "disputeId": "%s", + "skipCallHgForCreateAdjustment": %s + } + ] + } + """, disputeId, skipHg); + } + + public static String getBindCreatedRequest(String disputeId, String providerDisputeId) { + return String.format(""" + { + "bindParams": [ + { + "disputeId": "%s", + "providerDisputeId": "%s" + } + ] + } + """, disputeId, providerDisputeId); + } + + public static String getGetDisputeRequest(String disputeId, boolean withAttachments) { + return String.format(""" + { + "disputeParams": [ + { + "disputeId": "%s" + } + ], + "withAttachments": %s + } + """, disputeId, withAttachments); + } +} diff --git a/src/test/java/dev/vality/disputes/util/TestUrlPaths.java b/src/test/java/dev/vality/disputes/util/TestUrlPaths.java new file mode 100644 index 0000000..9e1343c --- /dev/null +++ b/src/test/java/dev/vality/disputes/util/TestUrlPaths.java @@ -0,0 +1,14 @@ +package dev.vality.disputes.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TestUrlPaths { + + public static final String S3_PATH = "/s3"; + public static final String MOCK_UPLOAD = "/mock/upload"; + public static final String MOCK_DOWNLOAD = "/mock/download"; + public static final String ADAPTER = "/adapter"; + +} diff --git a/src/test/java/dev/vality/disputes/util/WiremockUtils.java b/src/test/java/dev/vality/disputes/util/WiremockUtils.java new file mode 100644 index 0000000..c9a9334 --- /dev/null +++ b/src/test/java/dev/vality/disputes/util/WiremockUtils.java @@ -0,0 +1,27 @@ +package dev.vality.disputes.util; + +import com.github.tomakehurst.wiremock.client.WireMock; + +import java.util.Base64; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; + +@SuppressWarnings({"FileTabCharacter", "LineLength"}) +public class WiremockUtils { + + public static void mockS3AttachmentUpload() { + stubFor(WireMock.put(TestUrlPaths.S3_PATH + TestUrlPaths.MOCK_UPLOAD) + .willReturn(aResponse() + .withStatus(200) + .withBody(""))); + } + + public static void mockS3AttachmentDownload() { + stubFor(WireMock.get(TestUrlPaths.S3_PATH + TestUrlPaths.MOCK_DOWNLOAD) + .willReturn(aResponse() + .withStatus(200) + .withBody(Base64.getDecoder().decode( + "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")))); + } +}