package com.ruoyi.app.pay; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.system.domain.InfoUser; import com.ruoyi.system.domain.vo.RiderIncomeItem; import com.ruoyi.system.domain.vo.RiderIncomeSummary; import com.ruoyi.system.mapper.RiderIncomeMapper; import com.ruoyi.system.service.IInfoUserService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import java.math.BigDecimal; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; class RiderIncomeServiceTest { private final RiderIncomeMapper mapper = mock(RiderIncomeMapper.class); private final IInfoUserService users = mock(IInfoUserService.class); private final RiderIncomeService service = new RiderIncomeService(mapper, users); private MockedStatic messages; private InfoUser rider; private RiderIncomeSummary summary; @BeforeEach void setUp() { messages = mockStatic(MessageUtils.class); messages.when(() -> MessageUtils.message(anyString())).thenAnswer(call -> call.getArgument(0)); rider = new InfoUser(); rider.setUserId(7L); rider.setUserType("2"); rider.setStatus("0"); rider.setDelFlag("0"); when(users.selectInfoUserByUserId(7L)).thenReturn(rider); summary = new RiderIncomeSummary(); summary.setTotalIncome(new BigDecimal("1250.00")); summary.setTotalOrderCount(18L); summary.setFoodOrderCount(12L); summary.setFlashOrderCount(6L); when(mapper.summarize(eq(7L), anyString(), anyString())).thenReturn(summary); when(mapper.selectPage(eq(7L), anyString(), anyString(), anyString(), anyLong(), anyInt())) .thenReturn(List.of()); } @AfterEach void tearDown() { messages.close(); } @Test void categoriesOnlyChangeListAndTotalNotTopSummary() { for (String source : List.of("ALL", "FOOD", "FLASH")) { var result = service.list(7L, "2026-11-05 00:00:00", "2026-11-05 23:59:59", source, 2, 10); assertSame(summary, result.summary()); assertEquals(source.equals("ALL") ? 18L : source.equals("FOOD") ? 12L : 6L, result.total()); verify(mapper).selectPage(7L, "2026-11-05 00:00:00", "2026-11-05 23:59:59", source, 10L, 10); } verify(mapper, times(3)).summarize(7L, "2026-11-05 00:00:00", "2026-11-05 23:59:59"); } @Test void allTimeHasNoTimeBoundsAndStillKeepsSummaryIndependent() { when(mapper.summarize(7L, null, null)).thenReturn(summary); when(mapper.selectPage(7L, null, null, "FLASH", 0L, 20)).thenReturn(List.of()); var result = service.list(7L, null, null, "FLASH", 1, 20); assertNull(result.startTime()); assertNull(result.endTime()); assertSame(summary, result.summary()); assertEquals(6L, result.total()); verify(mapper).selectPage(7L, null, null, "FLASH", 0L, 20); } @Test void explicitCrossYearRangeIsNotReinterpreted() { var result = service.list(7L, "2025-12-29 00:00:00", "2026-01-04 23:59:59", "ALL", 1, 20); assertEquals("2025-12-29 00:00:00", result.startTime()); assertEquals("2026-01-04 23:59:59", result.endTime()); verify(mapper).summarize(7L, "2025-12-29 00:00:00", "2026-01-04 23:59:59"); } @Test void leapDateAndYearEndArePassedThroughWithoutAddingADay() { service.list(7L, "2024-02-01 00:00:00", "2024-02-29 23:59:59", "ALL", 1, 20); verify(mapper).summarize(7L, "2024-02-01 00:00:00", "2024-02-29 23:59:59"); service.list(7L, "9999-12-31 00:00:00", "9999-12-31 23:59:59", "ALL", 1, 20); verify(mapper).summarize(7L, "9999-12-31 00:00:00", "9999-12-31 23:59:59"); } @Test void arbitraryTimeRangeIsUsedExactlyAndResponseHasNoPeriodOptions() { var result = service.list(7L, "2026-11-05 12:30:15", "2026-11-05 18:45:30", "ALL", 1, 20); assertEquals("2026-11-05 12:30:15", result.startTime()); assertEquals("2026-11-05 18:45:30", result.endTime()); verify(mapper).summarize(7L, "2026-11-05 12:30:15", "2026-11-05 18:45:30"); var json = new ObjectMapper().valueToTree(result); assertFalse(json.has("period")); assertFalse(json.has("periodOptions")); assertFalse(json.has("startDate")); assertFalse(json.has("endDate")); } @Test void invalidTimesRangesCategoriesAndPaginationAreRejectedBeforeSql() { String valid = "2026-11-05 12:00:00"; for (String time : List.of("2026-02-30 00:00:00", "2026-1-1 00:00:00", "", "2026-11-05", "2026-11-05 23:59", "2026-11-05 24:00:00", "2026-11-05 12:60:00", "2026-11-05 12:00:60", "2026-11-05 12:00:00.123", "2026-11-05 12:00:00' OR 1=1")) { assertThrows(ServiceException.class, () -> service.list(7L, time, valid, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, valid, time, "ALL", 1, 20)); } assertThrows(ServiceException.class, () -> service.list(7L, null, valid, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, valid, null, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, "2026-11-05 12:00:01", valid, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, "0999-01-01 00:00:00", valid, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, valid, valid, "OTHER", 1, 20)); assertThrows(ServiceException.class, () -> service.list(7L, valid, valid, "ALL", 0, 20)); assertThrows(ServiceException.class, () -> service.list(7L, valid, valid, "ALL", 1, 0)); assertThrows(ServiceException.class, () -> service.list(7L, valid, valid, "ALL", 1, 101)); verifyNoInteractions(mapper); } @Test void nonRidersDeletedDisabledAndMissingAccountsCannotRead() { for (String type : List.of("0", "1", "3", "4", "5")) { rider.setUserType(type); assertThrows(ServiceException.class, () -> service.list(7L, null, null, "ALL", 1, 20)); assertThrows(ServiceException.class, () -> service.detail(7L, 1L)); } rider.setUserType("2"); rider.setStatus("1"); assertThrows(ServiceException.class, () -> service.detail(7L, 1L)); rider.setStatus("0"); rider.setDelFlag("2"); assertThrows(ServiceException.class, () -> service.detail(7L, 1L)); when(users.selectInfoUserByUserId(7L)).thenReturn(null); assertThrows(ServiceException.class, () -> service.detail(7L, 1L)); verifyNoInteractions(mapper); } @Test void detailUsesAuthenticatedRiderAndHidesUnavailableRecords() { var item = new RiderIncomeItem(); item.setBillId(1L); when(mapper.selectDetail(7L, 1L)).thenReturn(item); assertSame(item, service.detail(7L, 1L)); assertEquals("rider.income.not.found", assertThrows(ServiceException.class, () -> service.detail(7L, 2L)).getMessage()); assertThrows(ServiceException.class, () -> service.detail(7L, 0L)); } @Test void largePageOffsetDoesNotOverflowInteger() { service.list(7L, "2026-11-05 00:00:00", "2026-11-05 23:59:59", "ALL", Integer.MAX_VALUE, 100); verify(mapper).selectPage(7L, "2026-11-05 00:00:00", "2026-11-05 23:59:59", "ALL", 214748364600L, 100); } @Test void responseHasNoWalletWithdrawalTipOrSettlementFields() throws Exception { var item = new RiderIncomeItem(); item.setIncomeAmount(new BigDecimal("120.00")); var json = new ObjectMapper().valueToTree(item); assertEquals(6, json.size()); assertTrue(json.has("billId")); assertTrue(json.has("orderNo")); assertTrue(json.has("orderSource")); assertFalse(json.has("orderId")); assertTrue(json.has("incomeTime")); assertTrue(json.has("collectedAmount")); assertEquals(0, new BigDecimal("120.00").compareTo(json.get("incomeAmount").decimalValue())); } }