Creating ProfileDocumentServiceTest AdmittedDealRegisterServiceTest tests. Creating InstantDateTimeDeserializer and InstantDateTimeSerializer with pattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'").

This commit is contained in:
psemenkov 2023-02-28 11:47:42 +03:00
parent a1076c5026
commit e0b53a15d1
16 changed files with 664 additions and 3445 deletions

View file

@ -0,0 +1,113 @@
package ru.spcex.clearing.company.service;
import org.apache.kafka.clients.consumer.MockConsumer;
import org.apache.kafka.clients.producer.MockProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.profile.ProfileDocument;
import ru.spcex.clearing.company.config.HazelcastServiceTestConfiguration;
import ru.spcex.clearing.company.config.KafkaConfigTest;
import ru.spcex.clearing.company.utils.MatcherFactory;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.company.ProfileDocumentNewRequest;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService;
import javax.annotation.PostConstruct;
import java.time.LocalDate;
import static ru.spcex.clearing.company.config.HazelcastServiceTestConfiguration.currentID;
import static ru.spcex.clearing.company.utils.MatcherFactory.usingIgnoringFieldsComparator;
import static ru.spcex.clearing.company.utils.TestUtils.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
ProfileDocumentService.class,
KafkaConfigTest.class,
HazelcastServiceTestConfiguration.class})
class ProfileDocumentServiceTest {
public static final MatcherFactory.Matcher<ProfileDocument> PROFILE_DOCUMENT_MATCHER = usingIgnoringFieldsComparator();
private static final int PARTITION = 0;
private static final String TOPIC_DESTINATION_PROFILE_DOCUMENT_NEW = Consts.DESTINATION_PROFILE_DOCUMENT_NEW;
private static final Long ID = currentID.getAndIncrement();
@Autowired
ProfileDocumentService profileDocumentService;
@Autowired
@Qualifier("hazelcastServiceTest")
private HazelcastService hazelcastServiceTest;
private Imdg<ProfileDocument> profileDocumentMap;
private Imdg<Company> companyMap;
@Captor
private ArgumentCaptor<ProducerRecord> producerRecord;
@SpyBean
private MockProducer<String, Object> mockProducer;
@PostConstruct
private void init() {
hazelcastServiceTest.waitAvailable();
this.profileDocumentMap = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_ProfileDocument, ProfileDocument.class);
this.companyMap = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Company, Company.class);
}
/**
* {@link ProfileDocumentService#newProfileDocument(BaseRequest)} <br>
* Тест проверяет обновление сущности {@link ProfileDocument} в Hazelcast при передаче из Apache Kafka.<br>
* Входной запрос {@link ProfileDocumentNewRequest}:<br>
*/
@Test
void newProfileDocument() {
//ARRANGE
Long companyId = 1234L;
ProfileDocumentNewRequest profileDocumentNewRequest = new ProfileDocumentNewRequest();
profileDocumentNewRequest.setCompanyId(companyId);
profileDocumentNewRequest.setDocumentType("doc");
profileDocumentNewRequest.setIssueDate(LocalDate.now());
profileDocumentNewRequest.setIssuePlace("place");
profileDocumentNewRequest.setIssuer("issuer");
profileDocumentNewRequest.setIssuerCode("issuerCode");
profileDocumentNewRequest.setName("name");
profileDocumentNewRequest.setNumber("number");
profileDocumentNewRequest.setPlace("place");
profileDocumentNewRequest.setValidFromDate(LocalDate.now());
profileDocumentNewRequest.setValidToDate(LocalDate.now());
profileDocumentNewRequest.setLink("link");
ProfileDocument predictableProfileDocument = new ProfileDocument();
predictableProfileDocument.setCompanyId(profileDocumentNewRequest.getCompanyId());
predictableProfileDocument.setDocumentType(profileDocumentNewRequest.getDocumentType());
predictableProfileDocument.setIssueDate(profileDocumentNewRequest.getIssueDate());
predictableProfileDocument.setIssuePlace(profileDocumentNewRequest.getIssuePlace());
predictableProfileDocument.setIssuer(profileDocumentNewRequest.getIssuer());
predictableProfileDocument.setIssuerCode(profileDocumentNewRequest.getIssuerCode());
predictableProfileDocument.setName(profileDocumentNewRequest.getName());
predictableProfileDocument.setNumber(profileDocumentNewRequest.getNumber());
predictableProfileDocument.setPlace(profileDocumentNewRequest.getPlace());
predictableProfileDocument.setValidFromDate(profileDocumentNewRequest.getValidFromDate());
predictableProfileDocument.setValidToDate(profileDocumentNewRequest.getValidToDate());
predictableProfileDocument.setLink(profileDocumentNewRequest.getLink());
//ACT
String jsonString = getJsonStringForNew(profileDocumentNewRequest, ID);
addRecordToKafka((MockConsumer) profileDocumentService.getConsumer(), TOPIC_DESTINATION_PROFILE_DOCUMENT_NEW, PARTITION, 0, jsonString);
//ASSERT
waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord);
ProfileDocument resultNew = profileDocumentMap.getSingleObjectBySQL(String.format("companyId = %d", companyId));
predictableProfileDocument.setId(resultNew.getId());
PROFILE_DOCUMENT_MATCHER.assertMatch(resultNew, predictableProfileDocument);
}
}

