package com.trafficaudit.holiday.service; import com.trafficaudit.holiday.service.HolidayStatService.DailyMetrics; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.function.Function; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class HolidayBulletinServiceTest { @Test void formatsTableRatiosAsSignedPercentages() throws Exception { HolidayBulletinService service = new HolidayBulletinService(null); assertEquals("-4.7%", ratio(service, "95.3", "100")); assertEquals("1.0%", ratio(service, "101", "100")); assertEquals("0%", ratio(service, "100", "100")); assertEquals("0%", ratio(service, "10004", "10000")); assertEquals("—", ratio(service, "100", null)); assertEquals("—", ratio(service, "100", "0")); } @Test void comparesCivilAviationOnlyThroughCoveredDay() throws Exception { HolidayStatService stat = mock(HolidayStatService.class); HolidayBulletinService service = new HolidayBulletinService(stat); DailyMetrics current = new DailyMetrics(); current.setYear(2026); current.setHolidayType("NATIONAL_DAY"); current.setDayIndex(2); DailyMetrics coveredDay = new DailyMetrics(); coveredDay.setCivilAviation(new BigDecimal("6.7482")); DailyMetrics missingDay = new DailyMetrics(); when(stat.dailyMetrics(2026, "NATIONAL_DAY", 2)).thenReturn(missingDay); when(stat.dailyMetrics(2026, "NATIONAL_DAY", 1)).thenReturn(coveredDay); DailyMetrics previousYear = new DailyMetrics(); previousYear.setYear(2025); DailyMetrics previousYearThroughDay1 = new DailyMetrics(); previousYearThroughDay1.setCivilAviation(new BigDecimal("7.5003")); when(stat.cumulativeMetrics(2025, "NATIONAL_DAY", 1)).thenReturn(previousYearThroughDay1); Object result = comparableCumulative(service, current, previousYear, DailyMetrics::getCivilAviation); assertSame(previousYearThroughDay1, result); verify(stat).cumulativeMetrics(2025, "NATIONAL_DAY", 1); verify(stat, never()).cumulativeMetrics(2025, "NATIONAL_DAY", 2); } @Test void comparableCumulativeIsNullWhenCurrentYearHasNoMetricData() throws Exception { HolidayStatService stat = mock(HolidayStatService.class); HolidayBulletinService service = new HolidayBulletinService(stat); DailyMetrics current = new DailyMetrics(); current.setYear(2026); current.setHolidayType("NATIONAL_DAY"); current.setDayIndex(2); when(stat.dailyMetrics(2026, "NATIONAL_DAY", 2)).thenReturn(new DailyMetrics()); when(stat.dailyMetrics(2026, "NATIONAL_DAY", 1)).thenReturn(new DailyMetrics()); Object result = comparableCumulative(service, current, new DailyMetrics(), DailyMetrics::getCivilAviation); assertNull(result); verify(stat, never()).cumulativeMetrics(0, "NATIONAL_DAY", 1); } @Test void cumulativeParagraphUsesWanUnitAndSingleRounding() throws Exception { HolidayBulletinService service = new HolidayBulletinService(null); DailyMetrics current = new DailyMetrics(); current.setTotalPersonFlow(new BigDecimal("3088.62958")); DailyMetrics previousYear = new DailyMetrics(); previousYear.setTotalPersonFlow(new BigDecimal("3232.42742")); DailyMetrics twoYearsAgo = new DailyMetrics(); twoYearsAgo.setTotalPersonFlow(new BigDecimal("3137.80860")); Method method = HolidayBulletinService.class.getDeclaredMethod( "cumulativeParagraph", DailyMetrics.class, DailyMetrics.class, DailyMetrics.class); method.setAccessible(true); String text = (String) method.invoke(service, current, previousYear, twoYearsAgo); assertEquals( "假期以来,全省全社会跨区域人员流动量累计3088.63万人次,比2025年同期下降4.4%,比2024年同期下降1.6%。", text); } private Object comparableCumulative(HolidayBulletinService service, DailyMetrics current, DailyMetrics comparisonYear, Function selector) throws Exception { Method method = HolidayBulletinService.class.getDeclaredMethod( "comparableCumulative", DailyMetrics.class, DailyMetrics.class, Function.class); method.setAccessible(true); return method.invoke(service, current, comparisonYear, selector); } private String ratio(HolidayBulletinService service, String current, String base) throws Exception { Method method = HolidayBulletinService.class.getDeclaredMethod("ratio", BigDecimal.class, BigDecimal.class); method.setAccessible(true); BigDecimal currentValue = current == null ? null : new BigDecimal(current); BigDecimal baseValue = base == null ? null : new BigDecimal(base); return (String) method.invoke(service, currentValue, baseValue); } }