Files
friendolls-server/src/friends/friends.service.spec.ts
2025-12-27 22:11:23 +08:00

481 lines
15 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { FriendsService } from './friends.service';
import { PrismaService } from '../database/prisma.service';
import {
NotFoundException,
BadRequestException,
ConflictException,
} from '@nestjs/common';
enum FriendRequestStatus {
PENDING = 'PENDING',
ACCEPTED = 'ACCEPTED',
DENIED = 'DENIED',
}
describe('FriendsService', () => {
let service: FriendsService;
let eventEmitter: EventEmitter2;
const mockUser1 = {
id: 'user-1',
keycloakSub: 'f:realm:user1',
email: 'user1@example.com',
name: 'User One',
username: 'user1',
picture: null,
roles: [],
lastLoginAt: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
};
const mockUser2 = {
id: 'user-2',
keycloakSub: 'f:realm:user2',
email: 'user2@example.com',
name: 'User Two',
username: 'user2',
picture: null,
roles: [],
lastLoginAt: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
};
const mockFriendRequest = {
id: 'request-1',
senderId: 'user-1',
receiverId: 'user-2',
status: FriendRequestStatus.PENDING,
createdAt: new Date(),
updatedAt: new Date(),
sender: mockUser1,
receiver: mockUser2,
};
const mockFriendship = {
id: 'friendship-1',
userId: 'user-1',
friendId: 'user-2',
createdAt: new Date(),
};
const mockPrismaService = {
user: {
findUnique: jest.fn(),
},
friendRequest: {
create: jest.fn(),
findFirst: jest.fn(),
findUnique: jest.fn(),
findMany: jest.fn(),
update: jest.fn(),
updateMany: jest.fn(),
delete: jest.fn(),
},
friendship: {
create: jest.fn(),
findFirst: jest.fn(),
findMany: jest.fn(),
deleteMany: jest.fn(),
},
$transaction: jest.fn(),
};
const mockEventEmitter = {
emit: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FriendsService,
{
provide: PrismaService,
useValue: mockPrismaService,
},
{
provide: EventEmitter2,
useValue: mockEventEmitter,
},
],
}).compile();
service = module.get<FriendsService>(FriendsService);
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('sendFriendRequest', () => {
it('should send a friend request successfully', async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser2);
mockPrismaService.friendship.findFirst.mockResolvedValue(null);
mockPrismaService.friendRequest.findFirst.mockResolvedValue(null);
mockPrismaService.friendRequest.create.mockResolvedValue(
mockFriendRequest,
);
// Mock transaction implementation
mockPrismaService.$transaction.mockImplementation(
async (callback: (prisma: any) => Promise<any>) => {
return (await callback(mockPrismaService)) as unknown;
},
);
const result = await service.sendFriendRequest('user-1', 'user-2');
expect(result).toEqual(mockFriendRequest);
expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
where: { id: 'user-2' },
});
expect(mockPrismaService.friendRequest.create).toHaveBeenCalledWith({
data: {
senderId: 'user-1',
receiverId: 'user-2',
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
});
expect(mockEventEmitter.emit).toHaveBeenCalled();
});
it('should throw BadRequestException when trying to send request to self', async () => {
await expect(
service.sendFriendRequest('user-1', 'user-1'),
).rejects.toThrow(BadRequestException);
await expect(
service.sendFriendRequest('user-1', 'user-1'),
).rejects.toThrow('Cannot send friend request to yourself');
});
it('should throw NotFoundException when receiver does not exist', async () => {
mockPrismaService.user.findUnique.mockResolvedValue(null);
mockPrismaService.$transaction.mockImplementation(
async (callback: (prisma: any) => Promise<any>) => {
return (await callback(mockPrismaService)) as unknown;
},
);
await expect(
service.sendFriendRequest('user-1', 'nonexistent'),
).rejects.toThrow(NotFoundException);
await expect(
service.sendFriendRequest('user-1', 'nonexistent'),
).rejects.toThrow('User not found');
});
it('should throw ConflictException when users are already friends', async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser2);
mockPrismaService.friendship.findFirst.mockResolvedValue(mockFriendship);
mockPrismaService.$transaction.mockImplementation(
async (callback: (prisma: any) => Promise<any>) => {
return (await callback(mockPrismaService)) as unknown;
},
);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow(ConflictException);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow('You are already friends with this user');
});
it('should throw ConflictException when request already exists', async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser2);
mockPrismaService.friendship.findFirst.mockResolvedValue(null);
mockPrismaService.friendRequest.findFirst.mockResolvedValue(
mockFriendRequest,
);
mockPrismaService.$transaction.mockImplementation(
async (callback: (prisma: any) => Promise<any>) => {
return (await callback(mockPrismaService)) as unknown;
},
);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow(ConflictException);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow('You already sent a friend request to this user');
});
it('should throw ConflictException when reverse request exists', async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser1);
mockPrismaService.friendship.findFirst.mockResolvedValue(null);
mockPrismaService.friendRequest.findFirst.mockResolvedValue({
...mockFriendRequest,
senderId: 'user-2',
receiverId: 'user-1',
});
mockPrismaService.$transaction.mockImplementation(
async (callback: (prisma: any) => Promise<any>) => {
return (await callback(mockPrismaService)) as unknown;
},
);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow(ConflictException);
await expect(
service.sendFriendRequest('user-1', 'user-2'),
).rejects.toThrow('This user already sent you a friend request');
});
});
describe('getPendingReceivedRequests', () => {
it('should return pending received requests', async () => {
const requests = [mockFriendRequest];
mockPrismaService.friendRequest.findMany.mockResolvedValue(requests);
const result = await service.getPendingReceivedRequests('user-2');
expect(result).toEqual(requests);
expect(mockPrismaService.friendRequest.findMany).toHaveBeenCalledWith({
where: {
receiverId: 'user-2',
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
orderBy: {
createdAt: 'desc',
},
});
});
});
describe('getPendingSentRequests', () => {
it('should return pending sent requests', async () => {
const requests = [mockFriendRequest];
mockPrismaService.friendRequest.findMany.mockResolvedValue(requests);
const result = await service.getPendingSentRequests('user-1');
expect(result).toEqual(requests);
expect(mockPrismaService.friendRequest.findMany).toHaveBeenCalledWith({
where: {
senderId: 'user-1',
status: FriendRequestStatus.PENDING,
},
include: {
sender: true,
receiver: true,
},
orderBy: {
createdAt: 'desc',
},
});
});
});
describe('acceptFriendRequest', () => {
it('should accept a friend request and create friendship', async () => {
const acceptedRequest = {
...mockFriendRequest,
status: FriendRequestStatus.ACCEPTED,
updatedAt: expect.any(Date),
};
mockPrismaService.friendRequest.findUnique.mockResolvedValue(
mockFriendRequest,
);
mockPrismaService.$transaction.mockResolvedValue([acceptedRequest]);
const result = await service.acceptFriendRequest('request-1', 'user-2');
expect(result).toEqual(acceptedRequest);
expect(mockPrismaService.$transaction).toHaveBeenCalled();
expect(mockEventEmitter.emit).toHaveBeenCalled();
});
it('should throw NotFoundException when request does not exist', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue(null);
await expect(
service.acceptFriendRequest('nonexistent', 'user-2'),
).rejects.toThrow(NotFoundException);
await expect(
service.acceptFriendRequest('nonexistent', 'user-2'),
).rejects.toThrow('Friend request not found');
});
it('should throw BadRequestException when user is not the receiver', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue(
mockFriendRequest,
);
await expect(
service.acceptFriendRequest('request-1', 'user-3'),
).rejects.toThrow(BadRequestException);
await expect(
service.acceptFriendRequest('request-1', 'user-3'),
).rejects.toThrow('You can only accept friend requests sent to you');
});
it('should throw BadRequestException when request is already accepted', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue({
...mockFriendRequest,
status: FriendRequestStatus.ACCEPTED,
});
await expect(
service.acceptFriendRequest('request-1', 'user-2'),
).rejects.toThrow(BadRequestException);
await expect(
service.acceptFriendRequest('request-1', 'user-2'),
).rejects.toThrow('Friend request is already accepted');
});
});
describe('denyFriendRequest', () => {
it('should deny a friend request', async () => {
const deniedRequest = {
...mockFriendRequest,
status: FriendRequestStatus.DENIED,
updatedAt: expect.any(Date),
};
mockPrismaService.friendRequest.findUnique.mockResolvedValue(
mockFriendRequest,
);
mockPrismaService.friendRequest.delete.mockResolvedValue(
mockFriendRequest,
);
const result = await service.denyFriendRequest('request-1', 'user-2');
expect(result).toEqual(deniedRequest);
expect(mockPrismaService.friendRequest.delete).toHaveBeenCalledWith({
where: { id: 'request-1' },
});
expect(mockEventEmitter.emit).toHaveBeenCalled();
});
it('should throw NotFoundException when request does not exist', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue(null);
await expect(
service.denyFriendRequest('nonexistent', 'user-2'),
).rejects.toThrow(NotFoundException);
});
it('should throw BadRequestException when user is not the receiver', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue(
mockFriendRequest,
);
await expect(
service.denyFriendRequest('request-1', 'user-3'),
).rejects.toThrow(BadRequestException);
});
it('should throw BadRequestException when request is already denied', async () => {
mockPrismaService.friendRequest.findUnique.mockResolvedValue({
...mockFriendRequest,
status: FriendRequestStatus.DENIED,
});
await expect(
service.denyFriendRequest('request-1', 'user-2'),
).rejects.toThrow(BadRequestException);
});
});
describe('getFriends', () => {
it('should return list of friends', async () => {
const friendships = [
{
...mockFriendship,
friend: mockUser2,
},
];
mockPrismaService.friendship.findMany.mockResolvedValue(friendships);
const result = await service.getFriends('user-1');
expect(result).toEqual(friendships);
expect(mockPrismaService.friendship.findMany).toHaveBeenCalledWith({
where: { userId: 'user-1' },
include: {
friend: {
include: {
activeDoll: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
});
});
describe('unfriend', () => {
it('should unfriend a user successfully', async () => {
mockPrismaService.friendship.findFirst.mockResolvedValue(mockFriendship);
mockPrismaService.friendship.deleteMany.mockResolvedValue({ count: 2 });
await service.unfriend('user-1', 'user-2');
expect(mockPrismaService.friendship.deleteMany).toHaveBeenCalledWith({
where: {
OR: [
{ userId: 'user-1', friendId: 'user-2' },
{ userId: 'user-2', friendId: 'user-1' },
],
},
});
expect(mockEventEmitter.emit).toHaveBeenCalled();
});
it('should throw BadRequestException when trying to unfriend self', async () => {
await expect(service.unfriend('user-1', 'user-1')).rejects.toThrow(
BadRequestException,
);
await expect(service.unfriend('user-1', 'user-1')).rejects.toThrow(
'Cannot unfriend yourself',
);
});
it('should throw NotFoundException when not friends', async () => {
mockPrismaService.friendship.findFirst.mockResolvedValue(null);
await expect(service.unfriend('user-1', 'user-2')).rejects.toThrow(
NotFoundException,
);
await expect(service.unfriend('user-1', 'user-2')).rejects.toThrow(
'You are not friends with this user',
);
});
});
describe('areFriends', () => {
it('should return true when users are friends', async () => {
mockPrismaService.friendship.findFirst.mockResolvedValue(mockFriendship);
const result = await service.areFriends('user-1', 'user-2');
expect(result).toBe(true);
});
it('should return false when users are not friends', async () => {
mockPrismaService.friendship.findFirst.mockResolvedValue(null);
const result = await service.areFriends('user-1', 'user-2');
expect(result).toBe(false);
});
});
});