View file

@ -116,6 +116,7 @@ public class AllMapStoreTest {
} catch (InstantiationException | IllegalAccessException e) {
log.error(e.getMessage());
} catch (InvocationTargetException | NoSuchMethodException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}

View file

@ -1,6 +1,8 @@
package ru.spcex.clearing.imdg.structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.spcex.clearing.imdg.utils.MatcherFactory.Matcher;
import ru.spcex.platform.classes.base.SpcexObjectBase;
@ -12,6 +14,7 @@ import static ru.spcex.clearing.imdg.utils.MatcherFactory.usingIgnoringFieldsCom
public class BusinessObjectAndBusinessEventForCheckMapStore<T> {
public final Matcher<T> MATCHER;
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final String mapName;
private final Class<T> clazz;
private SettingOperation[] settingOperations;
@ -56,7 +59,13 @@ public class BusinessObjectAndBusinessEventForCheckMapStore<T> {
public void setPredictableObj(SpcexObjectBase predictableObj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (settingOperations != null) {
for (SettingOperation settingOperation : settingOperations) {
Method method = clazz.getDeclaredMethod(settingOperation.getMethodName(), settingOperation.getParameterTypes());
Method method = null;
try {
method = clazz.getDeclaredMethod(settingOperation.getMethodName(), settingOperation.getParameterTypes());
} catch (NoSuchMethodException exception) {
log.debug("settingOperations for Superclass " + clazz.getName());
method = clazz.getSuperclass().getDeclaredMethod(settingOperation.getMethodName(), settingOperation.getParameterTypes());
}
method.invoke(predictableObj, settingOperation.getParams());
}
}

View file

@ -8,6 +8,7 @@ import ru.clearing.classes.statics.data.company.CompanyRoleSet;
import ru.clearing.classes.statics.data.company.CompanySymbols;
import ru.clearing.classes.statics.data.company.relation.Relation;
import ru.clearing.classes.statics.data.company.relation.RelationHistory;
import ru.clearing.classes.statics.data.execution.ExecutionDeposit;
import ru.clearing.classes.statics.data.generated.ClearingMemberCategory;
import ru.clearing.classes.statics.data.journal.InDocumentJournal;
import ru.clearing.classes.statics.data.journal.ManagementJournal;
@ -19,8 +20,7 @@ import ru.clearing.classes.statics.data.misc.*;
import ru.clearing.classes.statics.data.payment.PaymentInstruction;
import ru.clearing.classes.statics.data.profile.Contact;
import ru.clearing.classes.statics.data.profile.ProfileDocument;
import ru.clearing.classes.statics.data.register.OrderRegister;
import ru.clearing.classes.statics.data.register.ReportRegister;
import ru.clearing.classes.statics.data.register.*;
import ru.clearing.classes.statics.data.scheduler.*;
import ru.clearing.classes.statics.data.sdf.*;
import ru.clearing.classes.statics.data.security.Security;
@ -32,9 +32,12 @@ import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.imdg.structure.BusinessObjectAndBusinessEventForCheckMapStore.SettingOperation;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import static ru.spcex.clearing.imdg.utils.DbDataUtils.generatingRandomBigDecimal;
import static ru.spcex.clearing.imdg.utils.DbDataUtils.generatingRandomInstant;
import static ru.spcex.clearing.imdg.utils.MatcherFactory.usingIgnoringFieldsComparator;
public class RunnableMapNamesForTesting {
@ -131,28 +134,38 @@ public class RunnableMapNamesForTesting {
//object
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_AccountRouting, AccountRouting.class));
// businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_AdmittedDealRegister, AdmittedDealRegister.class,
// new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("19.12")})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_AdmittedDealRegister, AdmittedDealRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_BankAccount, BankAccount.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ClearingMemberCategory, ClearingMemberCategory.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ClearMemberRegister, ClearMemberRegister.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_CompanyRoleSet, CompanyRoleSet.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_CompanySymbols, CompanySymbols.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_Contact, Contact.class));
// businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ContractRegister, ContractRegister.class));
// businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_CoveredDealRegister, CoveredDealRegister.class,
// new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("11.11")})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ContractRegister, ContractRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_CoveredDealRegister, CoveredDealRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_Currency, Currency.class));
// businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_DealRegister, DealRegister.class,
//// new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("11.11")})));
// businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ExecutionDeposit, ExecutionDeposit.class,
// new SettingOperation("setFirstLegAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("03.22")}),
// new SettingOperation("setSecondLegAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("03.33")}),
// new SettingOperation("setInterestAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("04.22")}),
// new SettingOperation("setLots", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("05.22")}),
// new SettingOperation("setQuantity", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("09.22")})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_DealRegister, DealRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ExecutionDeposit, ExecutionDeposit.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setFirstLegAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setSecondLegAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setInterestAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setLots", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setQuantity", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_InDocumentJournal, InDocumentJournal.class,
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("22.12")})));
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_InformationAccount, InformationAccount.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_KeyRate, KeyRate.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class,
@ -164,7 +177,9 @@ public class RunnableMapNamesForTesting {
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_PlannerAllToday, PlannerAllToday.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ProfileDocument, ProfileDocument.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ReportRegister, ReportRegister.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_ReportRegister, ReportRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_SDf01, SDf01.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_SDf02, SDf02.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_SDf03, SDf03.class));
@ -181,15 +196,19 @@ public class RunnableMapNamesForTesting {
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_SDf18, SDf18.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_Session, Session.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_STrade, STrade.class,
new SettingOperation("setQty", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("23.92")}),
new SettingOperation("setValue", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("24.92")}),
new SettingOperation("setExchange_commission", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("26.26")})));
new SettingOperation("setQty", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setValue", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setExchangeCommission", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_UncoveredDealRegister, UncoveredDealRegister.class,
new SettingOperation("setCreated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setUpdated", new Class[]{Instant.class}, new Object[]{generatingRandomInstant(true)}),
new SettingOperation("setAmount", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_UserRoleSession, UserRoleSession.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_UserSettings, UserSettings.class));
businessObjectAndBusinessEventForCheckMapStores.add(new BusinessObjectAndBusinessEventForCheckMapStore<>(IMDGDistributedNames.Map_VerificationResult, VerificationResult.class,
new SettingOperation("setDiffSum", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("13.22")}),
new SettingOperation("setInSum", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("14.22")}),
new SettingOperation("setOutExtSum", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("15.22")}),
new SettingOperation("setOutIntSum", new Class[]{BigDecimal.class}, new Object[]{new BigDecimal("19.22")})));
new SettingOperation("setDiffSum", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setInSum", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setOutExtSum", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)}),
new SettingOperation("setOutIntSum", new Class[]{BigDecimal.class}, new Object[]{generatingRandomBigDecimal(2)})));
}
}

