|
|
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
import { testClient } from 'hono/testing';
|
|
|
import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
|
|
|
import { JWTUtil } from '@d8d/shared-utils';
|
|
|
+import { JWTPayload } from '@d8d/shared-types';
|
|
|
import { UserEntity, Role } from '@d8d/user-module';
|
|
|
import { File } from '@d8d/file-module';
|
|
|
import { DisabledPerson, DisabledBankCard, DisabledPhoto, DisabledRemark, DisabledVisit } from '@d8d/allin-disability-module';
|
|
|
@@ -13,7 +14,7 @@ import orderRoutes from '../../src/routes/order.routes';
|
|
|
import { EmploymentOrder } from '../../src/entities/employment-order.entity';
|
|
|
import { OrderPerson } from '../../src/entities/order-person.entity';
|
|
|
import { OrderPersonAsset } from '../../src/entities/order-person-asset.entity';
|
|
|
-import { AssetType, AssetFileType } from '../../src/schemas/order.schema';
|
|
|
+import { AssetType, AssetFileType, AssetStatus, DownloadScope } from '../../src/schemas/order.schema';
|
|
|
import { OrderStatus, WorkStatus } from '@d8d/allin-enums';
|
|
|
import { OrderTestDataFactory } from '../utils/test-data-factory';
|
|
|
|
|
|
@@ -1109,4 +1110,513 @@ describe('订单管理API集成测试', () => {
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ describe('企业维度视频管理API测试', () => {
|
|
|
+ let testCompany: Company;
|
|
|
+ let testOrder: EmploymentOrder;
|
|
|
+ let testOrderPerson: OrderPerson;
|
|
|
+ let testVideoAssets: OrderPersonAsset[];
|
|
|
+
|
|
|
+ beforeEach(async () => {
|
|
|
+ // 创建测试公司
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const companyRepository = dataSource.getRepository(Company);
|
|
|
+ testCompany = companyRepository.create({
|
|
|
+ companyName: '视频管理测试公司',
|
|
|
+ contactPerson: '测试联系人',
|
|
|
+ contactPhone: '13800138001',
|
|
|
+ status: 1
|
|
|
+ });
|
|
|
+ await companyRepository.save(testCompany);
|
|
|
+
|
|
|
+ // 为测试用户生成包含companyId的token,添加enterprise角色
|
|
|
+ testToken = JWTUtil.generateToken({
|
|
|
+ id: testUser.id,
|
|
|
+ username: testUser.username,
|
|
|
+ roles: [{name:'user'}, {name:'enterprise'}]
|
|
|
+ }, { companyId: testCompany.id } as Partial<JWTPayload & { companyId: number }>);
|
|
|
+
|
|
|
+ // 更新用户实体的companyId(如果字段存在)
|
|
|
+ const userRepository = dataSource.getRepository(UserEntity);
|
|
|
+ await userRepository.update(testUser.id, { companyId: testCompany.id } as any);
|
|
|
+
|
|
|
+ // 创建测试订单
|
|
|
+ const orderRepository = dataSource.getRepository(EmploymentOrder);
|
|
|
+ testOrder = new EmploymentOrder({
|
|
|
+ orderName: '视频管理测试订单',
|
|
|
+ platformId: 1,
|
|
|
+ companyId: testCompany.id,
|
|
|
+ channelId: 1,
|
|
|
+ expectedStartDate: new Date(),
|
|
|
+ orderStatus: OrderStatus.DRAFT,
|
|
|
+ workStatus: WorkStatus.NOT_WORKING,
|
|
|
+ });
|
|
|
+ await orderRepository.save(testOrder);
|
|
|
+
|
|
|
+ // 创建测试残疾人记录
|
|
|
+ const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
|
|
|
+ const testDisabledPerson = disabledPersonRepository.create({
|
|
|
+ name: '视频测试人员',
|
|
|
+ gender: '男',
|
|
|
+ idCard: '3',
|
|
|
+ disabilityId: '3',
|
|
|
+ disabilityType: '肢体',
|
|
|
+ disabilityLevel: '三级',
|
|
|
+ idAddress: '地址',
|
|
|
+ phone: '13800138002',
|
|
|
+ canDirectContact: 1,
|
|
|
+ province: '省',
|
|
|
+ city: '市',
|
|
|
+ district: '区',
|
|
|
+ detailedAddress: '地址',
|
|
|
+ isInBlackList: 0,
|
|
|
+ jobStatus: 0,
|
|
|
+ createTime: new Date(),
|
|
|
+ updateTime: new Date()
|
|
|
+ });
|
|
|
+ await disabledPersonRepository.save(testDisabledPerson);
|
|
|
+
|
|
|
+ // 创建订单人员关联
|
|
|
+ const orderPersonRepository = dataSource.getRepository(OrderPerson);
|
|
|
+ testOrderPerson = orderPersonRepository.create({
|
|
|
+ orderId: testOrder.id,
|
|
|
+ personId: testDisabledPerson.id,
|
|
|
+ joinDate: new Date(),
|
|
|
+ workStatus: WorkStatus.NOT_WORKING,
|
|
|
+ salaryDetail: 5000.00
|
|
|
+ });
|
|
|
+ await orderPersonRepository.save(testOrderPerson);
|
|
|
+
|
|
|
+ // 创建测试视频资产
|
|
|
+ const assetRepository = dataSource.getRepository(OrderPersonAsset);
|
|
|
+ testVideoAssets = [
|
|
|
+ new OrderPersonAsset({
|
|
|
+ orderId: testOrder.id,
|
|
|
+ personId: testOrderPerson.personId,
|
|
|
+ assetType: AssetType.CHECKIN_VIDEO,
|
|
|
+ assetFileType: AssetFileType.VIDEO,
|
|
|
+ fileId: testFile.id,
|
|
|
+ relatedTime: new Date(),
|
|
|
+ status: AssetStatus.PENDING
|
|
|
+ }),
|
|
|
+ new OrderPersonAsset({
|
|
|
+ orderId: testOrder.id,
|
|
|
+ personId: testOrderPerson.personId,
|
|
|
+ assetType: AssetType.WORK_VIDEO,
|
|
|
+ assetFileType: AssetFileType.VIDEO,
|
|
|
+ fileId: testFile.id,
|
|
|
+ relatedTime: new Date(Date.now() - 86400000), // 昨天
|
|
|
+ status: AssetStatus.VERIFIED
|
|
|
+ }),
|
|
|
+ new OrderPersonAsset({
|
|
|
+ orderId: testOrder.id,
|
|
|
+ personId: testOrderPerson.personId,
|
|
|
+ assetType: AssetType.SALARY_VIDEO,
|
|
|
+ assetFileType: AssetFileType.VIDEO,
|
|
|
+ fileId: testFile.id,
|
|
|
+ relatedTime: new Date(Date.now() - 172800000), // 前天
|
|
|
+ status: AssetStatus.REJECTED
|
|
|
+ })
|
|
|
+ ];
|
|
|
+ await assetRepository.save(testVideoAssets);
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('GET /order/company-videos', () => {
|
|
|
+ it('应该返回企业维度视频列表', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {
|
|
|
+ companyId: testCompany.id.toString()
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data).toHaveProperty('data');
|
|
|
+ expect(data).toHaveProperty('total');
|
|
|
+ expect(data.data).toHaveLength(3); // 应该返回所有视频
|
|
|
+ expect(data.total).toBe(3);
|
|
|
+ // 验证数据结构
|
|
|
+ expect(data.data[0]).toHaveProperty('id');
|
|
|
+ expect(data.data[0]).toHaveProperty('orderId');
|
|
|
+ expect(data.data[0]).toHaveProperty('personId');
|
|
|
+ expect(data.data[0]).toHaveProperty('assetType');
|
|
|
+ expect(data.data[0]).toHaveProperty('assetFileType');
|
|
|
+ expect(data.data[0]).toHaveProperty('file');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持按视频类型过滤', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {
|
|
|
+ companyId: testCompany.id.toString(),
|
|
|
+ assetType: AssetType.CHECKIN_VIDEO
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.data).toHaveLength(1);
|
|
|
+ expect(data.data[0].assetType).toBe(AssetType.CHECKIN_VIDEO);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持分页查询', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {
|
|
|
+ companyId: testCompany.id.toString(),
|
|
|
+ page: '1',
|
|
|
+ pageSize: '2'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.data).toHaveLength(2);
|
|
|
+ expect(data.total).toBe(3);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持按关联时间排序', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {
|
|
|
+ companyId: testCompany.id.toString(),
|
|
|
+ sortBy: 'relatedTime',
|
|
|
+ sortOrder: 'ASC'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.data).toHaveLength(3);
|
|
|
+ // 验证排序:relatedTime 最早的应该在第一个
|
|
|
+ const firstDate = new Date(data.data[0].relatedTime).getTime();
|
|
|
+ const lastDate = new Date(data.data[2].relatedTime).getTime();
|
|
|
+ expect(firstDate).toBeLessThan(lastDate);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证企业ID必填', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {}
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 由于token中包含companyId,即使查询参数中没有companyId,API也能从token中获取
|
|
|
+ // 所以应该返回200而不是400
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data).toHaveProperty('data');
|
|
|
+ expect(data).toHaveProperty('total');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证企业ID有效性', async () => {
|
|
|
+ const response = await client['company-videos'].$get({
|
|
|
+ query: {
|
|
|
+ companyId: '999999' // 不存在的企业ID
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 注意:API可能会返回空列表而不是错误
|
|
|
+ // 根据实际实现,可能返回200且空列表,或返回404
|
|
|
+ // 这里我们假设返回200且空列表
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.data).toHaveLength(0);
|
|
|
+ expect(data.total).toBe(0);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('POST /order/batch-download', () => {
|
|
|
+ it('应该成功批量下载企业维度视频', async () => {
|
|
|
+ const requestData = {
|
|
|
+ downloadScope: DownloadScope.COMPANY,
|
|
|
+ companyId: testCompany.id,
|
|
|
+ assetTypes: [AssetType.CHECKIN_VIDEO, AssetType.WORK_VIDEO]
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client['batch-download'].$post({
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.success).toBe(true);
|
|
|
+ expect(data.message).toContain('批量下载成功');
|
|
|
+ expect(data.files).toHaveLength(2); // CHECKIN_VIDEO 和 WORK_VIDEO
|
|
|
+ expect(data.totalFiles).toBe(2);
|
|
|
+
|
|
|
+ // 验证文件项结构
|
|
|
+ const fileItem = data.files[0];
|
|
|
+ expect(fileItem).toHaveProperty('id');
|
|
|
+ expect(fileItem).toHaveProperty('name');
|
|
|
+ expect(fileItem).toHaveProperty('size');
|
|
|
+ expect(fileItem).toHaveProperty('url');
|
|
|
+ expect(fileItem).toHaveProperty('assetType');
|
|
|
+ expect(fileItem).toHaveProperty('orderId');
|
|
|
+ expect(fileItem).toHaveProperty('personId');
|
|
|
+ expect(fileItem).toHaveProperty('relatedTime');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该成功批量下载个人维度视频', async () => {
|
|
|
+ const requestData = {
|
|
|
+ downloadScope: DownloadScope.PERSON,
|
|
|
+ companyId: testCompany.id,
|
|
|
+ personId: testOrderPerson.personId,
|
|
|
+ assetTypes: [AssetType.CHECKIN_VIDEO]
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client['batch-download'].$post({
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.success).toBe(true);
|
|
|
+ expect(data.files).toHaveLength(1);
|
|
|
+ expect(data.files[0].assetType).toBe(AssetType.CHECKIN_VIDEO);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证个人维度下载需要personId', async () => {
|
|
|
+ const requestData = {
|
|
|
+ downloadScope: DownloadScope.PERSON,
|
|
|
+ companyId: testCompany.id
|
|
|
+ // 缺少personId
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client['batch-download'].$post({
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(400);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持指定文件ID列表下载', async () => {
|
|
|
+ // 获取测试视频资产的文件ID
|
|
|
+ const fileIds = [testFile.id];
|
|
|
+
|
|
|
+ const requestData = {
|
|
|
+ downloadScope: DownloadScope.COMPANY,
|
|
|
+ companyId: testCompany.id,
|
|
|
+ fileIds: fileIds
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client['batch-download'].$post({
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.success).toBe(true);
|
|
|
+ // 3个视频资产都使用同一个文件ID,所以应该返回3个文件项
|
|
|
+ expect(data.files).toHaveLength(3);
|
|
|
+ // 所有文件项的id都应该是指定的文件ID
|
|
|
+ data.files.forEach((fileItem: any) => {
|
|
|
+ expect(fileItem.id).toBe(fileIds[0]);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该处理没有符合条件的视频文件', async () => {
|
|
|
+ const requestData = {
|
|
|
+ downloadScope: DownloadScope.COMPANY,
|
|
|
+ companyId: 999999, // 不存在的企业
|
|
|
+ assetTypes: [AssetType.CHECKIN_VIDEO]
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client['batch-download'].$post({
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.success).toBe(true);
|
|
|
+ expect(data.files).toHaveLength(0);
|
|
|
+ expect(data.totalFiles).toBe(0);
|
|
|
+ expect(data.message).toContain('未找到符合条件的视频文件');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('PUT /order/videos/{id}/status', () => {
|
|
|
+ it('应该成功更新视频审核状态', async () => {
|
|
|
+ const testAsset = testVideoAssets[0];
|
|
|
+ const requestData = {
|
|
|
+ status: AssetStatus.VERIFIED as const
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client.videos[':id'].status.$put({
|
|
|
+ param: { id: testAsset.id.toString() },
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.id).toBe(testAsset.id);
|
|
|
+ expect(data.status).toBe('verified');
|
|
|
+
|
|
|
+ // 验证数据库中的状态已更新
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const assetRepository = dataSource.getRepository(OrderPersonAsset);
|
|
|
+ const updatedAsset = await assetRepository.findOne({
|
|
|
+ where: { id: testAsset.id }
|
|
|
+ });
|
|
|
+ expect(updatedAsset?.status).toBe('verified');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持更新状态为已拒绝', async () => {
|
|
|
+ const testAsset = testVideoAssets[1]; // 当前是verified
|
|
|
+ const requestData = {
|
|
|
+ status: AssetStatus.REJECTED as const
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client.videos[':id'].status.$put({
|
|
|
+ param: { id: testAsset.id.toString() },
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.status).toBe('rejected');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该处理不存在的视频资产ID', async () => {
|
|
|
+ const requestData = {
|
|
|
+ status: AssetStatus.VERIFIED as const
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client.videos[':id'].status.$put({
|
|
|
+ param: { id: '999999' },
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(404);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证状态值的有效性', async () => {
|
|
|
+ const testAsset = testVideoAssets[0];
|
|
|
+ const requestData = {
|
|
|
+ status: 'invalid_status' as any // 无效的状态
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client.videos[':id'].status.$put({
|
|
|
+ param: { id: testAsset.id.toString() },
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(400);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持状态更新为待审核', async () => {
|
|
|
+ const testAsset = testVideoAssets[2]; // 当前是rejected
|
|
|
+ const requestData = {
|
|
|
+ status: AssetStatus.PENDING as const
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await client.videos[':id'].status.$put({
|
|
|
+ param: { id: testAsset.id.toString() },
|
|
|
+ json: requestData
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${testToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.status).toBe('pending');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|