integration-test-utils.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { OpenAPIHono } from '@hono/zod-openapi';
  2. import { createApiClient, ApiClient } from './api-client';
  3. import { IntegrationTestDatabase } from './integration-test-db';
  4. import { DataSource } from 'typeorm';
  5. import { UserEntity } from '../modules/users/user.entity';
  6. import { Role } from '../modules/users/role.entity';
  7. import apiApp from '../api';
  8. /**
  9. * 集成测试配置选项
  10. */
  11. export interface IntegrationTestOptions {
  12. setupDatabase?: boolean;
  13. setupAuth?: boolean;
  14. setupMiddlewares?: boolean;
  15. }
  16. /**
  17. * 集成测试上下文
  18. */
  19. export interface IntegrationTestContext {
  20. app: OpenAPIHono;
  21. client: ApiClient;
  22. dataSource: DataSource | null;
  23. }
  24. /**
  25. * 创建集成测试应用实例
  26. */
  27. export async function createIntegrationTestApp(
  28. routes: any[],
  29. options: IntegrationTestOptions = {}
  30. ): Promise<OpenAPIHono> {
  31. // 使用主API应用
  32. return apiApp;
  33. }
  34. /**
  35. * 创建集成测试客户端
  36. */
  37. export function createIntegrationTestClient(
  38. app: OpenAPIHono,
  39. options: IntegrationTestOptions = {}
  40. ): ApiClient {
  41. const client = createApiClient(app, {
  42. baseURL: 'http://localhost:3000/api/v1'
  43. });
  44. // 设置默认认证头(如果需要)
  45. if (options.setupAuth !== false) {
  46. client.setAuthToken('test-integration-token');
  47. }
  48. return client;
  49. }
  50. /**
  51. * 设置集成测试环境
  52. */
  53. export async function setupIntegrationTestEnvironment(
  54. routes: any[],
  55. options: IntegrationTestOptions = {}
  56. ): Promise<IntegrationTestContext> {
  57. const {
  58. setupDatabase = true,
  59. setupAuth = true,
  60. setupMiddlewares = true
  61. } = options;
  62. // 创建测试应用
  63. const app = await createIntegrationTestApp(routes, options);
  64. // 初始化数据库(如果需要)
  65. let dataSource: DataSource | null = null;
  66. if (setupDatabase) {
  67. dataSource = await IntegrationTestDatabase.initialize();
  68. }
  69. // 创建API客户端
  70. const client = createIntegrationTestClient(app, { setupAuth });
  71. return {
  72. app,
  73. client,
  74. dataSource
  75. };
  76. }
  77. /**
  78. * 清理集成测试环境
  79. */
  80. export async function cleanupIntegrationTestEnvironment(): Promise<void> {
  81. await IntegrationTestDatabase.clearAllData();
  82. await IntegrationTestDatabase.cleanup();
  83. }
  84. /**
  85. * 测试数据工厂函数
  86. */
  87. export class TestDataFactory {
  88. /**
  89. * 创建测试用户
  90. */
  91. static async createTestUser(userData: Partial<UserEntity> = {}): Promise<UserEntity> {
  92. const dataSource = IntegrationTestDatabase.getDataSource();
  93. if (!dataSource) {
  94. throw new Error('Database not initialized');
  95. }
  96. const userRepository = dataSource.getRepository(UserEntity);
  97. const defaultUser: Partial<UserEntity> = {
  98. username: `testuser_${Date.now()}`,
  99. email: `test${Date.now()}@example.com`,
  100. password: 'testpassword123',
  101. firstName: 'Test',
  102. lastName: 'User',
  103. isActive: true,
  104. ...userData
  105. };
  106. const user = userRepository.create(defaultUser);
  107. return await userRepository.save(user);
  108. }
  109. /**
  110. * 创建测试管理员用户
  111. */
  112. static async createTestAdmin(userData: Partial<UserEntity> = {}): Promise<UserEntity> {
  113. const dataSource = IntegrationTestDatabase.getDataSource();
  114. if (!dataSource) {
  115. throw new Error('Database not initialized');
  116. }
  117. const userRepository = dataSource.getRepository(UserEntity);
  118. const roleRepository = dataSource.getRepository(Role);
  119. // 确保管理员角色存在
  120. let adminRole = await roleRepository.findOne({ where: { name: 'admin' } });
  121. if (!adminRole) {
  122. adminRole = roleRepository.create({ name: 'admin', description: 'Administrator' });
  123. adminRole = await roleRepository.save(adminRole);
  124. }
  125. const defaultUser: Partial<UserEntity> = {
  126. username: `admin_${Date.now()}`,
  127. email: `admin${Date.now()}@example.com`,
  128. password: 'adminpassword123',
  129. firstName: 'Admin',
  130. lastName: 'User',
  131. isActive: true,
  132. roles: [adminRole],
  133. ...userData
  134. };
  135. const user = userRepository.create(defaultUser);
  136. return await userRepository.save(user);
  137. }
  138. /**
  139. * 创建测试角色
  140. */
  141. static async createTestRole(roleData: Partial<Role> = {}): Promise<Role> {
  142. const dataSource = IntegrationTestDatabase.getDataSource();
  143. if (!dataSource) {
  144. throw new Error('Database not initialized');
  145. }
  146. const roleRepository = dataSource.getRepository(Role);
  147. const defaultRole: Partial<Role> = {
  148. name: `role_${Date.now()}`,
  149. description: 'Test Role',
  150. ...roleData
  151. };
  152. const role = roleRepository.create(defaultRole);
  153. return await roleRepository.save(role);
  154. }
  155. /**
  156. * 清空所有测试数据
  157. */
  158. static async clearAllTestData(): Promise<void> {
  159. await IntegrationTestDatabase.clearAllData();
  160. }
  161. }
  162. /**
  163. * 集成测试断言工具
  164. */
  165. export class IntegrationTestAssertions {
  166. /**
  167. * 断言响应状态码
  168. */
  169. static expectStatus(response: any, expectedStatus: number): void {
  170. if (response.status !== expectedStatus) {
  171. throw new Error(`Expected status ${expectedStatus}, but got ${response.status}. Response: ${JSON.stringify(response.data)}`);
  172. }
  173. }
  174. /**
  175. * 断言响应包含特定字段
  176. */
  177. static expectResponseToHave(response: any, expectedFields: Record<string, any>): void {
  178. for (const [key, value] of Object.entries(expectedFields)) {
  179. if ((response.data as any)[key] !== value) {
  180. throw new Error(`Expected field ${key} to be ${value}, but got ${(response.data as any)[key]}`);
  181. }
  182. }
  183. }
  184. /**
  185. * 断言响应包含特定结构
  186. */
  187. static expectResponseStructure(response: any, structure: Record<string, any>): void {
  188. for (const key of Object.keys(structure)) {
  189. if (!(key in response.data)) {
  190. throw new Error(`Expected response to have key: ${key}`);
  191. }
  192. }
  193. }
  194. /**
  195. * 断言用户存在于数据库中
  196. */
  197. static async expectUserToExist(username: string): Promise<void> {
  198. const dataSource = IntegrationTestDatabase.getDataSource();
  199. if (!dataSource) {
  200. throw new Error('Database not initialized');
  201. }
  202. const userRepository = dataSource.getRepository(UserEntity);
  203. const user = await userRepository.findOne({ where: { username } });
  204. if (!user) {
  205. throw new Error(`Expected user ${username} to exist in database`);
  206. }
  207. }
  208. /**
  209. * 断言用户不存在于数据库中
  210. */
  211. static async expectUserNotToExist(username: string): Promise<void> {
  212. const dataSource = IntegrationTestDatabase.getDataSource();
  213. if (!dataSource) {
  214. throw new Error('Database not initialized');
  215. }
  216. const userRepository = dataSource.getRepository(UserEntity);
  217. const user = await userRepository.findOne({ where: { username } });
  218. if (user) {
  219. throw new Error(`Expected user ${username} not to exist in database`);
  220. }
  221. }
  222. }
  223. /**
  224. * 集成测试生命周期钩子
  225. */
  226. export function setupIntegrationTestHooks() {
  227. beforeEach(async () => {
  228. await IntegrationTestDatabase.initialize();
  229. });
  230. afterEach(async () => {
  231. await IntegrationTestDatabase.clearAllData();
  232. });
  233. afterAll(async () => {
  234. await IntegrationTestDatabase.cleanup();
  235. });
  236. }