diff --git a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java index f510cac..9c1de6a 100644 --- a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java +++ b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java @@ -16,178 +16,184 @@ import org.junit.jupiter.api.Test; class MarketingAnalysisServiceChartPlacementTest { - // Keep this test free of Spring/Mockito to avoid agent-attach requirements. - private final MarketingAnalysisService marketingAnalysisService = new MarketingAnalysisService(null, null, null, - null); + // Keep this test free of Spring/Mockito to avoid agent-attach requirements. + private final MarketingAnalysisService marketingAnalysisService = new MarketingAnalysisService(null, null, null, + null, null); - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Map buildPlaceholderMap() throws Exception { - Class chartMappingClass = Class.forName("kz.konturai.parser.service.MarketingAnalysisService$ChartMapping"); - Constructor ctor = chartMappingClass.getDeclaredConstructor(String.class, String.class); - ctor.setAccessible(true); + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Map buildPlaceholderMap() throws Exception { + Class chartMappingClass = Class + .forName("kz.konturai.parser.service.MarketingAnalysisService$ChartMapping"); + Constructor ctor = chartMappingClass.getDeclaredConstructor(String.class, String.class); + ctor.setAccessible(true); - Map placeholderMap = new HashMap(); - placeholderMap.put("[[CHART_SEASONALITY]]", ctor.newInstance("seasonality", "seasonality")); - placeholderMap.put("[[CHART_AGE_DISTRIBUTION]]", ctor.newInstance("audienceAge", "audienceAge")); - placeholderMap.put("[[CHART_GENDER_DISTRIBUTION]]", ctor.newInstance("audienceGender", "audienceGender")); - placeholderMap.put("[[CHART_MARKET_SHARE]]", ctor.newInstance("marketShareChart", "marketShareChart")); - placeholderMap.put("[[TABLE_COMPARISON]]", ctor.newInstance("comparisonTable", "comparisonTable")); - placeholderMap.put("[[CHART_SWOT]]", ctor.newInstance("swot", "swot")); - placeholderMap.put("[[CHART_CHANNELS]]", ctor.newInstance("channelsPotential", "channelsPotential")); - placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", - ctor.newInstance("segmentsChannelMatrix", "segmentsChannelMatrix")); - placeholderMap.put("[[CHART_FUNNEL]]", ctor.newInstance("conversionFunnel", "funnel")); - return placeholderMap; - } + Map placeholderMap = new HashMap(); + placeholderMap.put("[[CHART_SEASONALITY]]", ctor.newInstance("seasonality", "seasonality")); + placeholderMap.put("[[CHART_AGE_DISTRIBUTION]]", ctor.newInstance("audienceAge", "audienceAge")); + placeholderMap.put("[[CHART_GENDER_DISTRIBUTION]]", + ctor.newInstance("audienceGender", "audienceGender")); + placeholderMap.put("[[CHART_MARKET_SHARE]]", ctor.newInstance("marketShareChart", "marketShareChart")); + placeholderMap.put("[[TABLE_COMPARISON]]", ctor.newInstance("comparisonTable", "comparisonTable")); + placeholderMap.put("[[CHART_SWOT]]", ctor.newInstance("swot", "swot")); + placeholderMap.put("[[CHART_CHANNELS]]", ctor.newInstance("channelsPotential", "channelsPotential")); + placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", + ctor.newInstance("segmentsChannelMatrix", "segmentsChannelMatrix")); + placeholderMap.put("[[CHART_FUNNEL]]", ctor.newInstance("conversionFunnel", "funnel")); + return placeholderMap; + } - private Map buildChartsDataSeasonalityOnly() { - Map chartsData = new HashMap<>(); - chartsData.put("seasonality", Map.of("labels", java.util.List.of("Jan"), "values", java.util.List.of(1))); - return chartsData; - } + private Map buildChartsDataSeasonalityOnly() { + Map chartsData = new HashMap<>(); + chartsData.put("seasonality", + Map.of("labels", java.util.List.of("Jan"), "values", java.util.List.of(1))); + return chartsData; + } - private Map buildChartsDataChannelsAndMatrix() { - Map chartsData = new HashMap<>(); - chartsData.put("channelsPotential", Map.of("labels", java.util.List.of("Instagram"), "datasets", - java.util.List.of(Map.of("label", "Потенциал", "data", java.util.List.of(80))))); - chartsData.put("segmentsChannelMatrix", - java.util.List.of(Map.of("segmentName", "Молодежь", "instagram", "high", "facebook", "medium"))); - return chartsData; - } + private Map buildChartsDataChannelsAndMatrix() { + Map chartsData = new HashMap<>(); + chartsData.put("channelsPotential", Map.of("labels", java.util.List.of("Instagram"), "datasets", + java.util.List.of(Map.of("label", "Потенциал", "data", java.util.List.of(80))))); + chartsData.put("segmentsChannelMatrix", + java.util.List.of(Map.of("segmentName", "Молодежь", "instagram", "high", "facebook", + "medium"))); + return chartsData; + } - @Test - void addMissingChartsToSections_shouldIgnoreTocListItems_andUseRealMarkdownHeading() throws Exception { - String report = "" - + "## Оглавление\n" - + "- [III. Анализ рынка и сезонности](#section-iii)\n" - + "- [IV. Анализ целевой аудитории](#section-iv)\n" - + "\n" - + "## III. Анализ рынка и сезонности\n" - + "Тут есть текст про рынок.\n" - + "\n" - + "## IV. Анализ целевой аудитории\n" - + "Тут аудитория.\n"; + @Test + void addMissingChartsToSections_shouldIgnoreTocListItems_andUseRealMarkdownHeading() throws Exception { + String report = "" + + "## Оглавление\n" + + "- [III. Анализ рынка и сезонности](#section-iii)\n" + + "- [IV. Анализ целевой аудитории](#section-iv)\n" + + "\n" + + "## III. Анализ рынка и сезонности\n" + + "Тут есть текст про рынок.\n" + + "\n" + + "## IV. Анализ целевой аудитории\n" + + "Тут аудитория.\n"; - Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, - Map.class, - Map.class, Set.class); - m.setAccessible(true); + Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, + Map.class, + Map.class, Set.class); + m.setAccessible(true); - String out = (String) m.invoke( - marketingAnalysisService, - report, - buildChartsDataSeasonalityOnly(), - buildPlaceholderMap(), - new HashSet()); + String out = (String) m.invoke( + marketingAnalysisService, + report, + buildChartsDataSeasonalityOnly(), + buildPlaceholderMap(), + new HashSet()); - int tocEnd = out.indexOf("## III."); - assertTrue(tocEnd > 0, "Sanity check: section III should exist"); - String tocPart = out.substring(0, tocEnd); - assertFalse(tocPart.contains("[[CHART_SEASONALITY]]"), "Must not insert into TOC block"); + int tocEnd = out.indexOf("## III."); + assertTrue(tocEnd > 0, "Sanity check: section III should exist"); + String tocPart = out.substring(0, tocEnd); + assertFalse(tocPart.contains("[[CHART_SEASONALITY]]"), "Must not insert into TOC block"); - int s3Start = out.indexOf("## III."); - int s4Start = out.indexOf("## IV."); - assertTrue(s3Start >= 0 && s4Start > s3Start, "Sanity check: III before IV"); - String section3Body = out.substring(s3Start, s4Start); - assertTrue(section3Body.contains("[[CHART_SEASONALITY]]"), "Must insert into section III body, not TOC"); - } + int s3Start = out.indexOf("## III."); + int s4Start = out.indexOf("## IV."); + assertTrue(s3Start >= 0 && s4Start > s3Start, "Sanity check: III before IV"); + String section3Body = out.substring(s3Start, s4Start); + assertTrue(section3Body.contains("[[CHART_SEASONALITY]]"), + "Must insert into section III body, not TOC"); + } - @Test - void addMissingChartsToSections_whenAnyPlaceholderProcessed_shouldNotAppendAnything() throws Exception { - String report = "" - + "## III. Анализ рынка и сезонности\n" - + "Тут есть текст про рынок.\n" - + "\n" - + "## IV. Анализ целевой аудитории\n" - + "Тут аудитория.\n"; + @Test + void addMissingChartsToSections_whenAnyPlaceholderProcessed_shouldNotAppendAnything() throws Exception { + String report = "" + + "## III. Анализ рынка и сезонности\n" + + "Тут есть текст про рынок.\n" + + "\n" + + "## IV. Анализ целевой аудитории\n" + + "Тут аудитория.\n"; - Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, - Map.class, - Map.class, Set.class); - m.setAccessible(true); + Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, + Map.class, + Map.class, Set.class); + m.setAccessible(true); - // Simulate: placeholder was already successfully replaced earlier in the - // pipeline - // (replaceChartPlaceholders adds normalized tokens to processedPlaceholders). - Set processed = new HashSet<>(); - processed.add("[[CHART_SEASONALITY]]".replaceAll("\\s+", "").toUpperCase(java.util.Locale.ROOT)); + // Simulate: placeholder was already successfully replaced earlier in the + // pipeline + // (replaceChartPlaceholders adds normalized tokens to processedPlaceholders). + Set processed = new HashSet<>(); + processed.add("[[CHART_SEASONALITY]]".replaceAll("\\s+", "").toUpperCase(java.util.Locale.ROOT)); - String out = (String) m.invoke( - marketingAnalysisService, - report, - buildChartsDataSeasonalityOnly(), - buildPlaceholderMap(), - processed); + String out = (String) m.invoke( + marketingAnalysisService, + report, + buildChartsDataSeasonalityOnly(), + buildPlaceholderMap(), + processed); - assertEquals(report, out, "If any placeholder was processed, must not append any missing placeholders"); - } + assertEquals(report, out, "If any placeholder was processed, must not append any missing placeholders"); + } - @Test - void addMissingChartsToSections_shouldInsertChannelsAndMatrixIntoSectionVII() throws Exception { - String report = "" - + "## Оглавление\n" - + "- [VII. Каналы продвижения и их потенциал](#section-vii)\n" - + "\n" - + "## VII. Каналы продвижения и их потенциал\n" - + "Тут есть текст про каналы.\n" - + "\n" - + "## VIII. Воронка спроса\n" - + "Тут есть текст про воронку.\n"; + @Test + void addMissingChartsToSections_shouldInsertChannelsAndMatrixIntoSectionVII() throws Exception { + String report = "" + + "## Оглавление\n" + + "- [VII. Каналы продвижения и их потенциал](#section-vii)\n" + + "\n" + + "## VII. Каналы продвижения и их потенциал\n" + + "Тут есть текст про каналы.\n" + + "\n" + + "## VIII. Воронка спроса\n" + + "Тут есть текст про воронку.\n"; - Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, - Map.class, - Map.class, Set.class); - m.setAccessible(true); + Method m = MarketingAnalysisService.class.getDeclaredMethod("addMissingChartsToSections", String.class, + Map.class, + Map.class, Set.class); + m.setAccessible(true); - String out = (String) m.invoke( - marketingAnalysisService, - report, - buildChartsDataChannelsAndMatrix(), - buildPlaceholderMap(), - new HashSet()); + String out = (String) m.invoke( + marketingAnalysisService, + report, + buildChartsDataChannelsAndMatrix(), + buildPlaceholderMap(), + new HashSet()); - int s7Start = out.indexOf("## VII."); - int s8Start = out.indexOf("## VIII."); - assertTrue(s7Start >= 0 && s8Start > s7Start, "Sanity check: VII before VIII"); - String section7Body = out.substring(s7Start, s8Start); - assertTrue(section7Body.contains("[[CHART_CHANNELS]]"), "Must insert CHART_CHANNELS into section VII"); - assertTrue(section7Body.contains("[[CHART_CHANNEL_MATRIX]]"), - "Must insert CHART_CHANNEL_MATRIX into section VII"); - } + int s7Start = out.indexOf("## VII."); + int s8Start = out.indexOf("## VIII."); + assertTrue(s7Start >= 0 && s8Start > s7Start, "Sanity check: VII before VIII"); + String section7Body = out.substring(s7Start, s8Start); + assertTrue(section7Body.contains("[[CHART_CHANNELS]]"), "Must insert CHART_CHANNELS into section VII"); + assertTrue(section7Body.contains("[[CHART_CHANNEL_MATRIX]]"), + "Must insert CHART_CHANNEL_MATRIX into section VII"); + } - @Test - void buildChartJsonBlock_shouldStartWithJsonKey_andEndWithDoubleNewline() throws Exception { - Method m = MarketingAnalysisService.class.getDeclaredMethod("buildChartJsonBlock", String.class, String.class); - m.setAccessible(true); + @Test + void buildChartJsonBlock_shouldStartWithJsonKey_andEndWithDoubleNewline() throws Exception { + Method m = MarketingAnalysisService.class.getDeclaredMethod("buildChartJsonBlock", String.class, + String.class); + m.setAccessible(true); - String out = (String) m.invoke(marketingAnalysisService, "segmentsChannelMatrix", "{\"a\":1}"); + String out = (String) m.invoke(marketingAnalysisService, "segmentsChannelMatrix", "{\"a\":1}"); - assertTrue(out.startsWith("```json:segmentsChannelMatrix\n"), - "Block must start strictly with ```json: and newline"); - assertTrue(out.endsWith("\n```\n\n"), "Block must end with closing fence followed by two newlines"); + assertTrue(out.startsWith("```json:segmentsChannelMatrix\n"), + "Block must start strictly with ```json: and newline"); + assertTrue(out.endsWith("\n```\n\n"), "Block must end with closing fence followed by two newlines"); - // Ensure there is no whitespace before opening fence (strict parsing on - // frontend) - assertTrue(Pattern.compile("(?m)^```json:segmentsChannelMatrix$").matcher(out).find(), - "Opening fence line must be exactly ```json: with no leading spaces"); - } + // Ensure there is no whitespace before opening fence (strict parsing on + // frontend) + assertTrue(Pattern.compile("(?m)^```json:segmentsChannelMatrix$").matcher(out).find(), + "Opening fence line must be exactly ```json: with no leading spaces"); + } - @Test - void replaceChartPlaceholders_shouldRemoveGenericChartDotsPlaceholder() throws Exception { - Method m = MarketingAnalysisService.class.getDeclaredMethod("replaceChartPlaceholders", String.class, - Map.class); - m.setAccessible(true); + @Test + void replaceChartPlaceholders_shouldRemoveGenericChartDotsPlaceholder() throws Exception { + Method m = MarketingAnalysisService.class.getDeclaredMethod("replaceChartPlaceholders", String.class, + Map.class); + m.setAccessible(true); - String report = "" - + "## I. Краткое резюме\n" - + "Текст перед.\n" - + "[[CHART_...]]\n" - + "Текст после.\n"; + String report = "" + + "## I. Краткое резюме\n" + + "Текст перед.\n" + + "[[CHART_...]]\n" + + "Текст после.\n"; - // Even if chartsData is empty, generic placeholders must be removed. - String out = (String) m.invoke(marketingAnalysisService, report, new HashMap()); + // Even if chartsData is empty, generic placeholders must be removed. + String out = (String) m.invoke(marketingAnalysisService, report, new HashMap()); - assertFalse(out.contains("[[CHART_...]]"), - "Generic placeholder [[CHART_...]] must be removed from fullAnalysis"); - } + assertFalse(out.contains("[[CHART_...]]"), + "Generic placeholder [[CHART_...]] must be removed from fullAnalysis"); + } } diff --git a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceDetailLevelPromptTest.java b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceDetailLevelPromptTest.java index 03fdf63..e1fcec0 100644 --- a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceDetailLevelPromptTest.java +++ b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceDetailLevelPromptTest.java @@ -14,7 +14,7 @@ class MarketingAnalysisServiceDetailLevelPromptTest { // Keep this test free of Spring/Mockito to avoid agent-attach requirements. private final MarketingAnalysisService marketingAnalysisService = new MarketingAnalysisService(null, null, null, - null); + null, null); @Test void appendDetailLevelDirectives_shouldForceBullets_forKratko() throws Exception { @@ -61,5 +61,3 @@ class MarketingAnalysisServiceDetailLevelPromptTest { assertTrue(out.contains("Porter's Five Forces")); } } - -