View file

@ -191,7 +191,7 @@ public final class DbDataUtils {
} else if (typeField.equals(Integer.TYPE) || typeField.equals(Integer.class)) {
field.set(object, generatingRandomInteger());
} else if (typeField.getName().equals(BigDecimal.class.getName())) {
field.set(object, new BigDecimal(String.format("%d3.220000000000000000", generatingRandomInteger())));
field.set(object, generatingRandomBigDecimal(18));
} else if (typeField.equals(Long.TYPE) || typeField.equals(Long.class)) {
field.set(object, generatingRandomLong());
} else if (typeField.equals(LocalDate.class)) {
@ -205,7 +205,7 @@ public final class DbDataUtils {
} else if (typeField.getName().equals(UUID.class.getName())) {
field.set(object, UUID.randomUUID());
} else if (typeField.getName().equals(Instant.class.getName())) {
field.set(object, Instant.parse(String.format("2022-10-0%dT15:39:18.659Z", generatingRandomInteger())));
field.set(object, generatingRandomInstant(false));
// } else if (typeField.getSimpleName().equals(CompanyInfo.class.getSimpleName())) {
// CompanyInfo companyInfo = new CompanyInfo();
// fillObjectDefaultValues(companyInfo, CompanyInfo.class);
@ -248,4 +248,19 @@ public final class DbDataUtils {
int rightLimit = 9;
return leftLimit + (int) (new Random().nextFloat() * (rightLimit - leftLimit));
}
public static int generatingRandomInteger(int leftLimit, int rightLimit) {
return leftLimit + (int) (new Random().nextFloat() * (rightLimit - leftLimit));
}
public static BigDecimal generatingRandomBigDecimal(int scale) {
BigDecimal num = new BigDecimal(String.format("%d3.%d20000000000000000", generatingRandomInteger(), generatingRandomInteger()));
return num.setScale(scale);
}
public static Instant generatingRandomInstant(boolean withoutDate) {
if (withoutDate)
return Instant.parse(String.format("1970-01-01T%d:%d:18.659Z", generatingRandomInteger(10, 21), generatingRandomInteger(10, 59)));
return Instant.parse(String.format("2022-10-0%dT%d:%d:18.659Z", generatingRandomInteger(), generatingRandomInteger(10, 23), generatingRandomInteger(10, 59)));
}
}

File diff suppressed because it is too large Load diff

View file

@ -69,6 +69,23 @@
<finalName>${project.artifactId}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.2.0-M1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0-M1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View file

@ -0,0 +1,71 @@
package ru.spcex.clearing.registry.config;
import com.hazelcast.config.*;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams;
import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService;
import ru.spcex.platform.imdg.iml.hazelcast.util.HazelcastHelper;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
@Configuration
public class HazelcastServiceTestConfiguration {
public static final AtomicLong currentID = new AtomicLong(0L);
private HazelcastInstance hazelcastInstance;
private static ThreadPoolTaskExecutor createThreadPoolTaskExecutor(int maxPoolSz, boolean waitForCompletion) {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
if (maxPoolSz > 2) {
pool.setKeepAliveSeconds(60);
pool.setAllowCoreThreadTimeOut(true);
}
pool.setCorePoolSize(maxPoolSz);
pool.setWaitForTasksToCompleteOnShutdown(waitForCompletion);
return pool;
}
@Bean(name = "hazelcastServiceTest")
public HazelcastService hazelcastService(@Qualifier("taskExecutorHazelcastClientInitializer") ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer, @Qualifier("taskExecutorIdGeneratorAwaiter") ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter, HazelcastClientParams params) {
Config cfg = new Config();
cfg.setInstanceName("localhost");
NetworkConfig networkConfig = new NetworkConfig();
JoinConfig joinConfig = new JoinConfig();
joinConfig.setMulticastConfig(new MulticastConfig().setEnabled(false));
joinConfig.setTcpIpConfig(new TcpIpConfig().setEnabled(true).setMembers(List.of("127.0.0.1")));
networkConfig.setJoin(joinConfig);
cfg.setNetworkConfig(networkConfig);
hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance(cfg);
HazelcastHelper.otcSystem_setStorageState(true, hazelcastInstance);
return new HazelcastService(taskExecutorHazelcastClientInitializer, taskExecutorIdGeneratorAwaiter, params);
}
@Bean(name = "taskExecutorHazelcastClientInitializer")
public ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer() {
return createThreadPoolTaskExecutor(1, true);
}
@Bean(name = "taskExecutorIdGeneratorAwaiter")
public ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter() {
return createThreadPoolTaskExecutor(1, false);
}
@Bean(name = "hazelcastClientParams")
public HazelcastClientParams getHazelcastClientParams() {
HazelcastClientParams params = new HazelcastClientParams();
params.setLogin("dev");
params.setPassword("dev-pass");
params.setClusterMembers("127.0.0.1");
params.setInstanceName("hzTestClient" + new Random().nextInt());
params.setNearCacheConfig(new NearCacheConfig());
return params;
}
}

View file

@ -0,0 +1,44 @@
package ru.spcex.clearing.registry.config;
import org.apache.kafka.clients.consumer.MockConsumer;
import org.apache.kafka.clients.consumer.OffsetResetStrategy;
import org.apache.kafka.clients.producer.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.service.RequestInfo;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgId;
import ru.spcex.platform.imdg.api.ImdgProvider;
@Configuration
public class KafkaConfigTest {
@Autowired
@Bean(name = "kafkaSenderTest")
public KafkaSender kafkaSender(Producer<String, Object> kafkaProducer, @Qualifier("hazelcastServiceTest") ImdgProvider imdgProvider) {
ImdgId imdgIdGenerator = imdgProvider.getImdgIdGenerator();
return KafkaSender
.setup()
.producer(kafkaProducer)
.idGenerator(imdgIdGenerator::nextId)
.imdgProvider(s -> {
Imdg<RequestInfo> imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_RequestInfo, RequestInfo.class);
return imdg::insert;
})
.build();
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = "mockConsumerTest")
public MockConsumer<String, Object> createConsumer() {
return new MockConsumer<>(OffsetResetStrategy.EARLIEST);
}
}

