package com.ruoyi.app.pay; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.BeanWrapperImpl; import java.math.BigDecimal; import java.sql.Connection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; class RiderIncomeMapperTest { private static final String NS = "com.ruoyi.system.mapper.RiderIncomeMapper."; private SqlSessionFactory factory; @BeforeEach void setUp() throws Exception { var source = new JdbcDataSource(); source.setURL("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1"); try (Connection connection = source.getConnection(); var sql = connection.createStatement()) { sql.execute("CREATE TABLE user_billing (id BIGINT PRIMARY KEY, user_id BIGINT, user_type VARCHAR(8), " + "type VARCHAR(8), state VARCHAR(8), dd_id VARCHAR(64), order_source VARCHAR(16), " + "amount DOUBLE, behalf_amount DOUBLE, cretim TIMESTAMP)"); sql.execute("INSERT INTO user_billing VALUES " + "(1,7,'2','0','0','same',NULL,60.10,300,'2026-01-01 00:00:00')," + "(2,7,'2','0','0','same','FLASH',120,0,'2026-01-01 12:00:00')," + "(3,7,'2','0','0','missing','',0,0,'2026-01-01 12:00:00')," + "(4,8,'2','0','0','other','FOOD',999,0,'2026-01-01 12:00:00')," + "(5,7,'2','2','0','withdraw','FOOD',999,0,'2026-01-01 12:00:00')," + "(6,7,'2','0','1','void','FOOD',999,0,'2026-01-01 12:00:00')," + "(7,7,'1','0','0','merchant','FOOD',999,0,'2026-01-01 12:00:00')," + "(8,7,'2','0','0','outside','FOOD',999,0,'2026-01-02 00:00:00')," + "(9,7,'2','0','0',NULL,'FOOD',999,0,'2026-01-01 12:00:00')," + "(10,7,'2','0','0','unknown','OTHER',999,0,'2026-01-01 12:00:00')," + "(11,7,'2','0','0','foreign','FOOD',0.20,0,'2026-01-01 23:59:59')," + "(12,7,'2','0','0','before','FOOD',999,0,'2025-12-31 23:59:59')"); } var configuration = new Configuration(new Environment("test", new JdbcTransactionFactory(), source)); String resource = "mapper/Billing/RiderIncomeMapper.xml"; try (var input = getClass().getClassLoader().getResourceAsStream(resource)) { assertNotNull(input, "骑手收入 Mapper 尚未实现"); new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse(); } factory = new SqlSessionFactoryBuilder().build(configuration); } private Map params(String source) { var params = new HashMap(); params.put("riderId", 7L); params.put("startTime", "2026-01-01 00:00:00"); params.put("endTime", "2026-01-01 23:59:59"); params.put("orderSource", source); params.put("offset", 0L); params.put("size", 100); return params; } private Object value(Object row, String field) { return new BeanWrapperImpl(row).getPropertyValue(field); } @Test void summaryUsesOnlyOwnValidIncomeAndIgnoresCategoryAndCollection() { try (SqlSession session = factory.openSession()) { for (String source : List.of("ALL", "FOOD", "FLASH")) { Object result = session.selectOne(NS + "summarize", params(source)); assertEquals(new BigDecimal("180.30"), value(result, "totalIncome")); assertEquals(4L, value(result, "totalOrderCount")); assertEquals(3L, value(result, "foodOrderCount")); assertEquals(1L, value(result, "flashOrderCount")); } } } @Test void listSeparatesSameOrderNumbersAndPaginatesWithStableTimeAndIdOrder() { try (SqlSession session = factory.openSession()) { List all = session.selectList(NS + "selectPage", params("ALL")); assertEquals(List.of(11L, 3L, 2L, 1L), all.stream().map(x -> value(x, "billId")).toList()); assertEquals("foreign", value(all.get(0), "orderNo")); assertEquals("missing", value(all.get(1), "orderNo"), "无需订单表也能读取历史账单"); assertEquals("FOOD", value(all.get(1), "orderSource")); assertEquals("same", value(all.get(2), "orderNo")); assertEquals("FLASH", value(all.get(2), "orderSource")); assertEquals("same", value(all.get(3), "orderNo")); assertEquals("FOOD", value(all.get(3), "orderSource")); assertEquals(new BigDecimal("300.00"), value(all.get(3), "collectedAmount")); List food = session.selectList(NS + "selectPage", params("FOOD")); assertEquals(3, food.size()); List flash = session.selectList(NS + "selectPage", params("FLASH")); assertEquals(1, flash.size()); assertEquals(new BigDecimal("120.00"), value(flash.get(0), "incomeAmount")); Map page = params("ALL"); page.put("offset", 1L); page.put("size", 2); List rows = session.selectList(NS + "selectPage", page); assertEquals(List.of(3L, 2L), rows.stream().map(x -> value(x, "billId")).toList()); } } @Test void allTimeIncludesHistoryOutsideDailyRangeButStillExcludesOtherAccounts() { try (SqlSession session = factory.openSession()) { var params = params("ALL"); params.put("startTime", null); params.put("endTime", null); Object result = session.selectOne(NS + "summarize", params); assertEquals(new BigDecimal("2178.30"), value(result, "totalIncome")); assertEquals(6L, value(result, "totalOrderCount")); assertEquals(5L, value(result, "foodOrderCount")); assertEquals(1L, value(result, "flashOrderCount")); assertEquals(6, session.selectList(NS + "selectPage", params).size()); } } @Test void exactSecondRangeIncludesBothEndpointsWithoutExpandingToWholeDay() { try (SqlSession session = factory.openSession()) { var params = params("ALL"); params.put("startTime", "2026-01-01 12:00:00"); params.put("endTime", "2026-01-01 12:00:00"); List rows = session.selectList(NS + "selectPage", params); assertEquals(List.of(3L, 2L), rows.stream().map(x -> value(x, "billId")).toList()); Object sum = session.selectOne(NS + "summarize", params); assertEquals(new BigDecimal("120.00"), value(sum, "totalIncome")); assertEquals(2L, value(sum, "totalOrderCount")); } } @Test void detailScopesByRiderAndRejectsNonIncome() { try (SqlSession session = factory.openSession()) { for (long id : List.of(4L, 5L, 6L, 7L, 9L, 10L, 999L)) { assertNull(session.selectOne(NS + "selectDetail", Map.of("riderId", 7L, "billId", id))); } Object row = session.selectOne(NS + "selectDetail", Map.of("riderId", 7L, "billId", 1L)); assertEquals(new BigDecimal("60.10"), value(row, "incomeAmount")); assertEquals("same", value(row, "orderNo")); } } @Test void noDataReturnsZeroSummaryAndEmptyPage() { try (SqlSession session = factory.openSession()) { var params = params("ALL"); params.put("riderId", 999L); Object result = session.selectOne(NS + "summarize", params); assertEquals(new BigDecimal("0.00"), value(result, "totalIncome")); assertEquals(0L, value(result, "totalOrderCount")); assertEquals(0L, value(result, "foodOrderCount")); assertEquals(0L, value(result, "flashOrderCount")); assertTrue(session.selectList(NS + "selectPage", params).isEmpty()); } } }