diff --git a/clearing-parent/securities-service/pom.xml b/clearing-parent/securities-service/pom.xml index 859ea7069..67c739b35 100644 --- a/clearing-parent/securities-service/pom.xml +++ b/clearing-parent/securities-service/pom.xml @@ -36,6 +36,46 @@ com.fasterxml.jackson.core jackson-databind + + org.assertj + assertj-core + test + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-test + test + + + org.springframework.boot + spring-boot-starter + + + junit + junit + test + + + junit + junit + test + + + org.mockito + mockito-junit-jupiter + 4.8.0 + test + + + + org.junit.jupiter + junit-jupiter + test + diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastInstanceTestConfiguration.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastInstanceTestConfiguration.java new file mode 100644 index 000000000..c8294909c --- /dev/null +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastInstanceTestConfiguration.java @@ -0,0 +1,16 @@ +package ru.spcex.clearing.securities.config; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.core.HazelcastInstance; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HazelcastInstanceTestConfiguration { + + @Bean(name = "hazelcastInstance") + public HazelcastInstance hazelcastInstance() { + return HazelcastClient.newHazelcastClient(); + } + +} \ No newline at end of file diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastServiceTestConfiguration.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastServiceTestConfiguration.java new file mode 100644 index 000000000..3af3f0514 --- /dev/null +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/config/HazelcastServiceTestConfiguration.java @@ -0,0 +1,73 @@ +package ru.spcex.clearing.securities.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; + +@Configuration +public class HazelcastServiceTestConfiguration { + 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.newHazelcastInstance(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; + } +} \ No newline at end of file diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityFactory.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityFactory.java new file mode 100644 index 000000000..2d56f90cf --- /dev/null +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityFactory.java @@ -0,0 +1,141 @@ +package ru.spcex.clearing.securities.service; + +import ru.clearing.classes.statics.data.misc.MoneyMarketSecurity; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityUpdateRequest; + +import java.math.BigDecimal; +import java.time.Instant; + + +class MoneyMarketSecurityFactory { + private Long id = 0L; + private Long securityId = 0L;// (linked to security) + private String description = "tsetse"; + private Double lotSize = 1.0; + private Instant startDate = Instant.ofEpochSecond(0); + private Instant endDate = Instant.ofEpochSecond(0); + private BigDecimal nominalValue = BigDecimal.valueOf(1.0); + + private Long nominalCurrency = 1L; // (linked to currencyCode) + private String instrumentType = "sdgaga"; // (linked to instrumentType) + private String fullName = "estat"; + + private Long nominalCurrencyId = 1L; + private String securitySymbol = "sdfafd"; + + public MoneyMarketSecurityFactory setLotSize(Double lotSize) { + this.lotSize = lotSize; + return this; + } + + public MoneyMarketSecurityFactory setNominalCurrencyId(Long nominalCurrencyId) { + this.nominalCurrencyId = nominalCurrencyId; + this.nominalCurrency = nominalCurrencyId; + return this; + } + + public MoneyMarketSecurityFactory setId(Long id) { + this.id = id; + return this; + } + + public MoneyMarketSecurityFactory setSecurityId(Long securityId) { + this.securityId = securityId; + return this; + } + + public MoneyMarketSecurityFactory setDescription(String description) { + this.description = description; + return this; + } + + public MoneyMarketSecurityFactory setStartDate(Instant startDate) { + this.startDate = startDate; + return this; + } + + public MoneyMarketSecurityFactory setEndDate(Instant endDate) { + this.endDate = endDate; + return this; + } + + public MoneyMarketSecurityFactory setNominalValue(BigDecimal nominalValue) { + this.nominalValue = nominalValue; + return this; + } + + public MoneyMarketSecurityFactory setNominalCurrency(Long nominalCurrency) { + this.nominalCurrency = nominalCurrency; + this.nominalCurrencyId = nominalCurrency; + return this; + } + + public MoneyMarketSecurityFactory setInstrumentType(String instrumentType) { + this.instrumentType = instrumentType; + return this; + } + + public MoneyMarketSecurityFactory setFullName(String fullName) { + this.fullName = fullName; + return this; + } + + public MoneyMarketSecurityFactory setSecuritySymbol(String securitySymbol) { + this.securitySymbol = securitySymbol; + return this; + } + + public MoneyMarketSecurity getMoneyMarketSecurity() { + MoneyMarketSecurity moneyMarketSecurity = new MoneyMarketSecurity(); + + moneyMarketSecurity.setId(id); + moneyMarketSecurity.setSecurityId(securityId); + moneyMarketSecurity.setDescription(description); + moneyMarketSecurity.setStartDate(startDate.minusSeconds(60 * 60 * 3)); + moneyMarketSecurity.setEndDate(endDate.minusSeconds(60 * 60 * 3)); + moneyMarketSecurity.setNominalValue(nominalValue); + moneyMarketSecurity.setNominalCurrency(nominalCurrency); + moneyMarketSecurity.setInstrumentType(instrumentType); + moneyMarketSecurity.setFullName(fullName); + moneyMarketSecurity.setSecuritySymbol(securitySymbol); + + return moneyMarketSecurity; + } + + public MoneyMarketSecurityUpdateRequest getMoneyMarketSecurityUpdateRequest() { + MoneyMarketSecurityUpdateRequest moneyMarketSecurityUpdateRequest + = new MoneyMarketSecurityUpdateRequest(); + + moneyMarketSecurityUpdateRequest.setId(id); + moneyMarketSecurityUpdateRequest.setEndDate(endDate); + moneyMarketSecurityUpdateRequest.setNominalValue(nominalValue.doubleValue()); + moneyMarketSecurityUpdateRequest.setNominalCurrencyId(nominalCurrencyId); + moneyMarketSecurityUpdateRequest.setInstrumentType(instrumentType); + moneyMarketSecurityUpdateRequest.setFullName(fullName); + + return moneyMarketSecurityUpdateRequest; + } + + public CommonDeleteRequest getCommonDeleteRequest() { + CommonDeleteRequest commonDeleteRequest = new CommonDeleteRequest(); + commonDeleteRequest.setId(id); + return commonDeleteRequest; + } + + public MoneyMarketSecurityNewRequest getMoneyMarketSecurityNewRequest() { + MoneyMarketSecurityNewRequest keyRequest = new MoneyMarketSecurityNewRequest(); + + keyRequest.setLotSize(lotSize); + keyRequest.setStartDate(startDate); + keyRequest.setEndDate(endDate); + keyRequest.setNominalValue(nominalValue.doubleValue()); + keyRequest.setNominalCurrencyId(nominalCurrencyId); + keyRequest.setInstrumentType(instrumentType); + keyRequest.setFullName(fullName); + keyRequest.setSecuritySymbol(securitySymbol); + + return keyRequest; + } +} diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityServiceTest.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityServiceTest.java new file mode 100644 index 000000000..77933d442 --- /dev/null +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/MoneyMarketSecurityServiceTest.java @@ -0,0 +1,297 @@ +package ru.spcex.clearing.securities.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; +import com.hazelcast.map.listener.EntryUpdatedListener; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.MockConsumer; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; +import org.apache.kafka.common.TopicPartition; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import ru.clearing.classes.statics.data.misc.KeyRate; +import ru.clearing.classes.statics.data.misc.MoneyMarketSecurity; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +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.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityUpdateRequest; +import ru.spcex.clearing.securities.config.HazelcastInstanceTestConfiguration; +import ru.spcex.clearing.securities.config.HazelcastServiceTestConfiguration; +import ru.spcex.clearing.securities.service.cud.MoneyMarketSecurityService; +import ru.spcex.clearing.securities.utils.MatcherFactory; +import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { + HazelcastServiceTestConfiguration.class, + HazelcastInstanceTestConfiguration.class}) +public class MoneyMarketSecurityServiceTest { + static final ObjectMapper objectMapper = new ObjectMapper(); + private MockConsumer kafkaMockQueue; + private final int TIMEOUT = 1000; + + @Autowired + @Qualifier("hazelcastServiceTest") + private HazelcastService hazelcastService; + @Autowired + @Qualifier("hazelcastInstance") + private HazelcastInstance hz; + + boolean isUsed; + + private void setIsUsed(boolean isUsed) { + this.isUsed = isUsed; + } + + private String jsonBaseRequest(BaseRequest baseRequest) { + String jsonBaseRequest; + + try { + jsonBaseRequest = objectMapper.writeValueAsString(baseRequest); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return jsonBaseRequest; + } + + void initKafka(String topic, int partition, Long offset) { + TopicPartition tp = new TopicPartition(topic, partition); + + kafkaMockQueue.schedulePollTask(() -> { + kafkaMockQueue.rebalance(Collections.singletonList(tp)); + }); + + HashMap startOffsets = new HashMap<>(); + startOffsets.put(tp, offset); + + kafkaMockQueue.updateBeginningOffsets(startOffsets); + } + + private void startService() throws InterruptedException { + MoneyMarketSecurityService keyRateService = + new MoneyMarketSecurityService(kafkaMockQueue, hazelcastService); + keyRateService.afterPropertiesSet(); + } + + @Test + public void testNewMoneyMarketSecurity() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_MONEY_MARKET_SECURITY_NEW; + final String MAP = IMDGDistributedNames.Map_MoneyMarketSecurity; + + final int PARTITION = 0; + final Long OFFSET = 0L; + final Long REQUEST_ID = 0L; + + final MoneyMarketSecurityFactory moneyMarketSecurityFactory = new MoneyMarketSecurityFactory(); + + final MoneyMarketSecurity moneyMarketSecurityPrediction = moneyMarketSecurityFactory + .getMoneyMarketSecurity(); + final MoneyMarketSecurityNewRequest keyRequest = moneyMarketSecurityFactory + .getMoneyMarketSecurityNewRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.NEW); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + isUsed = false; + + IMap iMap = hz.getMap(MAP); + + Object waiter = new Object(); + + String listenerID = iMap.addEntryListener((EntryAddedListener) entryEvent -> { + System.out.println("Checking Equality"); + MoneyMarketSecurity moneyMarketSecurityResult = entryEvent.getValue(); + moneyMarketSecurityPrediction.setId(entryEvent.getKey()); + + MatcherFactory.Matcher matcher = MatcherFactory + .usingIgnoringFieldsComparator("id", "securityId", "description"); + matcher.assertMatch(moneyMarketSecurityResult, moneyMarketSecurityPrediction); + System.out.println("ok"); + setIsUsed(true); + synchronized (waiter) { + waiter.notify(); + } + }, true); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + + @Test + public void testDeleteKeyRate() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_MONEY_MARKET_SECURITY_DELETE; + final String MAP = IMDGDistributedNames.Map_MoneyMarketSecurity; + + final int PARTITION = 0; + final Long ID = 1L; + final Long OFFSET = 0L; + final Long REQUEST_ID = 2L; + + final MoneyMarketSecurityFactory moneyMarketSecurityFactory = + new MoneyMarketSecurityFactory() + .setId(ID); + + final MoneyMarketSecurity initialMoneyMarketSecurity = moneyMarketSecurityFactory + .getMoneyMarketSecurity(); + final CommonDeleteRequest keyRequest = moneyMarketSecurityFactory + .getCommonDeleteRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.DELETE); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + isUsed = false; + + IMap iMap = hz.getMap(MAP); + iMap.put(ID, initialMoneyMarketSecurity); + + Object waiter = new Object(); + String listenerID = iMap.addEntryListener((EntryRemovedListener) entryEvent -> { + System.out.println("Checking If removed.."); + Assertions.assertEquals(ID, entryEvent.getKey()); + System.out.println("ok"); + setIsUsed(true); + synchronized (waiter) { + waiter.notify(); + } + }, false); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + + + @Test + public void testUpdateKeyRate() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_MONEY_MARKET_SECURITY_UPDATE; + final String MAP = IMDGDistributedNames.Map_MoneyMarketSecurity; + + final int PARTITION = 0; + final Long ID = 1L; + final Long OFFSET = 0L; + final Long REQUEST_ID = 1L; + + final MoneyMarketSecurityFactory moneyMarketSecurityFactory = + new MoneyMarketSecurityFactory() + .setId(ID); + + final MoneyMarketSecurity initialMoneyMarketSecurity = moneyMarketSecurityFactory + .getMoneyMarketSecurity(); + + moneyMarketSecurityFactory + .setFullName("sfgsdfg") + .setInstrumentType("qwerqwe") + .setNominalCurrencyId(2L) + .setNominalValue(BigDecimal.valueOf(2.0)) + .setEndDate(Instant.ofEpochSecond(24 * 3600)); + + final MoneyMarketSecurity moneyMarketSecurityPrediction = + moneyMarketSecurityFactory.getMoneyMarketSecurity(); + final MoneyMarketSecurityUpdateRequest keyRequest = + moneyMarketSecurityFactory.getMoneyMarketSecurityUpdateRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.UPDATE); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + IMap iMap = hz.getMap(MAP); + iMap.put(ID, initialMoneyMarketSecurity); + + isUsed = false; + + Object waiter = new Object(); + String listenerID = iMap.addEntryListener((EntryUpdatedListener) entryEvent -> { + System.out.println("Checking Equality.."); + MoneyMarketSecurity moneyMarketSecurityResult = entryEvent.getValue(); + + MatcherFactory.Matcher matcher = MatcherFactory + .usingIgnoringFieldsComparator("id"); + + matcher.assertMatch(moneyMarketSecurityResult, moneyMarketSecurityPrediction); + + System.out.println("Ok"); + setIsUsed(true); + + synchronized (waiter) { + waiter.notify(); + } + }, true); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + +} diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/utils/MatcherFactory.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/utils/MatcherFactory.java new file mode 100644 index 000000000..63bd29dab --- /dev/null +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/utils/MatcherFactory.java @@ -0,0 +1,38 @@ +package ru.spcex.clearing.securities.utils; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Factory for creating test matchers. + *

+ * Comparing actual and expected objects via AssertJ + */ +public class MatcherFactory { + + public static Matcher usingIgnoringFieldsComparator(String... fieldsToIgnore) { + return new Matcher<>(fieldsToIgnore); + } + + public static class Matcher { + 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 actual, T... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingRecursiveFieldByFieldElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(expected); + } + } +} \ No newline at end of file diff --git a/clearing-parent/utility-service/pom.xml b/clearing-parent/utility-service/pom.xml index 2aed17aef..b2754fd8c 100644 --- a/clearing-parent/utility-service/pom.xml +++ b/clearing-parent/utility-service/pom.xml @@ -32,10 +32,50 @@ org.springframework.boot spring-boot-starter + + org.springframework + spring-test + test + + + org.springframework.boot + spring-boot-starter + com.fasterxml.jackson.core jackson-databind + + org.mockito + mockito-core + 4.8.0 + test + + + org.assertj + assertj-core + test + + + junit + junit + + + junit + junit + + + org.mockito + mockito-junit-jupiter + 4.8.0 + test + + + + org.junit.jupiter + junit-jupiter + test + diff --git a/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastInstanceTestConfiguration.java b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastInstanceTestConfiguration.java new file mode 100644 index 000000000..148e3e29b --- /dev/null +++ b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastInstanceTestConfiguration.java @@ -0,0 +1,23 @@ +package ru.spcex.clearing.utility.config; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.config.Config; +import com.hazelcast.config.JoinConfig; +import com.hazelcast.config.MulticastConfig; +import com.hazelcast.config.NetworkConfig; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Random; + +@Configuration +public class HazelcastInstanceTestConfiguration { + + @Bean(name = "hazelcastInstance") + public HazelcastInstance hazelcastInstance() { + return HazelcastClient.newHazelcastClient(); + } + +} \ No newline at end of file diff --git a/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastServiceTestConfiguration.java b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastServiceTestConfiguration.java new file mode 100644 index 000000000..d1cfc84d0 --- /dev/null +++ b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/config/HazelcastServiceTestConfiguration.java @@ -0,0 +1,73 @@ +package ru.spcex.clearing.utility.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; + +@Configuration +public class HazelcastServiceTestConfiguration { + 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.newHazelcastInstance(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; + } +} \ No newline at end of file diff --git a/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateFactory.java b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateFactory.java new file mode 100644 index 000000000..e5c611ff9 --- /dev/null +++ b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateFactory.java @@ -0,0 +1,79 @@ +package ru.spcex.clearing.utility.service; + +import ru.clearing.classes.statics.data.misc.KeyRate; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateUpdateRequest; + +import java.time.Instant; + +class KeyRateFactory { + Instant startDate = Instant.ofEpochSecond(0); + Instant endDate = Instant.ofEpochSecond(0); + String document = "Test"; + double rate = 0.5; + + Long id = 0L; + + KeyRateFactory setRate(double rate) { + this.rate = rate; + return this; + } + + KeyRateFactory setEndDate(Instant endDate) { + this.endDate = endDate; + return this; + } + + KeyRateFactory setId(Long id) { + this.id = id; + return this; + } + + KeyRateFactory setDocument(String document) { + this.document = document; + return this; + } + + KeyRateFactory setStartDate(Instant startDate) { + this.startDate = startDate; + return this; + } + + public KeyRate getKeyRate() { + KeyRate keyRatePrediction = new KeyRate(); + keyRatePrediction.setId(id); + keyRatePrediction.setDocument(document); + keyRatePrediction.setEndDate(endDate.minusSeconds(60 * 60 * 3)); + keyRatePrediction.setStartDate(startDate.minusSeconds(60 * 60 * 3)); + keyRatePrediction.setRate(rate); + return keyRatePrediction; + } + + public KeyRateUpdateRequest getKeyRateUpdateRequest() { + KeyRateUpdateRequest keyRateUpdateRequest = new KeyRateUpdateRequest(); + + keyRateUpdateRequest.setId(id); + keyRateUpdateRequest.setKeyRate(rate); + keyRateUpdateRequest.setDocument(document); + keyRateUpdateRequest.setStartDate(startDate); + keyRateUpdateRequest.setEndDate(endDate); + + return keyRateUpdateRequest; + } + + public CommonDeleteRequest getCommonDeleteRequest() { + CommonDeleteRequest commonDeleteRequest = new CommonDeleteRequest(); + commonDeleteRequest.setId(id); + return commonDeleteRequest; + } + + public KeyRateNewRequest getKeyRateNewRequest() { + KeyRateNewRequest keyRequest = new KeyRateNewRequest(); + keyRequest.setKeyRate(rate); + keyRequest.setDocument(document); + keyRequest.setEndDate(endDate); + keyRequest.setStartDate(startDate); + return keyRequest; + } +} diff --git a/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateServiceTest.java b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateServiceTest.java new file mode 100644 index 000000000..6a3996f71 --- /dev/null +++ b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/service/KeyRateServiceTest.java @@ -0,0 +1,285 @@ +package ru.spcex.clearing.utility.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; +import com.hazelcast.map.listener.EntryUpdatedListener; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.MockConsumer; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; +import org.apache.kafka.common.TopicPartition; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import ru.clearing.classes.statics.data.misc.KeyRate; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +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.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateUpdateRequest; +import ru.spcex.clearing.utility.config.HazelcastInstanceTestConfiguration; +import ru.spcex.clearing.utility.config.HazelcastServiceTestConfiguration; +import ru.spcex.clearing.utility.utils.MatcherFactory; +import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService; + +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { + HazelcastServiceTestConfiguration.class, + HazelcastInstanceTestConfiguration.class}) +public class KeyRateServiceTest { + static final ObjectMapper objectMapper = new ObjectMapper(); + private MockConsumer kafkaMockQueue; + private final int TIMEOUT = 1000; + + @Autowired + @Qualifier("hazelcastServiceTest") + private HazelcastService hazelcastService; + @Autowired + @Qualifier("hazelcastInstance") + private HazelcastInstance hz; + + boolean isUsed; + + private void setIsUsed(boolean isUsed) { + this.isUsed = isUsed; + } + + private String jsonBaseRequest(BaseRequest baseRequest) { + String jsonBaseRequest; + + try { + jsonBaseRequest = objectMapper.writeValueAsString(baseRequest); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return jsonBaseRequest; + } + + void initKafka(String topic, int partition, Long offset) { + TopicPartition tp = new TopicPartition(topic, partition); + + kafkaMockQueue.schedulePollTask(() -> { + kafkaMockQueue.rebalance(Collections.singletonList(tp)); + }); + + HashMap startOffsets = new HashMap<>(); + startOffsets.put(tp, offset); + + kafkaMockQueue.updateBeginningOffsets(startOffsets); + } + + private void startService() throws InterruptedException { + KeyRateService keyRateService = + new KeyRateService(kafkaMockQueue, hazelcastService); + keyRateService.afterPropertiesSet(); + } + + @Test + public void testNewKeyRate() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_KEY_RATE_NEW; + final String MAP = IMDGDistributedNames.Map_KeyRate; + + final int PARTITION = 0; + final Long OFFSET = 0L; + final Long REQUEST_ID = 0L; + + final KeyRateFactory keyRateFactory = new KeyRateFactory(); + + final KeyRate keyRatePrediction = keyRateFactory.getKeyRate(); + final KeyRateNewRequest keyRequest = keyRateFactory.getKeyRateNewRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.NEW); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + + isUsed = false; + + IMap iMap = hz.getMap(MAP); + + Object waiter = new Object(); + + String listenerID = iMap.addEntryListener((EntryAddedListener) entryEvent -> { + System.out.println("Checking Equality"); + KeyRate keyRateResult = entryEvent.getValue(); + keyRatePrediction.setId(entryEvent.getKey()); + + MatcherFactory.Matcher matcher = MatcherFactory + .usingIgnoringFieldsComparator("id"); + matcher.assertMatch(keyRateResult, keyRatePrediction); + System.out.println("ok"); + setIsUsed(true); + synchronized (waiter) { + waiter.notify(); + } + }, true); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + + @Test + public void testDeleteKeyRate() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_KEY_RATE_DELETE; + final String MAP = IMDGDistributedNames.Map_KeyRate; + + final int PARTITION = 0; + final Long ID = 1L; + final Long OFFSET = 0L; + final Long REQUEST_ID = 2L; + + final KeyRateFactory keyRateFactory = new KeyRateFactory() + .setId(ID); + + final KeyRate initialKeyRate = keyRateFactory.getKeyRate(); + final CommonDeleteRequest keyRequest = keyRateFactory.getCommonDeleteRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.DELETE); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + + isUsed = false; + + IMap iMap = hz.getMap(MAP); + iMap.put(ID, initialKeyRate); + + Object waiter = new Object(); + String listenerID = iMap.addEntryListener((EntryRemovedListener) entryEvent -> { + System.out.println("Checking If removed.."); + Assertions.assertEquals(ID, entryEvent.getKey()); + System.out.println("ok"); + setIsUsed(true); + synchronized (waiter) { + waiter.notify(); + } + }, false); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + + @Test + public void testUpdateKeyRate() throws InterruptedException { + final String TOPIC = Consts.DESTINATION_KEY_RATE_UPDATE; + final String MAP = IMDGDistributedNames.Map_KeyRate; + + final int PARTITION = 0; + final Long ID = 1L; + final Long OFFSET = 0L; + final Long REQUEST_ID = 1L; + + final KeyRateFactory keyRateFactory = new KeyRateFactory() + .setId(ID); + + final KeyRate initialKeyRate = keyRateFactory.getKeyRate(); + + keyRateFactory + .setRate(5) + .setEndDate(Instant.ofEpochSecond(24 * 3600)) + .setStartDate(Instant.ofEpochSecond(24 * 3600)) + .setDocument("Update"); + + final KeyRate keyRatePrediction = keyRateFactory.getKeyRate(); + final KeyRateUpdateRequest keyRequest = keyRateFactory.getKeyRateUpdateRequest(); + + BaseRequest baseRequest = new BaseRequest(); + + baseRequest.setRequestPayload(keyRequest); + baseRequest.setId(REQUEST_ID); + baseRequest.setActionType(ActionType.UPDATE); + + String jsonRequest = jsonBaseRequest(baseRequest); + + ConsumerRecord consumerRecord = + new ConsumerRecord<>(TOPIC, PARTITION, 0, "key", jsonRequest); + + kafkaMockQueue = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + initKafka(TOPIC, PARTITION, OFFSET); + + kafkaMockQueue.schedulePollTask(() -> kafkaMockQueue.addRecord(consumerRecord)); + + IMap iMap = hz.getMap(MAP); + iMap.put(ID, initialKeyRate); + + isUsed = false; + + Object waiter = new Object(); + String listenerID = iMap.addEntryListener((EntryUpdatedListener) entryEvent -> { + System.out.println("Checking Equality.."); + KeyRate keyRateResult = entryEvent.getValue(); + + MatcherFactory.Matcher matcher = MatcherFactory + .usingIgnoringFieldsComparator("id"); + + matcher.assertMatch(keyRateResult, keyRatePrediction); + + System.out.println("Ok"); + setIsUsed(true); + + synchronized (waiter) { + waiter.notify(); + } + }, true); + + startService(); + + synchronized (waiter) { + waiter.wait(TIMEOUT); + } + + Assertions.assertEquals(true, isUsed); + iMap.removeEntryListener(listenerID); + } + +} diff --git a/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/utils/MatcherFactory.java b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/utils/MatcherFactory.java new file mode 100644 index 000000000..06162be8f --- /dev/null +++ b/clearing-parent/utility-service/src/test/java/ru/spcex/clearing/utility/utils/MatcherFactory.java @@ -0,0 +1,37 @@ +package ru.spcex.clearing.utility.utils; + +import java.util.Arrays; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Factory for creating test matchers. + *

+ * Comparing actual and expected objects via AssertJ + */ +public class MatcherFactory { + + public static Matcher usingIgnoringFieldsComparator(String... fieldsToIgnore) { + return new Matcher<>(fieldsToIgnore); + } + + public static class Matcher { + 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 actual, T... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingRecursiveFieldByFieldElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(expected); + } + } +} \ No newline at end of file diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/KeyRateNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/KeyRateNewRequest.java index 35f0c2a2a..9625006b0 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/KeyRateNewRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/KeyRateNewRequest.java @@ -22,7 +22,7 @@ public class KeyRateNewRequest { @JsonProperty public String document; - public Double getKeyRate() { + public double getKeyRate() { return keyRate; }