feat(imports): 导入向导兼容旧版工作簿并完善执行/预览
- 新增 xls 工作簿回退解析与测试 - 执行/预览/权限校验逻辑完善
This commit is contained in:
@@ -8,6 +8,7 @@ import { ImportRun } from './entities/import-run.entity';
|
||||
import { ImportStep } from './entities/import-step.entity';
|
||||
import { ImportRow } from './entities/import-row.entity';
|
||||
import { ImportsService } from './imports.service';
|
||||
import * as workbookModule from './imports.workbook';
|
||||
import type { ParsedImportFile } from './imports.types';
|
||||
|
||||
function makeRowsRepo() {
|
||||
@@ -810,4 +811,101 @@ describe('ImportsService', () => {
|
||||
expect(result.rows[0]).toMatchObject({ action: 'skip', status: 'valid' });
|
||||
expect(result.rows[0].errors.join(';')).toContain('按策略跳过');
|
||||
});
|
||||
|
||||
it('preflightFile 透传 headerRow 到解析层', async () => {
|
||||
const parseSpy = jest
|
||||
.spyOn(workbookModule, 'parseSheets')
|
||||
.mockResolvedValue([]);
|
||||
try {
|
||||
const service = new ImportsService(
|
||||
makeRunsRepo({} as ImportRun) as never,
|
||||
makeStepsRepo({} as ImportStep) as never,
|
||||
makeRowsRepo() as never,
|
||||
{} as never,
|
||||
);
|
||||
const report = await service.preflightFile(fileOf('students.xlsx', Buffer.from('x')), 3);
|
||||
expect(parseSpy).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
'students.xlsx',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
3,
|
||||
);
|
||||
expect(report.verdict).toBe('blocked');
|
||||
} finally {
|
||||
parseSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('createRun 按 stages 的 headerRow 生成对应工作表视图并写入 sheetsJson', async () => {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('名单');
|
||||
worksheet.addRow(['标题行', null]);
|
||||
worksheet.addRow(['姓名', '学号']);
|
||||
worksheet.addRow(['', '']);
|
||||
worksheet.addRow(['张三', '2024001']);
|
||||
const buffer = (await workbook.xlsx.writeBuffer()) as Buffer;
|
||||
const run = {
|
||||
id: 'run-h',
|
||||
userId: 7,
|
||||
conversationId: null,
|
||||
source: 'manual',
|
||||
fileName: 'students.xlsx',
|
||||
sheetsJson: '[]',
|
||||
status: 'ready',
|
||||
currentStepKey: null,
|
||||
error: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
} as ImportRun;
|
||||
const runsRepo = makeRunsRepo(run);
|
||||
const stepsRepo = makeStepsRepo({} as ImportStep);
|
||||
const service = new ImportsService(
|
||||
runsRepo as never,
|
||||
stepsRepo as never,
|
||||
makeRowsRepo() as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
await service.createRun(
|
||||
principal,
|
||||
'manual',
|
||||
fileOf('students.xlsx', buffer),
|
||||
null,
|
||||
[{ stepKey: 'students', sheet: '名单', headerRow: 2 }],
|
||||
);
|
||||
|
||||
const created = runsRepo.create.mock.calls[0][0] as { sheetsJson: string };
|
||||
const sheets = JSON.parse(created.sheetsJson) as Array<{
|
||||
headers: string[];
|
||||
rows: unknown[][];
|
||||
headerRow: number;
|
||||
rowNumbers: number[];
|
||||
}>;
|
||||
expect(sheets).toHaveLength(1);
|
||||
expect(sheets[0].headers).toEqual(['姓名', '学号']);
|
||||
expect(sheets[0].rows).toEqual([['张三', '2024001']]);
|
||||
expect(sheets[0].headerRow).toBe(2);
|
||||
expect(sheets[0].rowNumbers).toEqual([4]);
|
||||
});
|
||||
|
||||
it('createRun 拒绝映射到工作表表头之外的列名', async () => {
|
||||
const buffer = await xlsxBuffer(studentSheet());
|
||||
const service = new ImportsService(
|
||||
makeRunsRepo({} as ImportRun) as never,
|
||||
makeStepsRepo({} as ImportStep) as never,
|
||||
makeRowsRepo() as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.createRun(
|
||||
principal,
|
||||
'manual',
|
||||
fileOf('students.xlsx', buffer),
|
||||
null,
|
||||
[{ stepKey: 'students', sheet: '学生' }],
|
||||
{ students: { name: '姓名', studentNo: '不存在的列' } },
|
||||
),
|
||||
).rejects.toThrow('不在工作表表头中');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user