文件
hyb-oa/server/tests/unit/routes/me.test.js
T

183 行
4.8 KiB
JavaScript

const Fastify = require('fastify');
const meRoutes = require('../../../src/routes/me');
const { AppError } = require('../../../src/utils/AppError');
function createPrismaState(overrides = {}) {
const auditEntries = [];
const employeeRecord = {
id: 'employee-1',
employee_no: 'E001',
name: '测试员工',
phone: '13800000000',
email: '[email protected]',
department_id: 'department-1',
direct_manager_id: 'manager-1',
status: 'active',
emergency_contact_name: null,
emergency_contact_phone: null,
bank_account_encrypted: '',
bank_name: '',
avatar_path: null,
id_number_encrypted: null,
notification_push_enabled: true,
roles: [{ role_code: 'employee' }],
wecom_identity_binding: null,
...overrides,
};
return {
prisma: {
employee: {
findUnique: vi.fn().mockResolvedValue(employeeRecord),
update: vi.fn().mockImplementation(async ({ data }) => {
employeeRecord.notification_push_enabled = data.notification_push_enabled;
return {
id: employeeRecord.id,
notification_push_enabled: employeeRecord.notification_push_enabled,
};
}),
},
auditLog: {
create: vi.fn().mockImplementation(async ({ data }) => {
auditEntries.push(data);
return { id: `audit-${auditEntries.length}` };
}),
},
},
employeeRecord,
auditEntries,
};
}
async function buildApp(prisma) {
const app = Fastify();
app.decorate('prisma', prisma);
app.decorate('authenticate', async (request) => {
if (!request.headers.authorization) {
throw new AppError({
code: 'AUTH_001',
message: '未登录',
statusCode: 401,
});
}
request.employee = {
id: 'employee-1',
permissionCodes: [],
};
});
app.setErrorHandler((error, request, reply) => {
if (error instanceof AppError) {
return reply.status(error.statusCode).send({
requestId: request.id,
error: {
code: error.code,
message: error.message,
details: error.details ?? {},
},
});
}
return reply.status(500).send({
requestId: request.id,
error: {
code: 'SYS_001',
message: '服务器内部错误',
details: {},
},
});
});
await app.register(meRoutes, { prefix: '/api' });
return app;
}
describe('me routes', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('returns notificationPushEnabled in /me response', async () => {
const { prisma } = createPrismaState({ notification_push_enabled: false });
const app = await buildApp(prisma);
const response = await app.inject({
method: 'GET',
url: '/api/me',
headers: { authorization: 'Bearer test-token' },
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toEqual(
expect.objectContaining({
id: 'employee-1',
notificationPushEnabled: false,
profileCompleteness: {
hasEmergencyContact: false,
hasBankAccount: false,
hasAvatar: false,
hasIdNumber: false,
score: 0,
},
}),
);
await app.close();
});
it('calculates profileCompleteness score from completed profile fields', async () => {
const { prisma } = createPrismaState({
emergency_contact_name: '联系人',
emergency_contact_phone: '13900000000',
bank_account_encrypted: 'ENC',
bank_name: '招商银行',
avatar_path: null,
id_number_encrypted: null,
});
const app = await buildApp(prisma);
const response = await app.inject({
method: 'GET',
url: '/api/me',
headers: { authorization: 'Bearer test-token' },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.profileCompleteness).toEqual({
hasEmergencyContact: true,
hasBankAccount: true,
hasAvatar: false,
hasIdNumber: false,
score: 50,
});
await app.close();
});
it('updates notification preferences for current employee', async () => {
const { prisma, employeeRecord, auditEntries } = createPrismaState();
const app = await buildApp(prisma);
const response = await app.inject({
method: 'PATCH',
url: '/api/me/notification-preferences',
headers: { authorization: 'Bearer test-token' },
payload: {
notificationPushEnabled: false,
},
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toEqual({ notificationPushEnabled: false });
expect(employeeRecord.notification_push_enabled).toBe(false);
expect(auditEntries[0]).toEqual(
expect.objectContaining({
action: 'EMPLOYEE_NOTIFICATION_PREFERENCE_UPDATED',
}),
);
await app.close();
});
});