115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { ArchiveService } from './archive.service';
|
|
|
|
describe('ArchiveService purge sub-records', () => {
|
|
const createService = (overrides?: {
|
|
enrollment?: Record<string, unknown>;
|
|
examScore?: Record<string, unknown>;
|
|
learningRecord?: Record<string, unknown>;
|
|
attachment?: Record<string, unknown>;
|
|
scoreCount?: number;
|
|
}) => {
|
|
const enrollmentRepo = {
|
|
findOne: jest.fn().mockResolvedValue({
|
|
id: 1,
|
|
status: 'archived',
|
|
...overrides?.enrollment,
|
|
}),
|
|
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
};
|
|
const examScoreRepo = {
|
|
findOne: jest.fn().mockResolvedValue({
|
|
id: 2,
|
|
status: 'archived',
|
|
...overrides?.examScore,
|
|
}),
|
|
count: jest.fn().mockResolvedValue(overrides?.scoreCount ?? 0),
|
|
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
};
|
|
const learningRecordRepo = {
|
|
findOne: jest.fn().mockResolvedValue({
|
|
id: 3,
|
|
status: 'archived',
|
|
...overrides?.learningRecord,
|
|
}),
|
|
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
};
|
|
const attachmentRepo = {
|
|
findOne: jest.fn().mockResolvedValue({
|
|
id: 4,
|
|
status: 'archived',
|
|
filePath: 'x.pdf',
|
|
...overrides?.attachment,
|
|
}),
|
|
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
};
|
|
const service = new ArchiveService(
|
|
{} as never,
|
|
{} as never,
|
|
enrollmentRepo as never,
|
|
examScoreRepo as never,
|
|
learningRecordRepo as never,
|
|
{} as never,
|
|
attachmentRepo as never,
|
|
{} as never,
|
|
{} as never,
|
|
);
|
|
return { service, enrollmentRepo, examScoreRepo, learningRecordRepo, attachmentRepo };
|
|
};
|
|
|
|
it('rejects non-archived sub-records', async () => {
|
|
const { service, enrollmentRepo } = createService({ enrollment: { status: 'active' } });
|
|
await expect(service.purgeEnrollment(1)).rejects.toThrow(
|
|
new BadRequestException('仅已归档报名记录可以永久删除,请先归档'),
|
|
);
|
|
expect(enrollmentRepo.delete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects enrollments referenced by exam scores', async () => {
|
|
const { service, enrollmentRepo } = createService({ scoreCount: 1 });
|
|
await expect(service.purgeEnrollment(1)).rejects.toThrow(
|
|
new BadRequestException('该报名记录已被考试成绩引用,无法永久删除'),
|
|
);
|
|
expect(enrollmentRepo.delete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('deletes archived enrollment, exam score, and learning record', async () => {
|
|
const { service, enrollmentRepo, examScoreRepo, learningRecordRepo } = createService();
|
|
await expect(service.purgeEnrollment(1)).resolves.toEqual({
|
|
message: '已永久删除报名记录(不可恢复)',
|
|
});
|
|
await expect(service.purgeExamScore(2)).resolves.toEqual({
|
|
message: '已永久删除考试成绩(不可恢复)',
|
|
});
|
|
await expect(service.purgeLearningRecord(3)).resolves.toEqual({
|
|
message: '已永久删除学习记录(不可恢复)',
|
|
});
|
|
expect(enrollmentRepo.delete).toHaveBeenCalledWith(1);
|
|
expect(examScoreRepo.delete).toHaveBeenCalledWith(2);
|
|
expect(learningRecordRepo.delete).toHaveBeenCalledWith(3);
|
|
});
|
|
|
|
it('deletes the attachment row and removes the disk file', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'archive-purge-'));
|
|
process.env.UPLOAD_DIR = tmpDir;
|
|
const filePath = 'x.pdf';
|
|
const fullPath = path.join(tmpDir, 'archive', filePath);
|
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
fs.writeFileSync(fullPath, 'data');
|
|
try {
|
|
const { service, attachmentRepo } = createService({ attachment: { filePath } });
|
|
await expect(service.purgeAttachment(4)).resolves.toEqual({
|
|
message: '已永久删除附件(不可恢复)',
|
|
});
|
|
expect(fs.existsSync(fullPath)).toBe(false);
|
|
expect(attachmentRepo.delete).toHaveBeenCalledWith(4);
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
delete process.env.UPLOAD_DIR;
|
|
}
|
|
});
|
|
});
|