package com.goldenchart.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.goldenchart.entity.GcStrategy; import com.goldenchart.repository.GcStrategyRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class StrategyConditionTimeframeServiceTest { @Mock private GcStrategyRepository strategyRepo; @Spy private ObjectMapper objectMapper = new ObjectMapper(); @Mock private StrategyDslTimeframeNormalizer dslNormalizer; @Mock private LiveStrategyEvaluator liveStrategyEvaluator; private StrategyConditionTimeframeService service; @BeforeEach void setUpService() { service = new StrategyConditionTimeframeService( strategyRepo, objectMapper, dslNormalizer, liveStrategyEvaluator); } @BeforeEach void stubStrategy() { GcStrategy strategy = new GcStrategy(); strategy.setId(1L); strategy.setBuyConditionJson(""" { "type": "TIMEFRAME", "candleType": "5m", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """); strategy.setSellConditionJson(null); when(strategyRepo.findById(1L)).thenReturn(Optional.of(strategy)); when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(1)); when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(0)); } @Test void collectForStrategy_readsTimeframeFromDsl() { Set tfs = service.collectForStrategy(1L); assertEquals(Set.of("5m"), tfs); assertTrue(service.usesTimeframe(1L, "5m")); assertFalse(service.usesTimeframe(1L, "1m")); } @Test void collectForStrategy_reads3mAnd5mWithout1m() { GcStrategy onlyUpper = new GcStrategy(); onlyUpper.setId(4L); onlyUpper.setBuyConditionJson(""" { "type": "TIMEFRAME", "candleType": "3m", "candleTypes": ["3m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """); when(strategyRepo.findById(4L)).thenReturn(Optional.of(onlyUpper)); when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(1)); when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(0)); Set tfs = service.collectForStrategy(4L); assertEquals(Set.of("3m", "5m"), tfs); assertFalse(service.usesTimeframe(4L, "1m")); assertTrue(service.usesTimeframe(4L, "3m")); assertTrue(service.usesTimeframe(4L, "5m")); } @Test void collectForStrategy_readsMultipleCandleTypesFromTimeframeNode() { GcStrategy multi = new GcStrategy(); multi.setId(3L); multi.setBuyConditionJson(""" { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["1m", "3m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """); when(strategyRepo.findById(3L)).thenReturn(Optional.of(multi)); when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(1)); when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(0)); Set tfs = service.collectForStrategy(3L); assertEquals(Set.of("1m", "3m", "5m"), tfs); assertTrue(service.usesTimeframe(3L, "3m")); } @Test void collectForStrategy_buyStartScopeIgnoresSellLegacy1m() { GcStrategy mixed = new GcStrategy(); mixed.setId(5L); mixed.setBuyConditionJson(""" { "type": "TIMEFRAME", "candleType": "3m", "candleTypes": ["3m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "STOCHASTIC", "period": 14 } }] } """); mixed.setSellConditionJson(""" { "type": "TIMEFRAME", "candleType": "1m", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "STOCHASTIC", "period": 14 } }] } """); when(strategyRepo.findById(5L)).thenReturn(Optional.of(mixed)); when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(1)); when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(0)); Set tfs = service.collectForStrategy(5L); assertEquals(Set.of("3m", "5m"), tfs); assertFalse(service.usesTimeframe(5L, "1m")); } @Test void collectForStrategy_legacyTreeDefaultsTo1m() { GcStrategy legacy = new GcStrategy(); legacy.setId(2L); legacy.setBuyConditionJson(""" { "type": "AND", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """); when(strategyRepo.findById(2L)).thenReturn(Optional.of(legacy)); when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(1)); when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any())) .thenAnswer(inv -> inv.getArgument(0)); assertEquals(Set.of("1m"), service.collectForStrategy(2L)); } }