integration-test-utils.ts 7.1 KB

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