user-routes.integration.test.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
  4. import { JWTUtil } from '@d8d/shared-utils';
  5. import { UserEntity, Role } from '@d8d/user-module';
  6. import { userSupplierRoutes } from '../../src/routes';
  7. import { Supplier } from '../../src/entities';
  8. // 设置集成测试钩子
  9. setupIntegrationDatabaseHooksWithEntities([UserEntity, Role, Supplier])
  10. describe('用户供应商管理API集成测试', () => {
  11. let client: ReturnType<typeof testClient<typeof userSupplierRoutes>>;
  12. let userToken: string;
  13. let otherUserToken: string;
  14. let testUser: UserEntity;
  15. let otherUser: UserEntity;
  16. beforeEach(async () => {
  17. // 创建测试客户端
  18. client = testClient(userSupplierRoutes);
  19. // 获取数据源
  20. const dataSource = await IntegrationTestDatabase.getDataSource();
  21. // 创建测试用户
  22. const userRepository = dataSource.getRepository(UserEntity);
  23. testUser = userRepository.create({
  24. username: `test_user_${Date.now()}`,
  25. password: 'test_password',
  26. nickname: '测试用户',
  27. registrationSource: 'web'
  28. });
  29. await userRepository.save(testUser);
  30. // 创建其他用户
  31. otherUser = userRepository.create({
  32. username: `other_user_${Date.now()}`,
  33. password: 'other_password',
  34. nickname: '其他用户',
  35. registrationSource: 'web'
  36. });
  37. await userRepository.save(otherUser);
  38. // 生成测试用户的token
  39. userToken = JWTUtil.generateToken({
  40. id: testUser.id,
  41. username: testUser.username,
  42. roles: [{name:'user'}]
  43. });
  44. // 生成其他用户的token
  45. otherUserToken = JWTUtil.generateToken({
  46. id: otherUser.id,
  47. username: otherUser.username,
  48. roles: [{name:'user'}]
  49. });
  50. });
  51. describe('GET /suppliers', () => {
  52. it('应该返回当前用户的供应商列表', async () => {
  53. // 为测试用户创建一些供应商
  54. const dataSource = await IntegrationTestDatabase.getDataSource();
  55. const supplierRepository = dataSource.getRepository(Supplier);
  56. const userSupplier1 = supplierRepository.create({
  57. name: '用户供应商1',
  58. username: `user_supplier1_${Date.now()}`,
  59. password: 'password123',
  60. phone: '13800138001',
  61. realname: '用户供应商1',
  62. loginNum: 0,
  63. loginTime: 0,
  64. loginIp: null,
  65. lastLoginTime: 0,
  66. lastLoginIp: null,
  67. state: 1,
  68. createdBy: testUser.id
  69. });
  70. await supplierRepository.save(userSupplier1);
  71. const userSupplier2 = supplierRepository.create({
  72. name: '用户供应商2',
  73. username: `user_supplier2_${Date.now()}`,
  74. password: 'password123',
  75. phone: '13800138002',
  76. realname: '用户供应商2',
  77. loginNum: 0,
  78. loginTime: 0,
  79. loginIp: null,
  80. lastLoginTime: 0,
  81. lastLoginIp: null,
  82. state: 1,
  83. createdBy: testUser.id
  84. });
  85. await supplierRepository.save(userSupplier2);
  86. // 为其他用户创建一个供应商,确保不会返回
  87. const otherUserSupplier = supplierRepository.create({
  88. name: '其他用户供应商',
  89. username: `other_supplier_${Date.now()}`,
  90. password: 'password123',
  91. phone: '13800138003',
  92. realname: '其他用户供应商',
  93. loginNum: 0,
  94. loginTime: 0,
  95. loginIp: null,
  96. lastLoginTime: 0,
  97. lastLoginIp: null,
  98. state: 1,
  99. createdBy: otherUser.id
  100. });
  101. await supplierRepository.save(otherUserSupplier);
  102. const response = await client.index.$get({
  103. query: {}
  104. }, {
  105. headers: {
  106. 'Authorization': `Bearer ${userToken}`
  107. }
  108. });
  109. console.debug('用户供应商列表响应状态:', response.status);
  110. expect(response.status).toBe(200);
  111. if (response.status === 200) {
  112. const data = await response.json();
  113. if (data && 'data' in data) {
  114. expect(Array.isArray(data.data)).toBe(true);
  115. // 应该只返回当前用户的供应商
  116. data.data.forEach((supplier: any) => {
  117. expect(supplier.createdBy).toBe(testUser.id);
  118. });
  119. }
  120. }
  121. });
  122. it('应该拒绝未认证用户的访问', async () => {
  123. const response = await client.index.$get({
  124. query: {}
  125. });
  126. expect(response.status).toBe(401);
  127. });
  128. });
  129. describe('POST /suppliers', () => {
  130. it('应该成功创建供应商并自动使用当前用户ID', async () => {
  131. const createData = {
  132. name: '测试供应商',
  133. username: `test_supplier_${Date.now()}`,
  134. password: 'password123',
  135. phone: '13800138000',
  136. realname: '测试供应商',
  137. state: 1
  138. };
  139. const response = await client.index.$post({
  140. json: createData
  141. }, {
  142. headers: {
  143. 'Authorization': `Bearer ${userToken}`
  144. }
  145. });
  146. console.debug('用户创建供应商响应状态:', response.status);
  147. expect(response.status).toBe(201);
  148. if (response.status === 201) {
  149. const data = await response.json();
  150. console.debug('用户创建供应商响应数据:', JSON.stringify(data, null, 2));
  151. expect(data).toHaveProperty('id');
  152. expect(data.createdBy).toBe(testUser.id); // 自动使用当前用户ID
  153. expect(data.name).toBe(createData.name);
  154. expect(data.username).toBe(createData.username);
  155. expect(data.phone).toBe(createData.phone);
  156. expect(data.realname).toBe(createData.realname);
  157. }
  158. });
  159. it('应该验证创建供应商的必填字段', async () => {
  160. const invalidData = {
  161. // 缺少必填字段
  162. name: '',
  163. username: '',
  164. password: '',
  165. phone: '',
  166. realname: ''
  167. };
  168. const response = await client.index.$post({
  169. json: invalidData
  170. }, {
  171. headers: {
  172. 'Authorization': `Bearer ${userToken}`
  173. }
  174. });
  175. expect(response.status).toBe(400);
  176. });
  177. });
  178. describe('GET /suppliers/:id', () => {
  179. it('应该返回当前用户的供应商详情', async () => {
  180. // 先为当前用户创建一个供应商
  181. const dataSource = await IntegrationTestDatabase.getDataSource();
  182. const supplierRepository = dataSource.getRepository(Supplier);
  183. const testSupplier = supplierRepository.create({
  184. name: '测试供应商详情',
  185. username: `test_supplier_detail_${Date.now()}`,
  186. password: 'password123',
  187. phone: '13600136000',
  188. realname: '测试供应商详情',
  189. loginNum: 5,
  190. loginTime: Date.now(),
  191. loginIp: '192.168.1.1',
  192. lastLoginTime: Date.now(),
  193. lastLoginIp: '192.168.1.1',
  194. state: 1,
  195. createdBy: testUser.id
  196. });
  197. await supplierRepository.save(testSupplier);
  198. const response = await client[':id'].$get({
  199. param: { id: testSupplier.id }
  200. }, {
  201. headers: {
  202. 'Authorization': `Bearer ${userToken}`
  203. }
  204. });
  205. console.debug('用户供应商详情响应状态:', response.status);
  206. expect(response.status).toBe(200);
  207. if (response.status === 200) {
  208. const data = await response.json();
  209. expect(data.id).toBe(testSupplier.id);
  210. expect(data.createdBy).toBe(testUser.id);
  211. expect(data.name).toBe(testSupplier.name);
  212. expect(data.username).toBe(testSupplier.username);
  213. expect(data.phone).toBe(testSupplier.phone);
  214. expect(data.realname).toBe(testSupplier.realname);
  215. }
  216. });
  217. it('应该拒绝访问其他用户的供应商', async () => {
  218. // 为其他用户创建一个供应商
  219. const dataSource = await IntegrationTestDatabase.getDataSource();
  220. const supplierRepository = dataSource.getRepository(Supplier);
  221. const otherUserSupplier = supplierRepository.create({
  222. name: '其他用户供应商',
  223. username: `other_supplier_detail_${Date.now()}`,
  224. password: 'password123',
  225. phone: '13600136001',
  226. realname: '其他用户供应商',
  227. loginNum: 0,
  228. loginTime: 0,
  229. loginIp: null,
  230. lastLoginTime: 0,
  231. lastLoginIp: null,
  232. state: 1,
  233. createdBy: otherUser.id
  234. });
  235. await supplierRepository.save(otherUserSupplier);
  236. // 当前用户尝试访问其他用户的供应商
  237. const response = await client[':id'].$get({
  238. param: { id: otherUserSupplier.id }
  239. }, {
  240. headers: {
  241. 'Authorization': `Bearer ${userToken}`
  242. }
  243. });
  244. console.debug('用户访问其他用户供应商响应状态:', response.status);
  245. expect(response.status).toBe(404); // 应该返回404,而不是403
  246. });
  247. it('应该处理不存在的供应商', async () => {
  248. const response = await client[':id'].$get({
  249. param: { id: 999999 }
  250. }, {
  251. headers: {
  252. 'Authorization': `Bearer ${userToken}`
  253. }
  254. });
  255. expect(response.status).toBe(404);
  256. });
  257. });
  258. describe('PUT /suppliers/:id', () => {
  259. it('应该成功更新当前用户的供应商', async () => {
  260. // 先为当前用户创建一个供应商
  261. const dataSource = await IntegrationTestDatabase.getDataSource();
  262. const supplierRepository = dataSource.getRepository(Supplier);
  263. const testSupplier = supplierRepository.create({
  264. name: '原始供应商',
  265. username: `original_supplier_${Date.now()}`,
  266. password: 'password123',
  267. phone: '13500135000',
  268. realname: '原始供应商',
  269. loginNum: 0,
  270. loginTime: 0,
  271. loginIp: null,
  272. lastLoginTime: 0,
  273. lastLoginIp: null,
  274. state: 1,
  275. createdBy: testUser.id
  276. });
  277. await supplierRepository.save(testSupplier);
  278. const updateData = {
  279. name: '更新后的供应商',
  280. phone: '13700137000',
  281. realname: '更新后的供应商',
  282. state: 2
  283. };
  284. const response = await client[':id'].$put({
  285. param: { id: testSupplier.id },
  286. json: updateData
  287. }, {
  288. headers: {
  289. 'Authorization': `Bearer ${userToken}`
  290. }
  291. });
  292. console.debug('用户更新供应商响应状态:', response.status);
  293. expect(response.status).toBe(200);
  294. if (response.status === 200) {
  295. const data = await response.json();
  296. expect(data.name).toBe(updateData.name);
  297. expect(data.phone).toBe(updateData.phone);
  298. expect(data.realname).toBe(updateData.realname);
  299. expect(data.state).toBe(updateData.state);
  300. }
  301. });
  302. it('应该拒绝更新其他用户的供应商', async () => {
  303. // 为其他用户创建一个供应商
  304. const dataSource = await IntegrationTestDatabase.getDataSource();
  305. const supplierRepository = dataSource.getRepository(Supplier);
  306. const otherUserSupplier = supplierRepository.create({
  307. name: '其他用户供应商',
  308. username: `other_supplier_update_${Date.now()}`,
  309. password: 'password123',
  310. phone: '13500135001',
  311. realname: '其他用户供应商',
  312. loginNum: 0,
  313. loginTime: 0,
  314. loginIp: null,
  315. lastLoginTime: 0,
  316. lastLoginIp: null,
  317. state: 1,
  318. createdBy: otherUser.id
  319. });
  320. await supplierRepository.save(otherUserSupplier);
  321. const updateData = {
  322. name: '尝试更新的供应商',
  323. phone: '13700137001',
  324. realname: '尝试更新的供应商'
  325. };
  326. // 当前用户尝试更新其他用户的供应商
  327. const response = await client[':id'].$put({
  328. param: { id: otherUserSupplier.id },
  329. json: updateData
  330. }, {
  331. headers: {
  332. 'Authorization': `Bearer ${userToken}`
  333. }
  334. });
  335. console.debug('用户更新其他用户供应商响应状态:', response.status);
  336. expect(response.status).toBe(403); // 数据权限控制返回403
  337. });
  338. });
  339. describe('DELETE /suppliers/:id', () => {
  340. it('应该成功删除当前用户的供应商', async () => {
  341. // 先为当前用户创建一个供应商
  342. const dataSource = await IntegrationTestDatabase.getDataSource();
  343. const supplierRepository = dataSource.getRepository(Supplier);
  344. const testSupplier = supplierRepository.create({
  345. name: '待删除供应商',
  346. username: `delete_supplier_${Date.now()}`,
  347. password: 'password123',
  348. phone: '13400134000',
  349. realname: '待删除供应商',
  350. loginNum: 0,
  351. loginTime: 0,
  352. loginIp: null,
  353. lastLoginTime: 0,
  354. lastLoginIp: null,
  355. state: 1,
  356. createdBy: testUser.id
  357. });
  358. await supplierRepository.save(testSupplier);
  359. const response = await client[':id'].$delete({
  360. param: { id: testSupplier.id }
  361. }, {
  362. headers: {
  363. 'Authorization': `Bearer ${userToken}`
  364. }
  365. });
  366. console.debug('用户删除供应商响应状态:', response.status);
  367. expect(response.status).toBe(204);
  368. // 验证供应商确实被删除
  369. const deletedSupplier = await supplierRepository.findOne({
  370. where: { id: testSupplier.id }
  371. });
  372. expect(deletedSupplier).toBeNull();
  373. });
  374. it('应该拒绝删除其他用户的供应商', async () => {
  375. // 为其他用户创建一个供应商
  376. const dataSource = await IntegrationTestDatabase.getDataSource();
  377. const supplierRepository = dataSource.getRepository(Supplier);
  378. const otherUserSupplier = supplierRepository.create({
  379. name: '其他用户供应商',
  380. username: `other_supplier_delete_${Date.now()}`,
  381. password: 'password123',
  382. phone: '13400134001',
  383. realname: '其他用户供应商',
  384. loginNum: 0,
  385. loginTime: 0,
  386. loginIp: null,
  387. lastLoginTime: 0,
  388. lastLoginIp: null,
  389. state: 1,
  390. createdBy: otherUser.id
  391. });
  392. await supplierRepository.save(otherUserSupplier);
  393. // 当前用户尝试删除其他用户的供应商
  394. const response = await client[':id'].$delete({
  395. param: { id: otherUserSupplier.id }
  396. }, {
  397. headers: {
  398. 'Authorization': `Bearer ${userToken}`
  399. }
  400. });
  401. console.debug('用户删除其他用户供应商响应状态:', response.status);
  402. expect(response.status).toBe(403); // 数据权限控制返回403
  403. });
  404. });
  405. describe('数据权限验证', () => {
  406. it('用户应该只能访问和操作自己的数据', async () => {
  407. // 为两个用户都创建供应商
  408. const dataSource = await IntegrationTestDatabase.getDataSource();
  409. const supplierRepository = dataSource.getRepository(Supplier);
  410. const userSupplier = supplierRepository.create({
  411. name: '用户供应商',
  412. username: `user_supplier_perm_${Date.now()}`,
  413. password: 'password123',
  414. phone: '13800138004',
  415. realname: '用户供应商',
  416. loginNum: 0,
  417. loginTime: 0,
  418. loginIp: null,
  419. lastLoginTime: 0,
  420. lastLoginIp: null,
  421. state: 1,
  422. createdBy: testUser.id
  423. });
  424. await supplierRepository.save(userSupplier);
  425. const otherUserSupplier = supplierRepository.create({
  426. name: '其他用户供应商',
  427. username: `other_supplier_perm_${Date.now()}`,
  428. password: 'password123',
  429. phone: '13800138005',
  430. realname: '其他用户供应商',
  431. loginNum: 0,
  432. loginTime: 0,
  433. loginIp: null,
  434. lastLoginTime: 0,
  435. lastLoginIp: null,
  436. state: 1,
  437. createdBy: otherUser.id
  438. });
  439. await supplierRepository.save(otherUserSupplier);
  440. // 当前用户应该只能看到自己的供应商
  441. const listResponse = await client.index.$get({
  442. query: {}
  443. }, {
  444. headers: {
  445. 'Authorization': `Bearer ${userToken}`
  446. }
  447. });
  448. expect(listResponse.status).toBe(200);
  449. const listData = await listResponse.json();
  450. if (listData && 'data' in listData) {
  451. expect(Array.isArray(listData.data)).toBe(true);
  452. // 应该只包含当前用户的供应商
  453. listData.data.forEach((supplier: any) => {
  454. expect(supplier.createdBy).toBe(testUser.id);
  455. });
  456. }
  457. // 当前用户应该无法访问其他用户的供应商详情
  458. const getResponse = await client[':id'].$get({
  459. param: { id: otherUserSupplier.id }
  460. }, {
  461. headers: {
  462. 'Authorization': `Bearer ${userToken}`
  463. }
  464. });
  465. expect(getResponse.status).toBe(404);
  466. // 当前用户应该无法更新其他用户的供应商
  467. const updateResponse = await client[':id'].$put({
  468. param: { id: otherUserSupplier.id },
  469. json: { name: '尝试更新' }
  470. }, {
  471. headers: {
  472. 'Authorization': `Bearer ${userToken}`
  473. }
  474. });
  475. expect(updateResponse.status).toBe(403);
  476. // 当前用户应该无法删除其他用户的供应商
  477. const deleteResponse = await client[':id'].$delete({
  478. param: { id: otherUserSupplier.id }
  479. }, {
  480. headers: {
  481. 'Authorization': `Bearer ${userToken}`
  482. }
  483. });
  484. expect(deleteResponse.status).toBe(403);
  485. });
  486. });
  487. describe('供应商状态管理', () => {
  488. it('应该支持供应商状态管理', async () => {
  489. // 创建启用状态的供应商
  490. const createData = {
  491. name: '状态测试供应商',
  492. username: `status_test_supplier_${Date.now()}`,
  493. password: 'password123',
  494. phone: '13800138006',
  495. realname: '状态测试供应商',
  496. state: 1 // 启用状态
  497. };
  498. const createResponse = await client.index.$post({
  499. json: createData
  500. }, {
  501. headers: {
  502. 'Authorization': `Bearer ${userToken}`
  503. }
  504. });
  505. expect(createResponse.status).toBe(201);
  506. const createdData = await createResponse.json();
  507. // 更新为禁用状态
  508. const updateResponse = await client[':id'].$put({
  509. param: { id: createdData.id },
  510. json: { state: 2 } // 禁用状态
  511. }, {
  512. headers: {
  513. 'Authorization': `Bearer ${userToken}`
  514. }
  515. });
  516. expect(updateResponse.status).toBe(200);
  517. const updatedData = await updateResponse.json();
  518. expect(updatedData.state).toBe(2);
  519. });
  520. });
  521. describe('供应商登录统计', () => {
  522. it('应该支持供应商登录统计功能', async () => {
  523. // 创建供应商
  524. const createData = {
  525. name: '登录统计供应商',
  526. username: `login_stat_supplier_${Date.now()}`,
  527. password: 'password123',
  528. phone: '13800138007',
  529. realname: '登录统计供应商',
  530. state: 1
  531. };
  532. const createResponse = await client.index.$post({
  533. json: createData
  534. }, {
  535. headers: {
  536. 'Authorization': `Bearer ${userToken}`
  537. }
  538. });
  539. expect(createResponse.status).toBe(201);
  540. const createdData = await createResponse.json();
  541. // 验证初始登录统计
  542. expect(createdData.loginNum).toBe(0);
  543. expect(createdData.loginTime).toBe(0);
  544. expect(createdData.lastLoginTime).toBe(0);
  545. expect(createdData.loginIp).toBeNull();
  546. expect(createdData.lastLoginIp).toBeNull();
  547. // 获取供应商详情验证字段存在
  548. const getResponse = await client[':id'].$get({
  549. param: { id: createdData.id }
  550. }, {
  551. headers: {
  552. 'Authorization': `Bearer ${userToken}`
  553. }
  554. });
  555. expect(getResponse.status).toBe(200);
  556. const supplierData = await getResponse.json();
  557. expect(supplierData).toHaveProperty('loginNum');
  558. expect(supplierData).toHaveProperty('loginTime');
  559. expect(supplierData).toHaveProperty('lastLoginTime');
  560. expect(supplierData).toHaveProperty('loginIp');
  561. expect(supplierData).toHaveProperty('lastLoginIp');
  562. });
  563. });
  564. });