View file

@ -0,0 +1,118 @@
package ru.spcex.clearing.registry.service;
import org.apache.kafka.clients.consumer.MockConsumer;
import org.apache.kafka.clients.producer.MockProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import ru.clearing.classes.statics.data.register.AdmittedDealRegister;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.registry.AdmittedDealRegisterNewRequest;
import ru.spcex.clearing.registry.config.HazelcastServiceTestConfiguration;
import ru.spcex.clearing.registry.config.KafkaConfigTest;
import ru.spcex.clearing.registry.utils.MatcherFactory;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import static ru.spcex.clearing.registry.config.HazelcastServiceTestConfiguration.currentID;
import static ru.spcex.clearing.registry.utils.MatcherFactory.usingIgnoringFieldsComparator;
import static ru.spcex.clearing.registry.utils.TestUtils.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
AdmittedDealRegisterService.class,
KafkaConfigTest.class,
HazelcastServiceTestConfiguration.class})
class AdmittedDealRegisterServiceTest {
public static final MatcherFactory.Matcher<AdmittedDealRegister> ADMITTED_DEAL_REGISTER_MATCHER = usingIgnoringFieldsComparator();
private static final int PARTITION = 0;
private static final String TOPIC_REGISTRY_ADMITTED_DEAL_REGISTER_NEW = Consts.REGISTRY_ADMITTED_DEAL_REGISTER_NEW;
private static final Long ID = currentID.getAndIncrement();
@Autowired
AdmittedDealRegisterService admittedDealRegisterService;
@Autowired
@Qualifier("hazelcastServiceTest")
private HazelcastService hazelcastServiceTest;
private Imdg<AdmittedDealRegister> admittedDealRegisterMap;
@Captor
private ArgumentCaptor<ProducerRecord> producerRecord;
@SpyBean
private MockProducer<String, Object> mockProducer;
@PostConstruct
private void init() {
hazelcastServiceTest.waitAvailable();
this.admittedDealRegisterMap = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_AdmittedDealRegister, AdmittedDealRegister.class);
}
/**
* {@link AdmittedDealRegisterService#admittedDealRegisterNew(BaseRequest)} <br>
* Тест проверяет создание сущности {@link AdmittedDealRegister} в Hazelcast при передаче из Apache Kafka.<br>
* Входной запрос {@link AdmittedDealRegisterNewRequest}:<br>
*/
@Test
void admittedDealRegisterNew() {
//ARRANGE
Long executionId = 1234L;
AdmittedDealRegisterNewRequest admittedDealRegisterNewRequest = new AdmittedDealRegisterNewRequest();
admittedDealRegisterNewRequest.setExecutionId(executionId);
admittedDealRegisterNewRequest.setCompanyFullName("companyFullName");
admittedDealRegisterNewRequest.setTradingDate(LocalDate.now());
admittedDealRegisterNewRequest.setExchangeExecutionId(1L);
admittedDealRegisterNewRequest.setExchangeExecutionTime(Instant.now());
admittedDealRegisterNewRequest.setSecuritySymbol("symbol");
admittedDealRegisterNewRequest.setSecurityFullName("full name");
admittedDealRegisterNewRequest.setSellerFullName("full name");
admittedDealRegisterNewRequest.setSellerClearingCode("code");
admittedDealRegisterNewRequest.setSellerAccount("1561658");
admittedDealRegisterNewRequest.setBuyerFullName("full name");
admittedDealRegisterNewRequest.setBuyerClearingCode("ClearingCode");
admittedDealRegisterNewRequest.setBuyerAccount("65464643");
admittedDealRegisterNewRequest.setAmount(new BigDecimal(12));
admittedDealRegisterNewRequest.setClearingDate(LocalDate.now());
AdmittedDealRegister predictableAdmittedDealRegister = new AdmittedDealRegister();
predictableAdmittedDealRegister.setExecutionId(admittedDealRegisterNewRequest.getExecutionId());
predictableAdmittedDealRegister.setCompanyFullName(admittedDealRegisterNewRequest.getCompanyFullName());
predictableAdmittedDealRegister.setTradingDate(admittedDealRegisterNewRequest.getTradingDate());
predictableAdmittedDealRegister.setExchangeExecutionId(admittedDealRegisterNewRequest.getExchangeExecutionId());
predictableAdmittedDealRegister.setExchangeExecutionTime(admittedDealRegisterNewRequest.getExchangeExecutionTime());
predictableAdmittedDealRegister.setSecuritySymbol(admittedDealRegisterNewRequest.getSecuritySymbol());
predictableAdmittedDealRegister.setSecurityFullName(admittedDealRegisterNewRequest.getSecurityFullName());
predictableAdmittedDealRegister.setSellerFullName(admittedDealRegisterNewRequest.getSellerFullName());
predictableAdmittedDealRegister.setSellerClearingCode(admittedDealRegisterNewRequest.getSellerClearingCode());
predictableAdmittedDealRegister.setSellerAccount(admittedDealRegisterNewRequest.getSellerAccount());
predictableAdmittedDealRegister.setBuyerFullName(admittedDealRegisterNewRequest.getBuyerFullName());
predictableAdmittedDealRegister.setBuyerClearingCode(admittedDealRegisterNewRequest.getBuyerClearingCode());
predictableAdmittedDealRegister.setBuyerAccount(admittedDealRegisterNewRequest.getBuyerAccount());
predictableAdmittedDealRegister.setAmount(admittedDealRegisterNewRequest.getAmount());
predictableAdmittedDealRegister.setClearingDate(admittedDealRegisterNewRequest.getClearingDate());
//ACT
String jsonString = getJsonStringForNew(admittedDealRegisterNewRequest, ID);
addRecordToKafka((MockConsumer) admittedDealRegisterService.getConsumer(), TOPIC_REGISTRY_ADMITTED_DEAL_REGISTER_NEW, PARTITION, 0, jsonString);
//ASSERT
waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord);
AdmittedDealRegister resultNew = admittedDealRegisterMap.getSingleObjectBySQL(String.format("executionId = %d", executionId));
predictableAdmittedDealRegister.setId(resultNew.getId());
ADMITTED_DEAL_REGISTER_MATCHER.assertMatch(resultNew, predictableAdmittedDealRegister);
}
}

