package com.trafficaudit.holiday.service; import org.junit.jupiter.api.Test; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class HolidayTrendChartServiceTest { @Test void rendersLinePieAndStackedChartsAsPng() throws Exception { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries first = new HolidayTrendChartService.ChartSeries(); first.setName("2026年"); first.setValues(java.util.Arrays.asList( java.math.BigDecimal.ONE, java.math.BigDecimal.TEN, null)); HolidayTrendChartService.ChartSeries second = new HolidayTrendChartService.ChartSeries(); second.setName("2025年"); second.setValues(java.util.Arrays.asList( java.math.BigDecimal.valueOf(2), java.math.BigDecimal.valueOf(8), java.math.BigDecimal.valueOf(4))); byte[] line = service.lineChart("万人次", java.util.Arrays.asList(first, second), false); assertPng(line); assertNoLargeLightGrayBackground(line); HolidayTrendChartService.ChartCategory category = new HolidayTrendChartService.ChartCategory(); category.setName("铁路"); category.setValue(java.math.BigDecimal.TEN); byte[] pie = service.pieChart("人次", java.util.Collections.singletonList(category), 400, 300); assertPng(pie); byte[] stacked = service.stackedBarChart("占比", java.util.Arrays.asList("10月1日", "10月2日"), java.util.Arrays.asList(first, second), true, 500, 320); assertPng(stacked); assertNoLargeLightGrayBackground(stacked); } @Test void roundsLineDataToTheSamePrecisionAsLabels() { assertEquals(new BigDecimal("0.49"), HolidayTrendChartService.displayValue(new BigDecimal("0.4904"), false, 2)); assertEquals(new BigDecimal("0.49"), HolidayTrendChartService.displayValue(new BigDecimal("0.4911"), false, 2)); assertEquals(new BigDecimal("0.491"), HolidayTrendChartService.displayValue(new BigDecimal("0.4911"), false, 3)); assertEquals(new BigDecimal("0.123"), HolidayTrendChartService.displayValue(new BigDecimal("0.1234"), true, 2)); } @Test void rendersSameGeometryWhenValuesRoundToSameLabel() { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries first = new HolidayTrendChartService.ChartSeries(); first.setName("平均拥挤度"); first.setValues(java.util.Arrays.asList( new BigDecimal("0.4904"), new BigDecimal("0.4923"), new BigDecimal("0.5100"))); HolidayTrendChartService.ChartSeries second = new HolidayTrendChartService.ChartSeries(); second.setName("平均拥挤度"); second.setValues(java.util.Arrays.asList( new BigDecimal("0.4911"), new BigDecimal("0.4934"), new BigDecimal("0.5100"))); byte[] firstChart = service.lineChart("拥挤度", java.util.Collections.singletonList(first), false, 2, 1100, 590); byte[] secondChart = service.lineChart("拥挤度", java.util.Collections.singletonList(second), false, 2, 1100, 590); assertPng(firstChart); assertPng(secondChart); assertArrayEquals(firstChart, secondChart); } @Test void scalesLineChartHeightForBetterVerticalSeparation() throws Exception { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries series = new HolidayTrendChartService.ChartSeries(); series.setName("测试"); series.setValues(java.util.Arrays.asList( new BigDecimal("1.00"), new BigDecimal("2.00"), new BigDecimal("3.00"))); byte[] png = service.lineChart("万人次", java.util.Collections.singletonList(series), false, 1100, 590); BufferedImage image = ImageIO.read(new ByteArrayInputStream(png)); assertNotNull(image); assertEquals(HolidayTrendChartService.scaleLineChartHeight(590), image.getHeight()); } @Test void rendersHigherPrecisionLineLabelsWhenRequested() { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries congestion = new HolidayTrendChartService.ChartSeries(); congestion.setName("平均拥挤度"); congestion.setValues(java.util.Arrays.asList( new java.math.BigDecimal("0.467029"), new java.math.BigDecimal("0.490890"), new java.math.BigDecimal("0.492414"))); byte[] twoDecimals = service.lineChart("拥挤度", java.util.Collections.singletonList(congestion), false, 2, 1100, 590); byte[] threeDecimals = service.lineChart("拥挤度", java.util.Collections.singletonList(congestion), false, 3, 1100, 590); assertPng(twoDecimals); assertPng(threeDecimals); assertFalse(java.util.Arrays.equals(twoDecimals, threeDecimals)); } @Test void rendersLineChartWithConfiguredDayLabels() { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries congestion = new HolidayTrendChartService.ChartSeries(); congestion.setName("\u5e73\u5747\u62e5\u6324\u5ea6"); congestion.setValues(java.util.Arrays.asList( new BigDecimal("0.50"), new BigDecimal("0.48"), new BigDecimal("0.47"), new BigDecimal("0.49"), new BigDecimal("0.49"), new BigDecimal("0.49"), new BigDecimal("0.47"))); java.util.List sevenDays = java.util.Arrays.asList( "10\u67081\u65e5", "10\u67082\u65e5", "10\u67083\u65e5", "10\u67084\u65e5", "10\u67085\u65e5", "10\u67086\u65e5", "10\u67087\u65e5"); byte[] custom = service.lineChart("\u62e5\u6324\u5ea6", java.util.Collections.singletonList(congestion), false, 2, sevenDays, 1100, 590); byte[] defaults = service.lineChart("\u62e5\u6324\u5ea6", java.util.Collections.singletonList(congestion), false, 2, 1100, 590); assertPng(custom); assertPng(defaults); assertFalse(java.util.Arrays.equals(custom, defaults), "custom day labels should change the rendered axis"); } @Test void pinsRequestedLineLabelToFixedAnchor() throws Exception { HolidayTrendChartService service = new HolidayTrendChartService(); HolidayTrendChartService.ChartSeries series = new HolidayTrendChartService.ChartSeries(); series.setName("2026\u5e74"); series.setValues(java.util.Collections.singletonList(new BigDecimal("72.4"))); java.util.List single = java.util.Collections.singletonList(series); byte[] plain = service.lineChart("\u4e07\u4eba\u6b21", single, false, 2, 1100, 590); byte[] emptyOverrides = service.lineChart("\u4e07\u4eba\u6b21", single, false, 2, HolidayTrendChartService.DAY_LABELS, 1100, 590, java.util.Collections.emptyList()); assertArrayEquals(plain, emptyOverrides, "an empty override list must keep the automatic placement"); byte[] unmatched = service.lineChart("\u4e07\u4eba\u6b21", single, false, 2, HolidayTrendChartService.DAY_LABELS, 1100, 590, java.util.Collections.singletonList(new HolidayTrendChartService.LineLabelOverride( 0, 7, HolidayTrendChartService.LineLabelAnchor.ABOVE))); assertArrayEquals(plain, unmatched, "an override for a label that does not exist must not change rendering"); byte[] above = service.lineChart("\u4e07\u4eba\u6b21", single, false, 2, HolidayTrendChartService.DAY_LABELS, 1100, 590, java.util.Collections.singletonList(new HolidayTrendChartService.LineLabelOverride( 0, 0, HolidayTrendChartService.LineLabelAnchor.ABOVE))); byte[] below = service.lineChart("\u4e07\u4eba\u6b21", single, false, 2, HolidayTrendChartService.DAY_LABELS, 1100, 590, java.util.Collections.singletonList(new HolidayTrendChartService.LineLabelOverride( 0, 0, HolidayTrendChartService.LineLabelAnchor.BELOW))); assertPng(above); assertPng(below); assertFalse(java.util.Arrays.equals(above, below), "ABOVE and BELOW anchors must render differently"); assertTrue(meanSeriesColorY(above) < meanSeriesColorY(below), "the ABOVE anchor must place the label higher than the BELOW anchor"); } private double meanSeriesColorY(byte[] data) throws Exception { BufferedImage image = ImageIO.read(new ByteArrayInputStream(data)); assertNotNull(image); int target = 0xC0504D; long count = 0; long sum = 0; for (int y = 0; y < image.getHeight() - 60; y++) { for (int x = 0; x < image.getWidth(); x++) { if ((image.getRGB(x, y) & 0xFFFFFF) == target) { count++; sum += y; } } } assertTrue(count > 0, "expected series-coloured pixels for the item label"); return (double) sum / count; } private void assertNoLargeLightGrayBackground(byte[] data) throws Exception { BufferedImage image = ImageIO.read(new ByteArrayInputStream(data)); int matches = 0; for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { int rgb = image.getRGB(x, y) & 0xFFFFFF; if (rgb == 0xF3F4F3) matches++; } } assertTrue(matches < image.getWidth() * image.getHeight() / 20, "chart still contains a large light-gray background area"); } private void assertPng(byte[] data) { assertNotNull(data); assertTrue(data.length > 100); assertTrue(data[0] == (byte) 0x89 && data[1] == 0x50 && data[2] == 0x4e && data[3] == 0x47); } }