2
0

integration-test-db.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import { DataSource } from 'typeorm';
  2. import { beforeEach, afterEach } from 'vitest';
  3. import { UserEntity } from '@d8d/server/modules/users/user.entity';
  4. import { Role } from '@d8d/server/modules/users/role.entity';
  5. import { ActivityEntity } from '@d8d/server/modules/activities/activity.entity';
  6. import { RouteEntity } from '@d8d/server/modules/routes/route.entity';
  7. import { LocationEntity } from '@d8d/server/modules/locations/location.entity';
  8. import { AreaEntity } from '@d8d/server/modules/areas/area.entity';
  9. import { VehicleType } from '@d8d/server/modules/routes/route.schema';
  10. import { Passenger } from '@d8d/server/modules/passengers/passenger.entity';
  11. import { IdType } from '@d8d/server/modules/passengers/passenger.schema';
  12. import { Order } from '@d8d/server/modules/orders/order.entity';
  13. import { OrderStatus, PaymentStatus } from '@d8d/server/share/order.types';
  14. import { AppDataSource } from '@d8d/server/data-source';
  15. /**
  16. * 集成测试数据库工具类 - 使用真实PostgreSQL数据库
  17. */
  18. export class IntegrationTestDatabase {
  19. /**
  20. * 清理集成测试数据库
  21. */
  22. static async cleanup(): Promise<void> {
  23. if (AppDataSource.isInitialized) {
  24. await AppDataSource.destroy();
  25. }
  26. }
  27. /**
  28. * 获取当前数据源
  29. */
  30. static async getDataSource(): Promise<DataSource> {
  31. if(!AppDataSource.isInitialized) {
  32. await AppDataSource.initialize();
  33. }
  34. return AppDataSource
  35. }
  36. }
  37. /**
  38. * 测试数据工厂类
  39. */
  40. export class TestDataFactory {
  41. /**
  42. * 创建测试用户数据
  43. */
  44. static createUserData(overrides: Partial<UserEntity> = {}): Partial<UserEntity> {
  45. const timestamp = Date.now();
  46. return {
  47. username: `testuser_${timestamp}`,
  48. password: 'TestPassword123!',
  49. email: `test_${timestamp}@example.com`,
  50. phone: `138${timestamp.toString().slice(-8)}`,
  51. nickname: `Test User ${timestamp}`,
  52. name: `Test Name ${timestamp}`,
  53. isDisabled: 0,
  54. isDeleted: 0,
  55. ...overrides
  56. };
  57. }
  58. /**
  59. * 创建测试角色数据
  60. */
  61. static createRoleData(overrides: Partial<Role> = {}): Partial<Role> {
  62. const timestamp = Date.now();
  63. return {
  64. name: `test_role_${timestamp}`,
  65. description: `Test role description ${timestamp}`,
  66. ...overrides
  67. };
  68. }
  69. /**
  70. * 创建测试区域数据
  71. */
  72. static createAreaData(overrides: Partial<AreaEntity> = {}): Partial<AreaEntity> {
  73. const timestamp = Date.now();
  74. return {
  75. name: `测试区域_${timestamp}`,
  76. code: `area_${timestamp}`,
  77. level: 1,
  78. parentId: null,
  79. isDisabled: 0,
  80. isDeleted: 0,
  81. ...overrides
  82. };
  83. }
  84. /**
  85. * 创建测试地点数据
  86. */
  87. static createLocationData(overrides: Partial<LocationEntity> = {}): Partial<LocationEntity> {
  88. const timestamp = Date.now();
  89. return {
  90. name: `北京测试地点_${timestamp}`,
  91. address: `北京市测试地址_${timestamp}`,
  92. provinceId: 0, // 将在创建时自动设置
  93. cityId: 0, // 将在创建时自动设置
  94. districtId: 0, // 将在创建时自动设置
  95. latitude: 39.9042,
  96. longitude: 116.4074,
  97. isDisabled: 0,
  98. isDeleted: 0,
  99. ...overrides
  100. };
  101. }
  102. /**
  103. * 在数据库中创建测试用户
  104. */
  105. static async createTestUser(dataSource: DataSource, overrides: Partial<UserEntity> = {}): Promise<UserEntity> {
  106. const userData = this.createUserData(overrides);
  107. const userRepository = dataSource.getRepository(UserEntity);
  108. const user = userRepository.create(userData);
  109. return await userRepository.save(user);
  110. }
  111. /**
  112. * 在数据库中创建测试角色
  113. */
  114. static async createTestRole(dataSource: DataSource, overrides: Partial<Role> = {}): Promise<Role> {
  115. const roleData = this.createRoleData(overrides);
  116. const roleRepository = dataSource.getRepository(Role);
  117. const role = roleRepository.create(roleData);
  118. return await roleRepository.save(role);
  119. }
  120. /**
  121. * 在数据库中创建测试区域
  122. */
  123. static async createTestArea(dataSource: DataSource, overrides: Partial<AreaEntity> = {}): Promise<AreaEntity> {
  124. const areaData = this.createAreaData(overrides);
  125. const areaRepository = dataSource.getRepository(AreaEntity);
  126. // 对于顶级区域(省/直辖市),parentId应该为null
  127. if (areaData.level === 1) {
  128. areaData.parentId = null;
  129. }
  130. // 对于市级区域,确保有对应的省级区域
  131. else if (areaData.level === 2 && !areaData.parentId) {
  132. const province = await this.createTestArea(dataSource, { level: 1 });
  133. areaData.parentId = province.id;
  134. }
  135. // 对于区县级区域,确保有对应的市级区域
  136. else if (areaData.level === 3 && !areaData.parentId) {
  137. const city = await this.createTestArea(dataSource, { level: 2 });
  138. areaData.parentId = city.id;
  139. }
  140. const area = areaRepository.create(areaData);
  141. return await areaRepository.save(area);
  142. }
  143. /**
  144. * 在数据库中创建测试地点
  145. */
  146. static async createTestLocation(dataSource: DataSource, overrides: Partial<LocationEntity> = {}): Promise<LocationEntity> {
  147. const locationData = this.createLocationData(overrides);
  148. const locationRepository = dataSource.getRepository(LocationEntity);
  149. // 确保关联的区域存在 - 按层级顺序创建
  150. if (!locationData.provinceId) {
  151. const province = await this.createTestArea(dataSource, { level: 1 });
  152. locationData.provinceId = province.id;
  153. }
  154. if (!locationData.cityId) {
  155. const city = await this.createTestArea(dataSource, { level: 2, parentId: locationData.provinceId });
  156. locationData.cityId = city.id;
  157. }
  158. if (!locationData.districtId) {
  159. const district = await this.createTestArea(dataSource, { level: 3, parentId: locationData.cityId });
  160. locationData.districtId = district.id;
  161. }
  162. const location = locationRepository.create(locationData);
  163. return await locationRepository.save(location);
  164. }
  165. /**
  166. * 创建测试活动数据
  167. */
  168. static createActivityData(overrides: Partial<ActivityEntity> = {}): Partial<ActivityEntity> {
  169. const timestamp = Date.now();
  170. const now = new Date();
  171. const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
  172. return {
  173. name: `测试活动_${timestamp}`,
  174. description: `测试活动描述_${timestamp}`,
  175. startDate: now,
  176. endDate: tomorrow,
  177. venueLocationId: 0, // 将在创建时自动设置
  178. isDisabled: 0,
  179. isDeleted: 0,
  180. ...overrides
  181. };
  182. }
  183. /**
  184. * 创建测试路线数据
  185. */
  186. static createRouteData(overrides: Partial<RouteEntity> = {}): Partial<RouteEntity> {
  187. const timestamp = Date.now();
  188. const now = new Date();
  189. const departureTime = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 2小时后
  190. return {
  191. name: `测试路线_${timestamp}`,
  192. startLocationId: 0, // 将在创建时自动设置
  193. endLocationId: 0, // 将在创建时自动设置
  194. pickupPoint: `上车点_${timestamp}`,
  195. dropoffPoint: `下车点_${timestamp}`,
  196. departureTime: departureTime,
  197. vehicleType: VehicleType.BUS,
  198. price: 100,
  199. seatCount: 40,
  200. availableSeats: 40,
  201. isDisabled: 0,
  202. isDeleted: 0,
  203. ...overrides
  204. };
  205. }
  206. /**
  207. * 在数据库中创建测试活动
  208. */
  209. static async createTestActivity(dataSource: DataSource, overrides: Partial<ActivityEntity> = {}): Promise<ActivityEntity> {
  210. const activityData = this.createActivityData(overrides);
  211. const activityRepository = dataSource.getRepository(ActivityEntity);
  212. // 如果没有提供venueLocationId,自动创建一个测试地点
  213. if (!activityData.venueLocationId || activityData.venueLocationId === 0) {
  214. const testLocation = await this.createTestLocation(dataSource);
  215. activityData.venueLocationId = testLocation.id;
  216. }
  217. const activity = activityRepository.create(activityData);
  218. return await activityRepository.save(activity);
  219. }
  220. /**
  221. * 在数据库中创建测试路线
  222. */
  223. static async createTestRoute(dataSource: DataSource, overrides: Partial<RouteEntity> = {}): Promise<RouteEntity> {
  224. const routeData = this.createRouteData(overrides);
  225. const routeRepository = dataSource.getRepository(RouteEntity);
  226. // 如果没有提供startLocationId,自动创建一个测试地点
  227. if (!routeData.startLocationId || routeData.startLocationId === 0) {
  228. const startLocation = await this.createTestLocation(dataSource);
  229. routeData.startLocationId = startLocation.id;
  230. }
  231. // 如果没有提供endLocationId,自动创建一个测试地点
  232. if (!routeData.endLocationId || routeData.endLocationId === 0) {
  233. const endLocation = await this.createTestLocation(dataSource);
  234. routeData.endLocationId = endLocation.id;
  235. }
  236. // 如果没有提供activityId,自动创建一个测试活动
  237. if (!routeData.activityId) {
  238. const testActivity = await this.createTestActivity(dataSource);
  239. routeData.activityId = testActivity.id;
  240. }
  241. const route = routeRepository.create(routeData);
  242. return await routeRepository.save(route);
  243. }
  244. /**
  245. * 创建测试乘客数据
  246. */
  247. static createPassengerData(overrides: Partial<Passenger> = {}): Partial<Passenger> {
  248. const timestamp = Date.now();
  249. return {
  250. name: `测试乘客_${timestamp}`,
  251. idType: IdType.ID_CARD,
  252. idNumber: `11010119900101${timestamp.toString().slice(-4)}`,
  253. phone: `138${timestamp.toString().slice(-8)}`,
  254. isDefault: false,
  255. ...overrides
  256. };
  257. }
  258. /**
  259. * 在数据库中创建测试乘客
  260. */
  261. static async createTestPassenger(dataSource: DataSource, overrides: Partial<Passenger> = {}): Promise<Passenger> {
  262. const passengerData = this.createPassengerData(overrides);
  263. const passengerRepository = dataSource.getRepository(Passenger);
  264. // 如果没有提供userId,自动创建一个测试用户
  265. if (!passengerData.userId) {
  266. const testUser = await this.createTestUser(dataSource);
  267. passengerData.userId = testUser.id;
  268. }
  269. const passenger = passengerRepository.create(passengerData);
  270. return await passengerRepository.save(passenger);
  271. }
  272. /**
  273. * 创建测试订单数据
  274. */
  275. static createOrderData(overrides: Partial<Order> = {}): Partial<Order> {
  276. const timestamp = Date.now();
  277. return {
  278. userId: 0, // 将在创建时自动设置
  279. routeId: 0, // 将在创建时自动设置
  280. passengerCount: 2,
  281. totalAmount: 200.00,
  282. status: OrderStatus.PENDING_PAYMENT,
  283. paymentStatus: PaymentStatus.PENDING,
  284. passengerSnapshots: [
  285. { name: '测试乘客1', idType: '身份证', idNumber: '110101199001011234' },
  286. { name: '测试乘客2', idType: '身份证', idNumber: '110101199001011235' }
  287. ],
  288. routeSnapshot: {
  289. name: '测试路线快照',
  290. description: '测试路线描述',
  291. price: 100.00,
  292. departureTime: new Date(Date.now() + 24 * 60 * 60 * 1000)
  293. },
  294. ...overrides
  295. };
  296. }
  297. /**
  298. * 在数据库中创建测试订单
  299. */
  300. static async createTestOrder(dataSource: DataSource, overrides: Partial<Order> = {}): Promise<Order> {
  301. const orderData = this.createOrderData(overrides);
  302. const orderRepository = dataSource.getRepository(Order);
  303. // 如果没有提供userId,自动创建一个测试用户
  304. if (!orderData.userId || orderData.userId === 0) {
  305. const testUser = await this.createTestUser(dataSource);
  306. orderData.userId = testUser.id;
  307. }
  308. // 如果没有提供routeId,自动创建一个测试路线
  309. if (!orderData.routeId || orderData.routeId === 0) {
  310. const testRoute = await this.createTestRoute(dataSource);
  311. orderData.routeId = testRoute.id;
  312. }
  313. const order = orderRepository.create(orderData);
  314. return await orderRepository.save(order);
  315. }
  316. }
  317. /**
  318. * 集成测试数据库生命周期钩子
  319. */
  320. export function setupIntegrationDatabaseHooks() {
  321. beforeEach(async () => {
  322. await IntegrationTestDatabase.getDataSource();
  323. });
  324. afterEach(async () => {
  325. await IntegrationTestDatabase.cleanup();
  326. });
  327. }