Files
gongxue-base/apps/server/src/agent-tools/tools/update-students.tool.spec.ts

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: '学生不存在' }],
});
});
});