2
0

integration-test-db.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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, ActivityType } 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. type: ActivityType.DEPARTURE,
  176. startDate: now,
  177. endDate: tomorrow,
  178. venueLocationId: 0, // 将在创建时自动设置
  179. isDisabled: 0,
  180. isDeleted: 0,
  181. ...overrides
  182. };
  183. }
  184. /**
  185. * 创建测试路线数据
  186. */
  187. static createRouteData(overrides: Partial<RouteEntity> = {}): Partial<RouteEntity> {
  188. const timestamp = Date.now();
  189. const now = new Date();
  190. const departureTime = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 2小时后
  191. return {
  192. name: `测试路线_${timestamp}`,
  193. startLocationId: 0, // 将在创建时自动设置
  194. endLocationId: 0, // 将在创建时自动设置
  195. pickupPoint: `上车点_${timestamp}`,
  196. dropoffPoint: `下车点_${timestamp}`,
  197. departureTime: departureTime,
  198. vehicleType: VehicleType.BUS,
  199. price: 100,
  200. seatCount: 40,
  201. availableSeats: 40,
  202. isDisabled: 0,
  203. isDeleted: 0,
  204. ...overrides
  205. };
  206. }
  207. /**
  208. * 在数据库中创建测试活动
  209. */
  210. static async createTestActivity(dataSource: DataSource, overrides: Partial<ActivityEntity> = {}): Promise<ActivityEntity> {
  211. const activityData = this.createActivityData(overrides);
  212. const activityRepository = dataSource.getRepository(ActivityEntity);
  213. // 如果没有提供venueLocationId,自动创建一个测试地点
  214. if (!activityData.venueLocationId || activityData.venueLocationId === 0) {
  215. const testLocation = await this.createTestLocation(dataSource);
  216. activityData.venueLocationId = testLocation.id;
  217. }
  218. const activity = activityRepository.create(activityData);
  219. return await activityRepository.save(activity);
  220. }
  221. /**
  222. * 在数据库中创建测试路线
  223. */
  224. static async createTestRoute(dataSource: DataSource, overrides: Partial<RouteEntity> = {}): Promise<RouteEntity> {
  225. const routeData = this.createRouteData(overrides);
  226. const routeRepository = dataSource.getRepository(RouteEntity);
  227. // 如果没有提供startLocationId,自动创建一个测试地点
  228. if (!routeData.startLocationId || routeData.startLocationId === 0) {
  229. const startLocation = await this.createTestLocation(dataSource);
  230. routeData.startLocationId = startLocation.id;
  231. }
  232. // 如果没有提供endLocationId,自动创建一个测试地点
  233. if (!routeData.endLocationId || routeData.endLocationId === 0) {
  234. const endLocation = await this.createTestLocation(dataSource);
  235. routeData.endLocationId = endLocation.id;
  236. }
  237. // 如果没有提供activityId,自动创建一个测试活动
  238. if (!routeData.activityId) {
  239. const testActivity = await this.createTestActivity(dataSource);
  240. routeData.activityId = testActivity.id;
  241. }
  242. const route = routeRepository.create(routeData);
  243. return await routeRepository.save(route);
  244. }
  245. /**
  246. * 创建测试乘客数据
  247. */
  248. static createPassengerData(overrides: Partial<Passenger> = {}): Partial<Passenger> {
  249. const timestamp = Date.now();
  250. return {
  251. name: `测试乘客_${timestamp}`,
  252. idType: IdType.ID_CARD,
  253. idNumber: `11010119900101${timestamp.toString().slice(-4)}`,
  254. phone: `138${timestamp.toString().slice(-8)}`,
  255. isDefault: false,
  256. ...overrides
  257. };
  258. }
  259. /**
  260. * 在数据库中创建测试乘客
  261. */
  262. static async createTestPassenger(dataSource: DataSource, overrides: Partial<Passenger> = {}): Promise<Passenger> {
  263. const passengerData = this.createPassengerData(overrides);
  264. const passengerRepository = dataSource.getRepository(Passenger);
  265. // 如果没有提供userId,自动创建一个测试用户
  266. if (!passengerData.userId) {
  267. const testUser = await this.createTestUser(dataSource);
  268. passengerData.userId = testUser.id;
  269. }
  270. const passenger = passengerRepository.create(passengerData);
  271. return await passengerRepository.save(passenger);
  272. }
  273. /**
  274. * 创建测试订单数据
  275. */
  276. static createOrderData(overrides: Partial<Order> = {}): Partial<Order> {
  277. const timestamp = Date.now();
  278. return {
  279. userId: 0, // 将在创建时自动设置
  280. routeId: 0, // 将在创建时自动设置
  281. passengerCount: 2,
  282. totalAmount: 200.00,
  283. status: OrderStatus.PENDING_PAYMENT,
  284. paymentStatus: PaymentStatus.PENDING,
  285. passengerSnapshots: [
  286. { name: '测试乘客1', idType: '身份证', idNumber: '110101199001011234' },
  287. { name: '测试乘客2', idType: '身份证', idNumber: '110101199001011235' }
  288. ],
  289. routeSnapshot: {
  290. name: '测试路线快照',
  291. description: '测试路线描述',
  292. price: 100.00,
  293. departureTime: new Date(Date.now() + 24 * 60 * 60 * 1000)
  294. },
  295. ...overrides
  296. };
  297. }
  298. /**
  299. * 在数据库中创建测试订单
  300. */
  301. static async createTestOrder(dataSource: DataSource, overrides: Partial<Order> = {}): Promise<Order> {
  302. const orderData = this.createOrderData(overrides);
  303. const orderRepository = dataSource.getRepository(Order);
  304. // 如果没有提供userId,自动创建一个测试用户
  305. if (!orderData.userId || orderData.userId === 0) {
  306. const testUser = await this.createTestUser(dataSource);
  307. orderData.userId = testUser.id;
  308. }
  309. // 如果没有提供routeId,自动创建一个测试路线
  310. if (!orderData.routeId || orderData.routeId === 0) {
  311. const testRoute = await this.createTestRoute(dataSource);
  312. orderData.routeId = testRoute.id;
  313. }
  314. const order = orderRepository.create(orderData);
  315. return await orderRepository.save(order);
  316. }
  317. }
  318. /**
  319. * 集成测试数据库生命周期钩子
  320. */
  321. export function setupIntegrationDatabaseHooks() {
  322. beforeEach(async () => {
  323. await IntegrationTestDatabase.getDataSource();
  324. });
  325. afterEach(async () => {
  326. await IntegrationTestDatabase.cleanup();
  327. });
  328. }