increase test coverage (#13)

This commit is contained in:
Anatolii Karlov 2024-09-24 21:31:44 +07:00 committed by GitHub
parent 1cbbda9909
commit 5f866d78da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1516 additions and 28 deletions

View File

@ -69,14 +69,6 @@ public class DisputeDao extends AbstractGenericDao {
return Optional.ofNullable(fetchOne(query, disputeRowMapper));
}
public Optional<Dispute> getForUpdateSkipLocked(long disputeId) {
var query = getDslContext().selectFrom(DISPUTE)
.where(DISPUTE.ID.eq(disputeId))
.forUpdate()
.skipLocked();
return Optional.ofNullable(fetchOne(query, disputeRowMapper));
}
public List<Dispute> getDisputesForUpdateSkipLocked(int limit, DisputeStatus disputeStatus) {
var query = getDslContext().selectFrom(DISPUTE)
.where(DISPUTE.STATUS.eq(disputeStatus)

View File

@ -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) {
// обрабатываем здесь только вручную созданные диспуты, у остальных предполагается,

View File

@ -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()));

View File

@ -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<InvoicePaymentAdjustment> 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()));
}
}

View File

@ -72,7 +72,7 @@ public class CreateAdjustmentsService {
return;
}
} catch (InvoicingPaymentStatusPendingException e) {
// в теории 0%, что сюда попдает выполнение кода, но если попадет, то:
// в теории 0%, что сюда попадает выполнение кода, но если попадет, то:
// платеж с не финальным статусом будет заблочен для создания корректировок на стороне хелгейта
// и тогда диспут будет пулиться, пока платеж не зафиналится,
// и тк никакой записи в коде выше нет, то пуллинг не проблема

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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()));
}
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -23,7 +23,6 @@ public class DebugManualParsingControllerTest {
@MockBean
private ManualParsingServiceSrv.Iface manualParsingHandler;
@Autowired
private DebugManualParsingController debugManualParsingController;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<FieldHandler, String[]> 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 extends TBase> T fillRequiredTBaseObject(T tbase, Class<T> type) {
return DamselUtil.mockRequiredTBaseProcessor.process(tbase, new TBaseHandler<>(type));
}
}

View File

@ -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<Provider> createProvider() {
return CompletableFuture.completedFuture(new Provider()
.setName("propropro")
.setDescription("pepepepe")
.setProxy(new Proxy().setRef(new ProxyRef().setId(1))));
}
public static CompletableFuture<ProxyDefinition> createProxy() {
return createProxy("http://ya.ru");
}
public static CompletableFuture<ProxyDefinition> createProxy(String url) {
return CompletableFuture.completedFuture(new ProxyDefinition()
.setName("prprpr")
.setDescription("pepepepe")
.setUrl(url));
}
public static CompletableFuture<Terminal> createTerminal() {
return CompletableFuture.completedFuture(new Terminal()
.setName("prprpr")
.setDescription("pepepepe")
.setOptions(new HashMap<>()));
}
public static Map<String, String> getOptions() {
Map<String, String> 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<Currency> 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;
}
}

File diff suppressed because one or more lines are too long

View File

@ -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";
}

File diff suppressed because one or more lines are too long