Files
gongxue-base/apps/server/src/archive/archive.boundaries.spec.ts
xiong cbc04fea4f
Some checks failed
CI 检查 / lint (pull_request) Has been cancelled
CI 检查 / typecheck (pull_request) Has been cancelled
CI 检查 / test (pull_request) Has been cancelled
feat: add exam score management
2026-07-21 16:04:17 +08:00

119 lines
4.2 KiB
TypeScript

import { BadRequestException, NotFoundException } from '@nestjs/common';
import { ArchiveService } from './archive.service';
function createService(repos: Partial<Record<string, Record<string, jest.Mock>>> = {}) {
return new ArchiveService(
(repos.student ?? {}) as never,
(repos.profile ?? {}) as never,
(repos.enrollment ?? {}) as never,
(repos.exam ?? {}) as never,
(repos.learning ?? {}) as never,
(repos.result ?? {}) as never,
(repos.attachment ?? {}) as never,
(repos.attendance ?? {}) as never,
{} as never,
);
}
describe('ArchiveService — resource and relationship boundaries', () => {
it('rejects adding archive records for a missing student', async () => {
const student = { findOne: jest.fn().mockResolvedValue(null) };
const service = createService({ student });
await expect(
service.addEnrollment(404, { courseCategory: '文化', classType: '冲刺' }),
).rejects.toBeInstanceOf(NotFoundException);
await expect(
service.addLearningRecord(404, {
recordDate: '2026-07-14',
recordType: '沟通',
content: '内容',
}),
).rejects.toBeInstanceOf(NotFoundException);
});
it('rejects linking an exam score to another student enrollment', async () => {
const exam = { create: jest.fn(), save: jest.fn() };
const service = createService({
student: { findOne: jest.fn().mockResolvedValue({ id: 7 }) },
enrollment: { findOne: jest.fn().mockResolvedValue(null) },
exam,
});
await expect(
service.addExamScore(7, {
examType: '月考',
subject: '语文',
score: 90,
enrollmentId: 99,
}),
).rejects.toBeInstanceOf(BadRequestException);
expect(exam.save).not.toHaveBeenCalled();
});
it('rejects moving an existing exam score to another student enrollment', async () => {
const exam = {
findOne: jest.fn().mockResolvedValue({ id: 3, studentId: 7, enrollmentId: 1 }),
save: jest.fn(),
};
const service = createService({
enrollment: { findOne: jest.fn().mockResolvedValue(null) },
exam,
});
await expect(service.updateExamScore(3, { enrollmentId: 99 })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(exam.save).not.toHaveBeenCalled();
});
it('keeps exam-management scores read-only in the student archive', async () => {
const exam = {
findOne: jest.fn().mockResolvedValue({ id: 3, studentId: 7, examId: 8 }),
save: jest.fn(),
update: jest.fn(),
};
const service = createService({ exam });
await expect(service.updateExamScore(3, { score: 95 })).rejects.toBeInstanceOf(
BadRequestException,
);
await expect(service.deleteExamScore(3)).rejects.toBeInstanceOf(BadRequestException);
expect(exam.save).not.toHaveBeenCalled();
expect(exam.update).not.toHaveBeenCalled();
});
it('rejects a missing attachment upload before writing to disk', async () => {
const service = createService({ student: { findOne: jest.fn() } });
await expect(service.addAttachment(7, undefined as never, 'other')).rejects.toBeInstanceOf(
BadRequestException,
);
});
it('rejects attachment path traversal', async () => {
const service = createService({
attachment: {
findOne: jest.fn().mockResolvedValue({
id: 1,
studentId: 7,
filePath: '../../etc/passwd',
}),
},
});
await expect(service.getAttachmentFile(7, 1)).rejects.toBeInstanceOf(BadRequestException);
});
it('returns not found for update/delete of absent child records', async () => {
const service = createService({
enrollment: { findOne: jest.fn().mockResolvedValue(null) },
exam: { findOne: jest.fn().mockResolvedValue(null) },
learning: { findOne: jest.fn().mockResolvedValue(null) },
attachment: { findOne: jest.fn().mockResolvedValue(null) },
});
await expect(service.updateEnrollment(1, {})).rejects.toBeInstanceOf(NotFoundException);
await expect(service.deleteExamScore(1)).rejects.toBeInstanceOf(NotFoundException);
await expect(service.deleteLearningRecord(1)).rejects.toBeInstanceOf(NotFoundException);
await expect(service.deleteAttachment(1)).rejects.toBeInstanceOf(NotFoundException);
});
});