.
This commit is contained in:
+154
-148
@@ -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<String, Object> buildChartsDataSeasonalityOnly() {
|
||||
Map<String, Object> chartsData = new HashMap<>();
|
||||
chartsData.put("seasonality", Map.of("labels", java.util.List.of("Jan"), "values", java.util.List.of(1)));
|
||||
return chartsData;
|
||||
}
|
||||
private Map<String, Object> buildChartsDataSeasonalityOnly() {
|
||||
Map<String, Object> chartsData = new HashMap<>();
|
||||
chartsData.put("seasonality",
|
||||
Map.of("labels", java.util.List.of("Jan"), "values", java.util.List.of(1)));
|
||||
return chartsData;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildChartsDataChannelsAndMatrix() {
|
||||
Map<String, Object> 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<String, Object> buildChartsDataChannelsAndMatrix() {
|
||||
Map<String, Object> 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>());
|
||||
String out = (String) m.invoke(
|
||||
marketingAnalysisService,
|
||||
report,
|
||||
buildChartsDataSeasonalityOnly(),
|
||||
buildPlaceholderMap(),
|
||||
new HashSet<String>());
|
||||
|
||||
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<String> 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<String> 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>());
|
||||
String out = (String) m.invoke(
|
||||
marketingAnalysisService,
|
||||
report,
|
||||
buildChartsDataChannelsAndMatrix(),
|
||||
buildPlaceholderMap(),
|
||||
new HashSet<String>());
|
||||
|
||||
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:<key> 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:<key> 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:<key> 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:<key> 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<String, Object>());
|
||||
// Even if chartsData is empty, generic placeholders must be removed.
|
||||
String out = (String) m.invoke(marketingAnalysisService, report, new HashMap<String, Object>());
|
||||
|
||||
assertFalse(out.contains("[[CHART_...]]"),
|
||||
"Generic placeholder [[CHART_...]] must be removed from fullAnalysis");
|
||||
}
|
||||
assertFalse(out.contains("[[CHART_...]]"),
|
||||
"Generic placeholder [[CHART_...]] must be removed from fullAnalysis");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user