forked from wangziqi/gongxue-base
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
|
import { ClassesService } from './classes.service';
|
|
import { ClassStudent } from '../entities';
|
|
|
|
function createService(
|
|
classStudentRepo: Record<string, jest.Mock>,
|
|
classRepo: Record<string, jest.Mock> = { findOne: jest.fn().mockResolvedValue({ id: 3 }) },
|
|
studentRepo: Record<string, jest.Mock> = {
|
|
find: jest.fn().mockResolvedValue([{ id: 8 }, { id: 9 }, { id: 10 }]),
|
|
},
|
|
) {
|
|
return new ClassesService(
|
|
classRepo as never,
|
|
classStudentRepo as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
studentRepo as never,
|
|
{} as never,
|
|
);
|
|
}
|
|
|
|
describe('ClassesService — student membership lifecycle', () => {
|
|
it('marks an active membership as left instead of deleting it', async () => {
|
|
const membership = {
|
|
classId: 3,
|
|
studentId: 8,
|
|
status: 'active',
|
|
leaveDate: null,
|
|
} as ClassStudent;
|
|
const repo = {
|
|
findOne: jest.fn().mockResolvedValue(membership),
|
|
save: jest.fn().mockImplementation(async (value: ClassStudent) => value),
|
|
};
|
|
const service = createService(repo);
|
|
|
|
await service.removeStudent(3, 8);
|
|
|
|
expect(membership.status).toBe('left');
|
|
expect(membership.leaveDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
expect(repo.save).toHaveBeenCalledWith(membership);
|
|
});
|
|
|
|
it('rejects removing an already-left membership', async () => {
|
|
const repo = {
|
|
findOne: jest.fn().mockResolvedValue({ status: 'left' }),
|
|
save: jest.fn(),
|
|
};
|
|
const service = createService(repo);
|
|
|
|
await expect(service.removeStudent(3, 8)).rejects.toBeInstanceOf(BadRequestException);
|
|
expect(repo.save).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects removing a student without a membership', async () => {
|
|
const repo = {
|
|
findOne: jest.fn().mockResolvedValue(null),
|
|
save: jest.fn(),
|
|
};
|
|
const service = createService(repo);
|
|
|
|
await expect(service.removeStudent(3, 8)).rejects.toBeInstanceOf(NotFoundException);
|
|
});
|
|
|
|
it('reactivates left memberships, creates new ones, and skips active ones', async () => {
|
|
const left = {
|
|
id: 1,
|
|
classId: 3,
|
|
studentId: 8,
|
|
status: 'left',
|
|
joinDate: '2026-01-01',
|
|
leaveDate: '2026-02-01',
|
|
} as ClassStudent;
|
|
const active = { id: 2, classId: 3, studentId: 9, status: 'active' } as ClassStudent;
|
|
const repo = {
|
|
find: jest.fn().mockResolvedValue([left, active]),
|
|
create: jest.fn().mockImplementation((value: Partial<ClassStudent>) => value),
|
|
save: jest.fn().mockImplementation(async (value: ClassStudent[]) => value),
|
|
};
|
|
const service = createService(repo);
|
|
|
|
const result = await service.addStudents(3, [8, 9, 10, 10]);
|
|
|
|
expect(result).toEqual({ added: 2, skipped: 1 });
|
|
expect(left).toMatchObject({ status: 'active', leaveDate: null });
|
|
expect(left.joinDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
expect(repo.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({ classId: 3, studentId: 10, status: 'active' }),
|
|
);
|
|
expect(repo.save).toHaveBeenCalledWith(
|
|
expect.arrayContaining([left, expect.objectContaining({ studentId: 10 })]),
|
|
);
|
|
});
|
|
});
|