125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
import { NotFoundException } from '@nestjs/common';
|
|
import { UpdateStudentsTool } from './update-students.tool';
|
|
|
|
function createTool(overrides: Record<string, unknown> = {}) {
|
|
const studentsService = {
|
|
update: jest.fn(async (id: number, dto: Record<string, unknown>) => ({
|
|
id,
|
|
name: dto.name ?? '学生',
|
|
})),
|
|
...(overrides.studentsService ?? {}),
|
|
};
|
|
const tool = new UpdateStudentsTool(studentsService as never);
|
|
return { tool, studentsService };
|
|
}
|
|
|
|
const validInput = {
|
|
updates: [
|
|
{ id: 201, name: '於嘉丽' },
|
|
{ id: 172, name: '徐玚' },
|
|
],
|
|
};
|
|
|
|
describe('UpdateStudentsTool', () => {
|
|
it('exposes student:edit permission and student skill', () => {
|
|
const { tool } = createTool();
|
|
expect(tool.name).toBe('update_students');
|
|
expect(tool.skillKey).toBe('student');
|
|
expect(tool.requiredPermission).toBe('student:edit');
|
|
});
|
|
|
|
it('rejects forbidden, unknown, and empty input', () => {
|
|
const { tool } = createTool();
|
|
expect(tool.validate({ userId: 1 }).ok).toBe(false);
|
|
expect(tool.validate({ updates: [], admin: true }).ok).toBe(false);
|
|
expect(tool.validate({}).ok).toBe(false);
|
|
expect(tool.validate({ updates: [] }).ok).toBe(false);
|
|
expect(tool.validate({ updates: [{ id: 201 }] }).ok).toBe(false);
|
|
});
|
|
|
|
it('rejects invalid ids, duplicate ids, invalid fields, and oversized batches', () => {
|
|
const { tool } = createTool();
|
|
expect(tool.validate({ updates: [{ id: 0, name: 'A' }] }).ok).toBe(false);
|
|
expect(tool.validate({ updates: [{ id: 'x', name: 'A' }] }).ok).toBe(false);
|
|
expect(
|
|
tool.validate({
|
|
updates: [
|
|
{ id: 201, name: 'A' },
|
|
{ id: 201, name: 'B' },
|
|
],
|
|
}).ok,
|
|
).toBe(false);
|
|
expect(tool.validate({ updates: [{ id: 201, status: 'archived' }] }).ok).toBe(false);
|
|
expect(tool.validate({ updates: [{ id: 201, name: 'A'.repeat(51) }] }).ok).toBe(false);
|
|
expect(
|
|
tool.validate({
|
|
updates: Array.from({ length: 13 }, (_, index) => ({ id: index + 1, name: 'A' })),
|
|
}).ok,
|
|
).toBe(false);
|
|
});
|
|
|
|
it('accepts all supported editable fields', () => {
|
|
const { tool } = createTool();
|
|
const result = tool.validate({
|
|
updates: [
|
|
{
|
|
id: 201,
|
|
name: '於嘉丽',
|
|
studentNo: 'S201',
|
|
phone: '13800138000',
|
|
idNumber: 'ID201',
|
|
gender: '女',
|
|
ethnicity: '汉族',
|
|
emergencyContact: '家长',
|
|
emergencyPhone: '13900139000',
|
|
organizationId: 2,
|
|
supervisor: '王老师',
|
|
status: 'active',
|
|
},
|
|
],
|
|
});
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.value.updates[0]).toMatchObject({
|
|
id: 201,
|
|
name: '於嘉丽',
|
|
organizationId: 2,
|
|
status: 'active',
|
|
});
|
|
}
|
|
});
|
|
|
|
it('updates every student and reports a safe summary', async () => {
|
|
const { tool, studentsService } = createTool();
|
|
const result = await tool.execute(validInput, {} as never);
|
|
expect(studentsService.update).toHaveBeenCalledTimes(2);
|
|
expect(studentsService.update).toHaveBeenCalledWith(201, { name: '於嘉丽' });
|
|
expect(result).toEqual({
|
|
message: '成功更新 2 名学生,失败 0 条',
|
|
updated: [
|
|
{ id: 201, name: '於嘉丽' },
|
|
{ id: 172, name: '徐玚' },
|
|
],
|
|
failed: [],
|
|
});
|
|
expect(JSON.stringify(result)).not.toContain('13800138000');
|
|
});
|
|
|
|
it('continues when one student cannot be updated', async () => {
|
|
const { tool } = createTool({
|
|
studentsService: {
|
|
update: jest
|
|
.fn()
|
|
.mockRejectedValueOnce(new NotFoundException('not found'))
|
|
.mockResolvedValueOnce({ id: 172, name: '徐玚' }),
|
|
},
|
|
});
|
|
const result = await tool.execute(validInput, {} as never);
|
|
expect(result).toEqual({
|
|
message: '成功更新 1 名学生,失败 1 条',
|
|
updated: [{ id: 172, name: '徐玚' }],
|
|
failed: [{ id: 201, error: '学生不存在' }],
|
|
});
|
|
});
|
|
});
|