View file

@ -0,0 +1,38 @@
package ru.spcex.clearing.registry.utils;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Factory for creating test matchers.
* <p>
* Comparing actual and expected objects via AssertJ
*/
public class MatcherFactory {
public static <T> Matcher<T> usingIgnoringFieldsComparator(String... fieldsToIgnore) {
return new Matcher<>(fieldsToIgnore);
}
public static class Matcher<T> {
private final String[] fieldsToIgnore;
private Matcher(String... fieldsToIgnore) {
this.fieldsToIgnore = fieldsToIgnore;
}
public void assertMatch(T actual, T expected) {
assertThat(actual).usingRecursiveComparison().ignoringFields(fieldsToIgnore).isEqualTo(expected);
}
@SafeVarargs
public final void assertMatch(Iterable<T> actual, T... expected) {
assertMatch(actual, Arrays.asList(expected));
}
public void assertMatch(Iterable<T> actual, Iterable<T> expected) {
assertThat(actual).usingRecursiveFieldByFieldElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(expected);
}
}
}

View file

@ -0,0 +1,123 @@
package ru.spcex.clearing.registry.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.MockConsumer;
import org.apache.kafka.clients.producer.MockProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.TopicPartition;
import org.mockito.ArgumentCaptor;
import ru.spcex.clearing.platform.messaging.domain.ActionType;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate;
import ru.spcex.platform.classes.base.SpcexObjectBase;
import ru.spcex.platform.imdg.api.Imdg;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static ru.spcex.clearing.platform.messaging.service.Status.Success;
import static ru.spcex.clearing.registry.utils.MatcherFactory.usingIgnoringFieldsComparator;
public class TestUtils {
public static final MatcherFactory.Matcher<BaseRequest<Object>> BASE_REQUEST_MATCHER = usingIgnoringFieldsComparator();
private static final ObjectMapper objectMapper = new ObjectMapper();
public static void waitingWhenAddedRecordAndCheckIt(Long id, MockProducer mockProducer, ArgumentCaptor<ProducerRecord> producerRecord) {
BaseRequest<Object> predictableBaseRequest = new BaseRequest<>();
predictableBaseRequest.setId(id);
predictableBaseRequest.setActionType(ActionType.SYSTEM);
RequestInfoUpdate requestInfoUpdate = new RequestInfoUpdate();
requestInfoUpdate.setId(id);
requestInfoUpdate.setStatus(Success);
predictableBaseRequest.setRequestPayload(requestInfoUpdate);
//waiting for kafka producer send message (finale event)
verify(mockProducer, timeout(30_000L).times(1))
.send(producerRecord.capture());
BaseRequest<Object> baseRequestResult = (BaseRequest<Object>) producerRecord.getValue().value();
assertEquals(Consts.REQUEST_INFO_UPDATE, producerRecord.getValue().topic());
BASE_REQUEST_MATCHER.assertMatch(baseRequestResult, predictableBaseRequest);
}
public static void addRecordToKafka(MockConsumer mockConsumer, String topic, int partition, long offset, String jsonValue) {
TopicPartition tp = new TopicPartition(topic, partition);
HashMap<TopicPartition, Long> startOffsets = new HashMap<>();
startOffsets.put(tp, 0L);
mockConsumer.updateBeginningOffsets(startOffsets);
mockConsumer.schedulePollTask(() -> {
mockConsumer.rebalance(Collections.singletonList(tp));
mockConsumer.addRecord(new ConsumerRecord<>(topic, partition, offset, "key", jsonValue));
});
}
public static <T> String getJsonStringForNew(T accountRequest, long id) {
return getJsonBaseRequest(accountRequest, id, ActionType.NEW);
}
public static <T> String getJsonStringForUPDATE(T accountRequest, long id) {
return getJsonBaseRequest(accountRequest, id, ActionType.UPDATE);
}
public static <T> String getJsonStringForDELETE(T accountRequest, long id) {
return getJsonBaseRequest(accountRequest, id, ActionType.DELETE);
}
private static <T> String getJsonBaseRequest(T accountRequest, long id, ActionType actionType) {
BaseRequest<T> baseRequest = new BaseRequest<>();
baseRequest.setRequestPayload(accountRequest);
baseRequest.setId(id);
baseRequest.setActionType(actionType);
String jsonBaseRequest;
try {
jsonBaseRequest = objectMapper.writeValueAsString(baseRequest);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return jsonBaseRequest;
}
public static <T extends SpcexObjectBase> void clearAllInImdg(Imdg<T> imdg) {
Collection<T> values = imdg.getAllValues();
values.forEach(imdg::delete);
}
public static class FutureRecordMetadata implements Future<RecordMetadata> {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return false;
}
@Override
public RecordMetadata get() throws InterruptedException, ExecutionException {
return null;
}
@Override
public RecordMetadata get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
}

View file

@ -1,6 +1,12 @@
package ru.spcex.clearing.platform.messaging.domain.cud.registry;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDateTimeDeserializer;
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalDateDeserializer;
import ru.spcex.clearing.platform.messaging.domain.json.serialize.InstantDateTimeSerializer;
import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalDateSerializer;
import java.math.BigDecimal;
import java.time.Instant;
@ -11,10 +17,16 @@ public class AdmittedDealRegisterNewRequest {
public Long executionId;
@JsonProperty
public String companyFullName;
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonProperty
public LocalDate tradingDate;
@JsonProperty
public Long exchangeExecutionId;
@JsonSerialize(using = InstantDateTimeSerializer.class)
@JsonDeserialize(using = InstantDateTimeDeserializer.class)
@JsonProperty
public Instant exchangeExecutionTime;
@JsonProperty
@ -37,10 +49,19 @@ public class AdmittedDealRegisterNewRequest {
public BigDecimal amount;
@JsonProperty
public Long id;
@JsonSerialize(using = InstantDateTimeSerializer.class)
@JsonDeserialize(using = InstantDateTimeDeserializer.class)
@JsonProperty
public Instant createdAt;
@JsonSerialize(using = InstantDateTimeSerializer.class)
@JsonDeserialize(using = InstantDateTimeDeserializer.class)
@JsonProperty
public Instant updatedAt;
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonProperty
public LocalDate clearingDate;

View file

@ -0,0 +1,23 @@
package ru.spcex.clearing.platform.messaging.domain.json.deserialize;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import ru.spcex.platform.utils.time.TimeUtil;
import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
public class InstantDateTimeDeserializer extends JsonDeserializer<Instant> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'");
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String date = p.getText();
if (date == null || date.trim().length() == 0) {
return null;
}
return TimeUtil.parseInstantDateAndTime(date, formatter);
}
}

View file

@ -0,0 +1,21 @@
package ru.spcex.clearing.platform.messaging.domain.json.serialize;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import ru.spcex.platform.utils.time.TimeUtil;
import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
public class InstantDateTimeSerializer extends JsonSerializer<Instant> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'");
@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) return;
gen.writeString(TimeUtil.formatInstant(value, formatter));
}
}

View file

@ -17,6 +17,10 @@ public class TimeUtil {
return LocalDate.parse(date, formatter).atStartOfDay(zone).toInstant();
}
public static Instant parseInstantDateAndTime(String date, DateTimeFormatter formatter) {
return LocalDateTime.parse(date, formatter).atZone(zone).toInstant();
}
public static String formatInstant(Instant date, DateTimeFormatter formatter) {
if (date == null) return null;
return formatter.format(date.atZone(zone));