@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
217 lines (180 loc) • 7.45 kB
text/typescript
import { UserJSON } from '@clerk/backend';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { UserModel } from '@/database/models/user';
import { UserItem } from '@/database/schemas';
import { pino } from '@/libs/logger';
import { AgentService } from '@/server/services/agent';
import { UserService } from './index';
// Mock dependencies
vi.mock('@/database/models/user', () => {
const MockUserModel = vi.fn();
// @ts-ignore
MockUserModel.findById = vi.fn();
// @ts-ignore
MockUserModel.createUser = vi.fn();
// @ts-ignore
MockUserModel.deleteUser = vi.fn();
// Mock instance methods
MockUserModel.prototype.updateUser = vi.fn();
return { UserModel: MockUserModel };
});
vi.mock('@/libs/logger', () => ({
pino: {
info: vi.fn(),
},
}));
vi.mock('@/server/services/agent', () => ({
AgentService: vi.fn().mockImplementation(() => ({
createInbox: vi.fn().mockResolvedValue(undefined),
})),
}));
let service: UserService;
const mockUserId = 'test-user-id';
// Mock user data
const mockUserJSON: UserJSON = {
id: mockUserId,
email_addresses: [{ id: 'email-1', email_address: 'test@example.com' }],
phone_numbers: [{ id: 'phone-1', phone_number: '+1234567890' }],
primary_email_address_id: 'email-1',
primary_phone_number_id: 'phone-1',
image_url: 'https://example.com/avatar.jpg',
first_name: 'Test',
last_name: 'User',
username: 'testuser',
created_at: '2023-01-01T00:00:00Z',
} as unknown as UserJSON;
beforeEach(() => {
service = new UserService();
vi.clearAllMocks();
});
describe('UserService', () => {
describe('createUser', () => {
it('should create a new user when user does not exist', async () => {
// Mock user not found
vi.mocked(UserModel.findById).mockResolvedValue(null as any);
const result = await service.createUser(mockUserId, mockUserJSON);
expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(UserModel.createUser).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
id: mockUserId,
email: 'test@example.com',
phone: '+1234567890',
firstName: 'Test',
lastName: 'User',
username: 'testuser',
avatar: 'https://example.com/avatar.jpg',
clerkCreatedAt: new Date('2023-01-01T00:00:00Z'),
}),
);
expect(AgentService).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(vi.mocked(AgentService).mock.results[0].value.createInbox).toHaveBeenCalled();
expect(result).toEqual({
message: 'user created',
success: true,
});
});
it('should not create user if already exists', async () => {
// Mock user found
vi.mocked(UserModel.findById).mockResolvedValue({ id: mockUserId } as UserItem);
const result = await service.createUser(mockUserId, mockUserJSON);
expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(UserModel.createUser).not.toHaveBeenCalled();
expect(AgentService).not.toHaveBeenCalled();
expect(result).toEqual({
message: 'user not created due to user already existing in the database',
success: false,
});
});
it('should handle user without primary phone number', async () => {
vi.mocked(UserModel.findById).mockResolvedValue(null as any);
const userWithoutPrimaryPhone = {
...mockUserJSON,
primary_phone_number_id: null,
phone_numbers: [{ id: 'phone-1', phone_number: '+1234567890' }],
} as UserJSON;
await service.createUser(mockUserId, userWithoutPrimaryPhone);
expect(UserModel.createUser).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
phone: '+1234567890', // Should use first phone number
}),
);
expect(AgentService).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(vi.mocked(AgentService).mock.results[0].value.createInbox).toHaveBeenCalled();
});
});
describe('deleteUser', () => {
it('should delete user', async () => {
await service.deleteUser(mockUserId);
expect(UserModel.deleteUser).toHaveBeenCalledWith(expect.anything(), mockUserId);
});
it('should throw error if deletion fails', async () => {
const error = new Error('Deletion failed');
vi.mocked(UserModel.deleteUser).mockRejectedValue(error);
await expect(service.deleteUser(mockUserId)).rejects.toThrow('Deletion failed');
});
});
describe('updateUser', () => {
it('should update user when user exists', async () => {
// Mock user found
vi.mocked(UserModel.findById).mockResolvedValue({ id: mockUserId } as UserItem);
const mockUpdateUser = vi.mocked(UserModel.prototype.updateUser);
const result = await service.updateUser(mockUserId, mockUserJSON);
expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(pino.info).toHaveBeenCalledWith('updating user due to clerk webhook');
expect(mockUpdateUser).toHaveBeenCalledWith(
expect.objectContaining({
id: mockUserId,
email: 'test@example.com',
phone: '+1234567890',
firstName: 'Test',
lastName: 'User',
username: 'testuser',
avatar: 'https://example.com/avatar.jpg',
}),
);
expect(result).toEqual({
message: 'user updated',
success: true,
});
});
it('should not update user when user does not exist', async () => {
// Mock user not found
vi.mocked(UserModel.findById).mockResolvedValue(null as any);
const mockUpdateUser = vi.mocked(UserModel.prototype.updateUser);
const result = await service.updateUser(mockUserId, mockUserJSON);
expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
expect(mockUpdateUser).not.toHaveBeenCalled();
expect(result).toEqual({
message: "user not updated due to the user don't existing in the database",
success: false,
});
});
it('should handle user without primary email and phone', async () => {
vi.mocked(UserModel.findById).mockResolvedValue({ id: mockUserId } as UserItem);
const mockUpdateUser = vi.mocked(UserModel.prototype.updateUser);
const userWithoutPrimaryContacts = {
...mockUserJSON,
primary_email_address_id: null,
primary_phone_number_id: null,
email_addresses: [{ id: 'email-1', email_address: 'test@example.com' }],
phone_numbers: [{ id: 'phone-1', phone_number: '+1234567890' }],
} as UserJSON;
await service.updateUser(mockUserId, userWithoutPrimaryContacts);
// Verify that the first email and phone are used when primary is not specified
expect(mockUpdateUser).toHaveBeenCalledWith(
expect.objectContaining({
phone: '+1234567890',
}),
);
});
it('should handle update failure', async () => {
vi.mocked(UserModel.findById).mockResolvedValue({ id: mockUserId } as UserItem);
const mockUpdateUser = vi.mocked(UserModel.prototype.updateUser);
const error = new Error('Update failed');
mockUpdateUser.mockRejectedValue(error);
await expect(service.updateUser(mockUserId, mockUserJSON)).rejects.toThrow('Update failed');
});
});
});