From 159f32b4766d25ade01172a3634a90746d14e31f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:42:53 +0000 Subject: [PATCH 1/5] Initial plan From b78be42a2af18731a311783aea7fc53babb97a95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:53:28 +0000 Subject: [PATCH 2/5] Fix friend search to support full name search - add CONCAT to backend query Co-authored-by: juva99 <69242165+juva99@users.noreply.github.com> --- backend/src/friends/friends.service.ts | 4 +- .../group-members/group-members.service.ts | 2 +- backend/src/users/users.service.spec.ts | 154 ++++++++++++++++++ backend/src/users/users.service.ts | 6 +- 4 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 backend/src/users/users.service.spec.ts diff --git a/backend/src/friends/friends.service.ts b/backend/src/friends/friends.service.ts index 155d24ba..50d5c89e 100644 --- a/backend/src/friends/friends.service.ts +++ b/backend/src/friends/friends.service.ts @@ -17,7 +17,7 @@ export class FriendsService { ) {} async setStatus(setStatusDto: FriendSetStatusDto): Promise { const { req_uid, status } = setStatusDto; - let reqStatus = await this.friendRepository.findOne({ + const reqStatus = await this.friendRepository.findOne({ where: { id: req_uid }, }); if (!reqStatus) { @@ -55,7 +55,7 @@ export class FriendsService { await this.friendRepository.remove(req); } - async checkUser(user: User, relationId: string): Promise { + async checkUser(user: User, relationId: string): Promise { const relations = await this.friendRepository.find({ where: [ { user1: user, id: relationId }, diff --git a/backend/src/group-members/group-members.service.ts b/backend/src/group-members/group-members.service.ts index ab87c11b..6bc81661 100644 --- a/backend/src/group-members/group-members.service.ts +++ b/backend/src/group-members/group-members.service.ts @@ -196,7 +196,7 @@ export class GroupMembersService { return this.groupMemberRepository.save(groupMember); } - async isManager(groupId: string, userId: string): Promise { + async isManager(groupId: string, userId: string): Promise { const groupMember = await this.findGroupMember(groupId, userId); return groupMember.isManager; diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts new file mode 100644 index 00000000..b0204bad --- /dev/null +++ b/backend/src/users/users.service.spec.ts @@ -0,0 +1,154 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { UsersService } from './users.service'; +import { User } from './users.entity'; +import { Role } from 'src/enums/role.enum'; + +describe('UsersService', () => { + let service: UsersService; + + const mockUser: User = { + uid: '1', + firstName: 'John', + lastName: 'Doe', + userEmail: 'john.doe@example.com', + pass: 'hashedPassword', + birthDay: '1990-01-01', + isMale: true, + address: '123 Main St', + profilePic: null, + phoneNum: '1234567890', + role: Role.USER, + fieldsManage: [], + sentFriendRequests: [], + receivedFriendRequests: [], + gameParticipations: [], + createdGames: [], + passwordResetToken: null, + hashedRefreshToken: null, + passwordResetExpires: undefined, + groupMemberIn: [], + }; + + const currentUser: User = { + uid: '2', + firstName: 'Jane', + lastName: 'Smith', + userEmail: 'jane.smith@example.com', + pass: 'hashedPassword', + birthDay: '1992-01-01', + isMale: false, + address: '456 Oak St', + profilePic: null, + phoneNum: '0987654321', + role: Role.USER, + fieldsManage: [], + sentFriendRequests: [], + receivedFriendRequests: [], + gameParticipations: [], + createdGames: [], + passwordResetToken: null, + hashedRefreshToken: null, + passwordResetExpires: undefined, + groupMemberIn: [], + }; + + const mockQueryBuilder = { + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + setParameter: jest.fn().mockReturnThis(), + subQuery: jest.fn().mockReturnThis(), + select: jest.fn().mockReturnThis(), + from: jest.fn().mockReturnThis(), + getQuery: jest.fn().mockReturnValue('subquery'), + getMany: jest.fn(), + }; + + const mockRepository = { + createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + UsersService, + { + provide: getRepositoryToken(User), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findByName', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should find users by first name', async () => { + mockQueryBuilder.getMany.mockResolvedValue([mockUser]); + + const result = await service.findByName('John', currentUser); + + expect(mockQueryBuilder.where).toHaveBeenCalledWith( + "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + { name: '%John%' }, + ); + expect(result).toEqual([mockUser]); + }); + + it('should find users by last name', async () => { + mockQueryBuilder.getMany.mockResolvedValue([mockUser]); + + const result = await service.findByName('Doe', currentUser); + + expect(mockQueryBuilder.where).toHaveBeenCalledWith( + "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + { name: '%Doe%' }, + ); + expect(result).toEqual([mockUser]); + }); + + it('should find users by full name after the fix', async () => { + mockQueryBuilder.getMany.mockResolvedValue([mockUser]); + + const result = await service.findByName('John Doe', currentUser); + + // Fixed implementation should search firstName OR lastName OR concatenated full name + expect(mockQueryBuilder.where).toHaveBeenCalledWith( + "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + { name: '%John Doe%' }, + ); + + // Should now find the user with firstName="John" and lastName="Doe" + expect(result).toEqual([mockUser]); + }); + + it('should exclude current user from results', async () => { + mockQueryBuilder.getMany.mockResolvedValue([]); + + await service.findByName('John', currentUser); + + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'user.uid != :currentUserId', + { currentUserId: currentUser.uid }, + ); + }); + + it('should exclude existing friends from results', async () => { + mockQueryBuilder.getMany.mockResolvedValue([]); + + await service.findByName('John', currentUser); + + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'user.uid NOT IN (subquery)', + ); + }); + }); +}); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 18907ce1..76d0001d 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -7,7 +7,7 @@ import { import { User } from './users.entity'; import { CreateUserDto } from './dto/create-users.dto'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, ILike, MoreThan, In } from 'typeorm'; +import { Repository, MoreThan, In } from 'typeorm'; import { FriendRelation } from 'src/friends/friends.entity'; import * as crypto from 'crypto'; import * as bcrypt from 'bcrypt'; @@ -83,9 +83,9 @@ export class UsersService { // look up for new friends by first and last name const queryBuilder = this.userRepository.createQueryBuilder('user'); - // Search by first name or last name + // Search by first name, last name, or full name queryBuilder.where( - '(user.firstName ILIKE :name OR user.lastName ILIKE :name)', + "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", { name: `%${name}%` }, ); From 49c5453bfe6e5d9110946bde47153098f8fdaf6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Jul 2025 07:23:53 +0000 Subject: [PATCH 3/5] Simplify friend search query to use only CONCAT as suggested in review Co-authored-by: juva99 <69242165+juva99@users.noreply.github.com> --- backend/src/users/users.service.spec.ts | 8 ++++---- backend/src/users/users.service.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index b0204bad..da989c64 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -97,7 +97,7 @@ describe('UsersService', () => { const result = await service.findByName('John', currentUser); expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", { name: '%John%' }, ); expect(result).toEqual([mockUser]); @@ -109,7 +109,7 @@ describe('UsersService', () => { const result = await service.findByName('Doe', currentUser); expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", { name: '%Doe%' }, ); expect(result).toEqual([mockUser]); @@ -120,9 +120,9 @@ describe('UsersService', () => { const result = await service.findByName('John Doe', currentUser); - // Fixed implementation should search firstName OR lastName OR concatenated full name + // Simplified implementation using only concatenated full name search expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", { name: '%John Doe%' }, ); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 76d0001d..77268f33 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -80,12 +80,12 @@ export class UsersService { } async findByName(name: string, currentUser: User): Promise { - // look up for new friends by first and last name + // look up for new friends by full name const queryBuilder = this.userRepository.createQueryBuilder('user'); - // Search by first name, last name, or full name + // Search by full name (concatenated first and last name) queryBuilder.where( - "(user.firstName ILIKE :name OR user.lastName ILIKE :name OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :name)", + "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", { name: `%${name}%` }, ); From 9081c8b35671470da5a6310ae063797cb5b43a9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:25:37 +0000 Subject: [PATCH 4/5] Simplify test file to resolve merge conflicts Co-authored-by: juva99 <69242165+juva99@users.noreply.github.com> --- backend/src/users/users.service.spec.ts | 31 ++----------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index da989c64..c6e26399 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -56,10 +56,10 @@ describe('UsersService', () => { const mockQueryBuilder = { where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), - setParameter: jest.fn().mockReturnThis(), subQuery: jest.fn().mockReturnThis(), select: jest.fn().mockReturnThis(), from: jest.fn().mockReturnThis(), + setParameter: jest.fn().mockReturnThis(), getQuery: jest.fn().mockReturnValue('subquery'), getMany: jest.fn(), }; @@ -91,42 +91,15 @@ describe('UsersService', () => { jest.clearAllMocks(); }); - it('should find users by first name', async () => { - mockQueryBuilder.getMany.mockResolvedValue([mockUser]); - - const result = await service.findByName('John', currentUser); - - expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", - { name: '%John%' }, - ); - expect(result).toEqual([mockUser]); - }); - - it('should find users by last name', async () => { - mockQueryBuilder.getMany.mockResolvedValue([mockUser]); - - const result = await service.findByName('Doe', currentUser); - - expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", - { name: '%Doe%' }, - ); - expect(result).toEqual([mockUser]); - }); - - it('should find users by full name after the fix', async () => { + it('should find users by full name search', async () => { mockQueryBuilder.getMany.mockResolvedValue([mockUser]); const result = await service.findByName('John Doe', currentUser); - // Simplified implementation using only concatenated full name search expect(mockQueryBuilder.where).toHaveBeenCalledWith( "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", { name: '%John Doe%' }, ); - - // Should now find the user with firstName="John" and lastName="Doe" expect(result).toEqual([mockUser]); }); From d2cf3aa25d7888d6745cc87c80747cda796e5275 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:43:27 +0000 Subject: [PATCH 5/5] Remove unrelated file changes and test file to resolve merge conflicts Co-authored-by: juva99 <69242165+juva99@users.noreply.github.com> --- backend/src/friends/friends.service.ts | 4 +- .../group-members/group-members.service.ts | 2 +- backend/src/users/users.service.spec.ts | 127 ------------------ 3 files changed, 3 insertions(+), 130 deletions(-) delete mode 100644 backend/src/users/users.service.spec.ts diff --git a/backend/src/friends/friends.service.ts b/backend/src/friends/friends.service.ts index 50d5c89e..155d24ba 100644 --- a/backend/src/friends/friends.service.ts +++ b/backend/src/friends/friends.service.ts @@ -17,7 +17,7 @@ export class FriendsService { ) {} async setStatus(setStatusDto: FriendSetStatusDto): Promise { const { req_uid, status } = setStatusDto; - const reqStatus = await this.friendRepository.findOne({ + let reqStatus = await this.friendRepository.findOne({ where: { id: req_uid }, }); if (!reqStatus) { @@ -55,7 +55,7 @@ export class FriendsService { await this.friendRepository.remove(req); } - async checkUser(user: User, relationId: string): Promise { + async checkUser(user: User, relationId: string): Promise { const relations = await this.friendRepository.find({ where: [ { user1: user, id: relationId }, diff --git a/backend/src/group-members/group-members.service.ts b/backend/src/group-members/group-members.service.ts index 6bc81661..ab87c11b 100644 --- a/backend/src/group-members/group-members.service.ts +++ b/backend/src/group-members/group-members.service.ts @@ -196,7 +196,7 @@ export class GroupMembersService { return this.groupMemberRepository.save(groupMember); } - async isManager(groupId: string, userId: string): Promise { + async isManager(groupId: string, userId: string): Promise { const groupMember = await this.findGroupMember(groupId, userId); return groupMember.isManager; diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts deleted file mode 100644 index c6e26399..00000000 --- a/backend/src/users/users.service.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { UsersService } from './users.service'; -import { User } from './users.entity'; -import { Role } from 'src/enums/role.enum'; - -describe('UsersService', () => { - let service: UsersService; - - const mockUser: User = { - uid: '1', - firstName: 'John', - lastName: 'Doe', - userEmail: 'john.doe@example.com', - pass: 'hashedPassword', - birthDay: '1990-01-01', - isMale: true, - address: '123 Main St', - profilePic: null, - phoneNum: '1234567890', - role: Role.USER, - fieldsManage: [], - sentFriendRequests: [], - receivedFriendRequests: [], - gameParticipations: [], - createdGames: [], - passwordResetToken: null, - hashedRefreshToken: null, - passwordResetExpires: undefined, - groupMemberIn: [], - }; - - const currentUser: User = { - uid: '2', - firstName: 'Jane', - lastName: 'Smith', - userEmail: 'jane.smith@example.com', - pass: 'hashedPassword', - birthDay: '1992-01-01', - isMale: false, - address: '456 Oak St', - profilePic: null, - phoneNum: '0987654321', - role: Role.USER, - fieldsManage: [], - sentFriendRequests: [], - receivedFriendRequests: [], - gameParticipations: [], - createdGames: [], - passwordResetToken: null, - hashedRefreshToken: null, - passwordResetExpires: undefined, - groupMemberIn: [], - }; - - const mockQueryBuilder = { - where: jest.fn().mockReturnThis(), - andWhere: jest.fn().mockReturnThis(), - subQuery: jest.fn().mockReturnThis(), - select: jest.fn().mockReturnThis(), - from: jest.fn().mockReturnThis(), - setParameter: jest.fn().mockReturnThis(), - getQuery: jest.fn().mockReturnValue('subquery'), - getMany: jest.fn(), - }; - - const mockRepository = { - createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UsersService, - { - provide: getRepositoryToken(User), - useValue: mockRepository, - }, - ], - }).compile(); - - service = module.get(UsersService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('findByName', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should find users by full name search', async () => { - mockQueryBuilder.getMany.mockResolvedValue([mockUser]); - - const result = await service.findByName('John Doe', currentUser); - - expect(mockQueryBuilder.where).toHaveBeenCalledWith( - "CONCAT(user.firstName, ' ', user.lastName) ILIKE :name", - { name: '%John Doe%' }, - ); - expect(result).toEqual([mockUser]); - }); - - it('should exclude current user from results', async () => { - mockQueryBuilder.getMany.mockResolvedValue([]); - - await service.findByName('John', currentUser); - - expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - 'user.uid != :currentUserId', - { currentUserId: currentUser.uid }, - ); - }); - - it('should exclude existing friends from results', async () => { - mockQueryBuilder.getMany.mockResolvedValue([]); - - await service.findByName('John', currentUser); - - expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - 'user.uid NOT IN (subquery)', - ); - }); - }); -});