package com.goldenchart.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; class StrategyDslTimeframeNormalizerTest { private final ObjectMapper objectMapper = new ObjectMapper(); private final StrategyDslTimeframeNormalizer normalizer = new StrategyDslTimeframeNormalizer(objectMapper); @Test void normalize_preserves3mAnd5mWithout1m() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["3m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """; String normalized = normalizer.normalizeJson(json, "테스트"); JsonNode root = objectMapper.readTree(normalized); assertEquals(2, root.path("candleTypes").size()); assertEquals("3m", root.path("candleTypes").get(0).asText()); assertEquals("5m", root.path("candleTypes").get(1).asText()); assertEquals("3m", root.path("candleType").asText()); } @Test void normalize_preservesSingleExplicit3m() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "3m", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """; String normalized = normalizer.normalizeJson(json, "테스트"); JsonNode root = objectMapper.readTree(normalized); assertEquals("3m", root.path("candleType").asText()); } @Test void normalize_dropsStale1mWhenConditionsUse5mOnly() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["1m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "CCI", "leftCandleType": "5m", "rightCandleType": "5m" } }] } """; String normalized = normalizer.normalizeJson(json, "알트레이어 매수 알림"); JsonNode root = objectMapper.readTree(normalized); assertEquals(1, root.path("candleTypes").size()); assertEquals("5m", root.path("candleTypes").get(0).asText()); assertEquals("5m", root.path("candleType").asText()); } @Test void resolveStartCandleTypes_meaningfulOverridesStaleStart1m() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "CCI", "leftCandleType": "5m", "rightCandleType": "5m" } }] } """; JsonNode root = objectMapper.readTree(json); assertEquals(Set.of("5m"), StrategyDslTimeframeNormalizer.resolveStartCandleTypes(root)); } @Test void resolveStartCandleTypes_dropsStale1mPair() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["1m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "CCI", "leftCandleType": "5m" } }] } """; JsonNode root = objectMapper.readTree(json); assertEquals(Set.of("5m"), StrategyDslTimeframeNormalizer.resolveStartCandleTypes(root)); } @Test void resolveStartCandleTypes_resolves3mAnd15mAnd1h() throws Exception { for (String tf : List.of("3m", "15m", "1h")) { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "leftCandleType": "%s", "rightCandleType": "%s" } }] } """.formatted(tf, tf); JsonNode root = objectMapper.readTree(json); assertEquals(Set.of(tf), StrategyDslTimeframeNormalizer.resolveStartCandleTypes(root), "expected " + tf); } } @Test void normalize_alignsStaleStartToConditionTimeframe() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["1m", "15m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "CCI", "leftCandleType": "15m" } }] } """; JsonNode root = objectMapper.readTree(normalizer.normalizeJson(json, "test")); assertEquals(1, root.path("candleTypes").size()); assertEquals("15m", root.path("candleTypes").get(0).asText()); assertEquals("15m", root.path("candleType").asText()); } @Test void normalize_preservesMultiCandleTypesOnSave() throws Exception { String json = """ { "type": "TIMEFRAME", "candleType": "1m", "candleTypes": ["1m", "3m", "5m"], "children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI", "period": 14 } }] } """; String normalized = normalizer.normalizeJson(json, "5분봉 테스트 전략"); JsonNode root = objectMapper.readTree(normalized); assertTrue(root.path("candleTypes").isArray()); assertEquals(3, root.path("candleTypes").size()); assertEquals("1m", root.path("candleTypes").get(0).asText()); assertEquals("3m", root.path("candleTypes").get(1).asText()); assertEquals("5m", root.path("candleTypes").get(2).asText()); } }