talent-employment.integration.test.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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 { UserType } from '@d8d/shared-types';
  6. import { UserEntity, Role } from '@d8d/user-module';
  7. import { File } from '@d8d/file-module';
  8. import { DisabledPerson, DisabledBankCard, DisabledPhoto, DisabledRemark, DisabledVisit } from '@d8d/allin-disability-module/entities';
  9. import { Company } from '@d8d/allin-company-module';
  10. import { Platform } from '@d8d/allin-platform-module';
  11. import { Channel } from '@d8d/allin-channel-module';
  12. import { BankName } from '@d8d/bank-names-module';
  13. import talentEmploymentRoutes from '../../src/routes/talent-employment.routes';
  14. import { EmploymentOrder } from '../../src/entities/employment-order.entity';
  15. import { OrderPerson } from '../../src/entities/order-person.entity';
  16. import { OrderPersonAsset } from '../../src/entities/order-person-asset.entity';
  17. import { AssetType, AssetFileType } from '../../src/schemas/order.schema';
  18. import { OrderStatus, WorkStatus } from '@d8d/allin-enums';
  19. // 设置集成测试钩子
  20. setupIntegrationDatabaseHooksWithEntities([
  21. UserEntity, Role, File, Platform, Company, Channel,
  22. DisabledPerson, DisabledBankCard, DisabledPhoto, DisabledRemark, DisabledVisit,
  23. BankName,
  24. EmploymentOrder, OrderPerson, OrderPersonAsset
  25. ]);
  26. describe('人才就业信息API集成测试 - 故事015.005 + 015.006', () => {
  27. let client: ReturnType<typeof testClient<typeof talentEmploymentRoutes>>;
  28. let testToken: string;
  29. let testTalentUser: UserEntity;
  30. let testDisabledPerson: DisabledPerson;
  31. let testCompany: Company;
  32. let testPlatform: Platform;
  33. let testFile: File;
  34. beforeEach(async () => {
  35. // 创建测试客户端
  36. client = testClient(talentEmploymentRoutes);
  37. // 获取数据源
  38. const dataSource = await IntegrationTestDatabase.getDataSource();
  39. // 创建测试银行
  40. const bankNameRepository = dataSource.getRepository(BankName);
  41. const testBankName = bankNameRepository.create({
  42. name: '测试银行',
  43. code: 'TEST001',
  44. remark: '测试银行',
  45. status: 1
  46. });
  47. await bankNameRepository.save(testBankName);
  48. // 创建测试残疾人 - 使用正确的字段名
  49. const personRepository = dataSource.getRepository(DisabledPerson);
  50. testDisabledPerson = personRepository.create({
  51. name: '李四',
  52. gender: '1',
  53. disabilityType: '肢体残疾',
  54. disabilityLevel: '三级',
  55. idCard: `110101${Date.now() % 100000000}`,
  56. disabilityId: `D${Date.now() % 100000000}`,
  57. idAddress: '北京市朝阳区',
  58. phone: '13900139000',
  59. province: '北京市',
  60. city: '北京市',
  61. district: '朝阳区',
  62. jobStatus: 0, // 0-未在职,1-已在职
  63. canDirectContact: 1,
  64. isInBlackList: 0,
  65. birthDate: new Date('1990-01-01'),
  66. idValidDate: new Date('2030-01-01'),
  67. disabilityValidDate: new Date('2025-12-31'),
  68. specificDisability: '左腿轻微残疾'
  69. });
  70. await personRepository.save(testDisabledPerson);
  71. // 创建测试人才用户(残疾人已经创建,可以用作personId)
  72. const userRepository = dataSource.getRepository(UserEntity);
  73. testTalentUser = userRepository.create({
  74. username: `talent_${Date.now()}`,
  75. password: 'test_password',
  76. nickname: '测试人才',
  77. userType: UserType.TALENT,
  78. personId: testDisabledPerson.id,
  79. registrationSource: 'mini',
  80. isDisabled: 0,
  81. isDeleted: 0
  82. });
  83. await userRepository.save(testTalentUser);
  84. // 生成测试用户的token
  85. testToken = JWTUtil.generateToken({
  86. id: testTalentUser.id,
  87. username: testTalentUser.username,
  88. roles: [{ name: 'talent' }]
  89. }, {
  90. personId: testDisabledPerson.id,
  91. userType: UserType.TALENT
  92. });
  93. // 创建测试平台
  94. const platformRepository = dataSource.getRepository(Platform);
  95. testPlatform = platformRepository.create({
  96. platformName: '测试平台',
  97. status: 1
  98. });
  99. await platformRepository.save(testPlatform);
  100. // 创建测试企业
  101. const companyRepository = dataSource.getRepository(Company);
  102. testCompany = companyRepository.create({
  103. platformId: testPlatform.id,
  104. companyName: '测试科技有限公司',
  105. contactPerson: '张三',
  106. contactPhone: '13800138000',
  107. status: 1
  108. });
  109. await companyRepository.save(testCompany);
  110. // 创建测试文件(用于视频资产)
  111. const fileRepository = dataSource.getRepository(File);
  112. testFile = fileRepository.create({
  113. name: '工资视频_2025-01.mp4',
  114. type: 'video/mp4',
  115. size: 1024000,
  116. path: `videos/${Date.now()}_salary_video.mp4`,
  117. uploadUserId: testTalentUser.id,
  118. uploadTime: new Date()
  119. });
  120. await fileRepository.save(testFile);
  121. });
  122. describe('GET /employment/status - 当前就业状态查询', () => {
  123. it('应该成功查询当前就业状态 - 在职状态', async () => {
  124. // 创建测试订单
  125. const dataSource = await IntegrationTestDatabase.getDataSource();
  126. const orderRepository = dataSource.getRepository(EmploymentOrder);
  127. const testOrder = orderRepository.create({
  128. platformId: testPlatform.id,
  129. companyId: testCompany.id,
  130. orderName: '包装工',
  131. expectedStartDate: new Date('2025-01-01'),
  132. actualStartDate: new Date('2025-01-15'),
  133. orderStatus: OrderStatus.IN_PROGRESS
  134. });
  135. await orderRepository.save(testOrder);
  136. // 创建订单人员关联
  137. const orderPersonRepository = dataSource.getRepository(OrderPerson);
  138. const orderPerson = orderPersonRepository.create({
  139. orderId: testOrder.id,
  140. personId: testDisabledPerson.id,
  141. joinDate: new Date('2025-01-15'),
  142. actualStartDate: new Date('2025-01-15'),
  143. workStatus: WorkStatus.WORKING,
  144. salaryDetail: 3500.00
  145. });
  146. await orderPersonRepository.save(orderPerson);
  147. // 查询当前就业状态
  148. const response = await client.employment.status.$get(undefined, {
  149. headers: {
  150. 'Authorization': `Bearer ${testToken}`
  151. }
  152. });
  153. expect(response.status).toBe(200);
  154. if (response.status === 200) {
  155. const data = await response.json();
  156. expect(data.companyName).toBe('测试科技有限公司');
  157. expect(data.orderId).toBe(testOrder.id);
  158. expect(data.orderName).toBe('包装工');
  159. expect(data.workStatus).toBe('working');
  160. expect(data.salaryLevel).toBe(3500.00);
  161. }
  162. });
  163. it('应该返回200和null当用户无就业记录时 - 故事015.006', async () => {
  164. const response = await client.employment.status.$get(undefined, {
  165. headers: {
  166. 'Authorization': `Bearer ${testToken}`
  167. }
  168. });
  169. // 无就业记录时返回200状态码和null,不是404
  170. expect(response.status).toBe(200);
  171. if (response.status === 200) {
  172. const data = await response.json();
  173. expect(data).toBeNull();
  174. }
  175. });
  176. it('应该返回404当用户未关联残疾人信息时', async () => {
  177. // 创建一个没有personId的用户
  178. const dataSource = await IntegrationTestDatabase.getDataSource();
  179. const userRepository = dataSource.getRepository(UserEntity);
  180. const userWithoutPerson = userRepository.create({
  181. username: `no_person_${Date.now()}`,
  182. password: 'test_password',
  183. nickname: '无残疾人关联用户',
  184. userType: UserType.TALENT,
  185. registrationSource: 'mini',
  186. isDisabled: 0,
  187. isDeleted: 0
  188. });
  189. await userRepository.save(userWithoutPerson);
  190. const token = JWTUtil.generateToken({
  191. id: userWithoutPerson.id,
  192. username: userWithoutPerson.username,
  193. roles: [{ name: 'talent' }]
  194. }, {
  195. userType: UserType.TALENT
  196. });
  197. const response = await client.employment.status.$get(undefined, {
  198. headers: {
  199. 'Authorization': `Bearer ${token}`
  200. }
  201. });
  202. expect(response.status).toBe(404);
  203. if (response.status === 404) {
  204. const error = await response.json();
  205. expect(error.code).toBe(404);
  206. expect(error.message).toContain('用户不存在');
  207. }
  208. });
  209. });
  210. describe('GET /employment/salary-records - 薪资记录查询', () => {
  211. beforeEach(async () => {
  212. // 创建测试订单和人员关联
  213. const dataSource = await IntegrationTestDatabase.getDataSource();
  214. const orderRepository = dataSource.getRepository(EmploymentOrder);
  215. const testOrder = orderRepository.create({
  216. platformId: testPlatform.id,
  217. companyId: testCompany.id,
  218. orderName: '包装工',
  219. expectedStartDate: new Date('2025-01-01'),
  220. actualStartDate: new Date('2025-01-15'),
  221. orderStatus: OrderStatus.IN_PROGRESS
  222. });
  223. await orderRepository.save(testOrder);
  224. const orderPersonRepository = dataSource.getRepository(OrderPerson);
  225. const orderPerson = orderPersonRepository.create({
  226. orderId: testOrder.id,
  227. personId: testDisabledPerson.id,
  228. joinDate: new Date('2025-01-15'),
  229. actualStartDate: new Date('2025-01-15'),
  230. workStatus: WorkStatus.WORKING,
  231. salaryDetail: 3500.00
  232. });
  233. await orderPersonRepository.save(orderPerson);
  234. });
  235. it('应该成功查询薪资记录', async () => {
  236. const response = await client.employment['salary-records'].$get({
  237. query: {
  238. skip: 0,
  239. take: 10
  240. }
  241. }, {
  242. headers: {
  243. 'Authorization': `Bearer ${testToken}`
  244. }
  245. });
  246. if (response.status !== 200) {
  247. const error = await response.json();
  248. console.debug('查询薪资记录失败:', JSON.stringify(error, null, 2));
  249. }
  250. expect(response.status).toBe(200);
  251. if (response.status === 200) {
  252. const data = await response.json();
  253. expect(data.data).toBeInstanceOf(Array);
  254. expect(data.total).toBeGreaterThanOrEqual(0);
  255. }
  256. });
  257. it('应该支持按月份过滤薪资记录', async () => {
  258. const response = await client.employment['salary-records'].$get({
  259. query: {
  260. month: '2025-01',
  261. skip: 0,
  262. take: 10
  263. }
  264. }, {
  265. headers: {
  266. 'Authorization': `Bearer ${testToken}`
  267. }
  268. });
  269. expect(response.status).toBe(200);
  270. if (response.status === 200) {
  271. const data = await response.json();
  272. expect(data.data).toBeInstanceOf(Array);
  273. }
  274. });
  275. });
  276. describe('GET /employment/history - 就业历史查询', () => {
  277. beforeEach(async () => {
  278. // 创建多个测试订单来模拟就业历史
  279. const dataSource = await IntegrationTestDatabase.getDataSource();
  280. const orderRepository = dataSource.getRepository(EmploymentOrder);
  281. const order1 = orderRepository.create({
  282. platformId: testPlatform.id,
  283. companyId: testCompany.id,
  284. orderName: '包装工',
  285. expectedStartDate: new Date('2024-06-01'),
  286. actualStartDate: new Date('2024-06-15'),
  287. actualEndDate: new Date('2024-12-31'),
  288. orderStatus: OrderStatus.COMPLETED
  289. });
  290. await orderRepository.save(order1);
  291. const orderPersonRepository = dataSource.getRepository(OrderPerson);
  292. const orderPerson1 = orderPersonRepository.create({
  293. orderId: order1.id,
  294. personId: testDisabledPerson.id,
  295. joinDate: new Date('2024-06-15'),
  296. leaveDate: new Date('2024-12-31'),
  297. workStatus: WorkStatus.RESIGNED,
  298. salaryDetail: 3000.00
  299. });
  300. await orderPersonRepository.save(orderPerson1);
  301. const order2 = orderRepository.create({
  302. platformId: testPlatform.id,
  303. companyId: testCompany.id,
  304. orderName: '组装工',
  305. expectedStartDate: new Date('2025-01-01'),
  306. actualStartDate: new Date('2025-01-15'),
  307. orderStatus: OrderStatus.IN_PROGRESS
  308. });
  309. await orderRepository.save(order2);
  310. const orderPerson2 = orderPersonRepository.create({
  311. orderId: order2.id,
  312. personId: testDisabledPerson.id,
  313. joinDate: new Date('2025-01-15'),
  314. workStatus: WorkStatus.WORKING,
  315. salaryDetail: 3500.00
  316. });
  317. await orderPersonRepository.save(orderPerson2);
  318. });
  319. it('应该成功查询就业历史,按时间倒序排列', async () => {
  320. const response = await client.employment['history'].$get({
  321. query: {
  322. skip: 0,
  323. take: 20
  324. }
  325. }, {
  326. headers: {
  327. 'Authorization': `Bearer ${testToken}`
  328. }
  329. });
  330. if (response.status !== 200) {
  331. const error = await response.json();
  332. console.debug('查询就业历史失败:', JSON.stringify(error, null, 2));
  333. }
  334. expect(response.status).toBe(200);
  335. if (response.status === 200) {
  336. const data = await response.json();
  337. expect(data.data).toBeInstanceOf(Array);
  338. expect(data.total).toBeGreaterThanOrEqual(0);
  339. // 验证按时间倒序排列(最新的在前)
  340. if (data.data.length > 1) {
  341. expect(new Date(data.data[0].joinDate).getTime())
  342. .toBeGreaterThanOrEqual(new Date(data.data[1].joinDate).getTime());
  343. }
  344. }
  345. });
  346. it('应该支持分页查询就业历史', async () => {
  347. const response = await client.employment['history'].$get({
  348. query: {
  349. skip: 0,
  350. take: 1
  351. }
  352. }, {
  353. headers: {
  354. 'Authorization': `Bearer ${testToken}`
  355. }
  356. });
  357. expect(response.status).toBe(200);
  358. if (response.status === 200) {
  359. const data = await response.json();
  360. expect(data.data).toBeInstanceOf(Array);
  361. expect(data.total).toBe(2);
  362. }
  363. });
  364. });
  365. describe('GET /employment/salary-videos - 薪资视频查询', () => {
  366. beforeEach(async () => {
  367. // 创建测试订单和人员关联
  368. const dataSource = await IntegrationTestDatabase.getDataSource();
  369. const orderRepository = dataSource.getRepository(EmploymentOrder);
  370. const testOrder = orderRepository.create({
  371. platformId: testPlatform.id,
  372. companyId: testCompany.id,
  373. orderName: '包装工',
  374. expectedStartDate: new Date('2025-01-01'),
  375. orderStatus: OrderStatus.IN_PROGRESS
  376. });
  377. await orderRepository.save(testOrder);
  378. const orderPersonRepository = dataSource.getRepository(OrderPerson);
  379. const orderPerson = orderPersonRepository.create({
  380. orderId: testOrder.id,
  381. personId: testDisabledPerson.id,
  382. joinDate: new Date('2025-01-15'),
  383. workStatus: WorkStatus.WORKING,
  384. salaryDetail: 3500.00
  385. });
  386. await orderPersonRepository.save(orderPerson);
  387. // 创建薪资视频资产
  388. const assetRepository = dataSource.getRepository(OrderPersonAsset);
  389. const salaryVideo = assetRepository.create({
  390. orderId: testOrder.id,
  391. personId: testDisabledPerson.id,
  392. fileId: testFile.id,
  393. assetType: AssetType.SALARY_VIDEO,
  394. assetFileType: AssetFileType.VIDEO,
  395. status: 'verified',
  396. relatedTime: new Date('2025-01-15')
  397. });
  398. await assetRepository.save(salaryVideo);
  399. });
  400. it('应该成功查询薪资视频', async () => {
  401. const response = await client.employment['salary-videos'].$get({
  402. query: {
  403. skip: 0,
  404. take: 10
  405. }
  406. }, {
  407. headers: {
  408. 'Authorization': `Bearer ${testToken}`
  409. }
  410. });
  411. if (response.status !== 200) {
  412. const error = await response.json();
  413. console.debug('查询薪资视频失败:', JSON.stringify(error, null, 2));
  414. }
  415. expect(response.status).toBe(200);
  416. if (response.status === 200) {
  417. const data = await response.json();
  418. expect(data.data).toBeInstanceOf(Array);
  419. expect(data.total).toBeGreaterThanOrEqual(0);
  420. }
  421. });
  422. it('应该支持按类型过滤薪资视频', async () => {
  423. const response = await client.employment['salary-videos'].$get({
  424. query: {
  425. assetType: 'salary_video',
  426. skip: 0,
  427. take: 10
  428. }
  429. }, {
  430. headers: {
  431. 'Authorization': `Bearer ${testToken}`
  432. }
  433. });
  434. expect(response.status).toBe(200);
  435. if (response.status === 200) {
  436. const data = await response.json();
  437. expect(data.data).toBeInstanceOf(Array);
  438. }
  439. });
  440. it('应该支持按月份过滤薪资视频', async () => {
  441. const response = await client.employment['salary-videos'].$get({
  442. query: {
  443. month: '2025-01',
  444. skip: 0,
  445. take: 10
  446. }
  447. }, {
  448. headers: {
  449. 'Authorization': `Bearer ${testToken}`
  450. }
  451. });
  452. expect(response.status).toBe(200);
  453. if (response.status === 200) {
  454. const data = await response.json();
  455. expect(data.data).toBeInstanceOf(Array);
  456. }
  457. });
  458. });
  459. describe('认证和权限验证', () => {
  460. it('应该返回401当未提供token时', async () => {
  461. const response = await client.employment.status.$get(undefined, {
  462. headers: {}
  463. });
  464. expect(response.status).toBe(401);
  465. });
  466. it('应该返回401当token无效时', async () => {
  467. const response = await client.employment.status.$get(undefined, {
  468. headers: {
  469. 'Authorization': 'Bearer invalid_token'
  470. }
  471. });
  472. expect(response.status).toBe(401);
  473. });
  474. });
  475. });