diff --git a/backend/package.json b/backend/package.json index faa8f6d..10e4fd5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,12 +9,15 @@ "test:list": "jest --listTests", "test:specific": "jest path/to/your/tests", "test:clearCache": "jest --clearCache", + "test:coverage": "jest --coverage", "dev": "concurrently \"tsc --watch\" \"nodemon -q dist/src/server.js\"", "build": "rimraf dist && npx tsoa spec-and-routes && npm run tsc", "start": "node dist/src/server.js", "tsoa:spec": "npx tsoa spec", "tsc": "tsc --noEmit --emitDeclarationOnly false --experimentalDecorators", - "tsoa:routes": "npx tsoa routes" + "tsoa:routes": "npx tsoa routes", + "stryker-run": "npx stryker run", + "test:coverage-stryker": "npm run test:coverage && npm run stryker-run" }, "dependencies": { "axios": "^1.7.7", @@ -40,6 +43,7 @@ "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.25.9", + "@stryker-mutator/jest-runner": "^8.7.1", "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", diff --git a/backend/src/Models/Posts/__tests__/NofindUsersByPosts.test.ts b/backend/src/Models/Posts/__tests__/NofindUsersByPosts.test.ts new file mode 100644 index 0000000..87a810e --- /dev/null +++ b/backend/src/Models/Posts/__tests__/NofindUsersByPosts.test.ts @@ -0,0 +1,101 @@ +// import { findUsersByPosts } from '../post.model'; +// import { UserPost } from '../../../../Types/posts'; + +// // Define individual mock functions for the chained methods +// const mockSelect = jest.fn().mockReturnThis(); +// const mockOrderBy = jest.fn().mockReturnThis(); +// const mockLimit = jest.fn().mockReturnThis(); +// const mockOffset = jest.fn().mockResolvedValue([]); + +// // Mock the default export of knex/db +// jest.mock('../../../db/db', () => { +// return { +// __esModule: true, +// default: jest.fn(() => ({ +// select: mockSelect, +// orderBy: mockOrderBy, +// limit: mockLimit, +// offset: mockOffset, +// })), +// }; +// }); + +// // Clear all mocks after each test to ensure a clean slate for each test case +// afterEach(() => { +// jest.clearAllMocks(); +// }); + +// describe('findUsersByPosts', () => { +// // Test Case #1: Successfully return posts when the database query is successful +// test('should return posts when the database query is successful', async () => { +// // Explicitly type mockPosts as UserPost[] +// const mockPosts: UserPost[] = [ +// { id: 1, user_id: 123, username: 'user1', post_content: 'Post content 1', created_at: new Date(), comments: ["comment1", "comment2"], reactions: 10, locationName: 'Park', latitude: 10, longitude: 20 }, +// { id: 2, user_id: 456, username: 'user2', post_content: 'Post content 2', created_at: new Date(), comments: ["comment3", "comment4"], reactions: 8, locationName: 'Mall', latitude: 12, longitude: 22 } +// ]; + +// // Mock the 'offset' method to return the mock posts +// mockOffset.mockResolvedValueOnce(mockPosts); + +// const limit = 5; +// const offset = 0; + +// // Call the function under test +// const result = await findUsersByPosts(limit, offset); + +// // Verify that the result matches the mock data +// expect(result).toEqual(mockPosts); +// expect(mockSelect).toHaveBeenCalledWith('id', 'user_id', 'username', 'post_content', 'created_at', 'comments', 'reactions', 'locationName', 'latitude', 'longitude'); +// expect(mockOrderBy).toHaveBeenCalledWith('created_at', 'desc'); +// expect(mockLimit).toHaveBeenCalledWith(limit); +// expect(mockOffset).toHaveBeenCalledWith(offset); +// }); + +// // Test Case #2: Database query fails +// test('should throw an error if the database query fails', async () => { +// // Mock the 'offset' method to reject with an error +// mockOffset.mockRejectedValueOnce(new Error('Database error')); + +// const limit = 5; +// const offset = 0; + +// // Verify that the error is thrown +// await expect(findUsersByPosts(limit, offset)).rejects.toThrow('Failed to fetch posts with comments'); +// }); + +// // Test Case #3: No posts found, return an empty array +// test('should return an empty array if no posts are found', async () => { +// const mockPosts: UserPost[] = []; // Empty array for no posts scenario + +// // Mock the 'offset' method to return an empty array +// mockOffset.mockResolvedValueOnce(mockPosts); + +// const limit = 5; +// const offset = 0; + +// // Call the function under test +// const result = await findUsersByPosts(limit, offset); + +// // Verify that the result is an empty array +// expect(result).toEqual(mockPosts); +// }); + +// // Test Case #4: Handle invalid limit and offset values gracefully +// test('should handle invalid limit and offset values gracefully', async () => { +// const mockPosts: UserPost[] = [ +// { id: 1, user_id: 123, username: 'user1', post_content: 'Post content 1', created_at: new Date(), comments: ["comment1"], reactions: 10, locationName: 'Park', latitude: 10, longitude: 20 } +// ]; + +// // Mock the 'offset' method to return the mock posts +// mockOffset.mockResolvedValueOnce(mockPosts); + +// const limit = -5; // Invalid limit +// const offset = -1; // Invalid offset + +// // Call the function under test and expect it to handle the invalid values +// const result = await findUsersByPosts(limit, offset); + +// // Verify that the result is the mock posts despite invalid inputs +// expect(result).toEqual(mockPosts); +// }); +// }); diff --git a/backend/src/Models/Posts/__tests__/createUserPost.test.ts b/backend/src/Models/Posts/__tests__/createUserPost.test.ts index 5d79880..823245b 100644 --- a/backend/src/Models/Posts/__tests__/createUserPost.test.ts +++ b/backend/src/Models/Posts/__tests__/createUserPost.test.ts @@ -2,7 +2,7 @@ import { createUserPost } from '../post.model'; import { UserPost } from '../../../../Types/posts'; import { User } from '../../../../Types/users'; -// Mocking the knex object with method chaining +// Mock the db module with correct chaining behavior const mockInsert = jest.fn().mockReturnThis(); const mockReturning = jest.fn().mockResolvedValue([]); @@ -18,7 +18,7 @@ jest.mock('../../../db/db', () => { }; }); -// Clear all mocks after each test to ensure clean slate for each test case +// Clear all mocks after each test to ensure a clean slate for each test case afterEach(() => { jest.clearAllMocks(); }); @@ -47,7 +47,7 @@ describe('createUserPost', () => { created_at: new Date(), comment_ids: [], reactions: 0, - locationName: "", + locationName: '', latitude: 0, longitude: 0, }; diff --git a/backend/src/Models/Posts/__tests__/findComments.test.ts b/backend/src/Models/Posts/__tests__/findComments.test.ts new file mode 100644 index 0000000..165c4a7 --- /dev/null +++ b/backend/src/Models/Posts/__tests__/findComments.test.ts @@ -0,0 +1,92 @@ +import { findComments } from '../post.model'; +import { UserPost } from '../../../../Types/posts'; + +// Mock the db module with correct chaining behavior +const mockSelect = jest.fn().mockReturnThis(); +const mockWhereIn = jest.fn().mockReturnThis(); +const mockOrderBy = jest.fn().mockReturnThis(); +const mockLimit = jest.fn().mockReturnThis(); +const mockOffset = jest.fn().mockReturnThis(); + +// Mocking the database module to return the mocked db methods +jest.mock('../../../db/db', () => ({ + __esModule: true, + default: jest.fn(() => ({ + select: mockSelect, + whereIn: mockWhereIn, + orderBy: mockOrderBy, + limit: mockLimit, + offset: mockOffset, + })), +})); + +// Clear all mocks after each test to ensure a clean slate for each test case +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('findComments', () => { + // Test Case #1: Successfully fetch comments + test('should fetch comments successfully', async () => { + const mockComments: UserPost[] = [ + { + id: 1, + user_id: 123, + username: 'user1', + post_content: 'First Comment', + created_at: new Date(), + comment_ids: [], + reactions: 0, + locationName: '', + latitude: 0, + longitude: 0, + }, + ]; + + // Mock the offset method to resolve with mockComments + mockOffset.mockResolvedValueOnce(mockComments); + + const limit = 5; + const offset = 0; + const commentIDs = [1]; + + // Call the function under test + const result = await findComments(limit, offset, commentIDs); + + // Verify the result matches the mock data + expect(result).toEqual(mockComments); + + // Verify interactions with the mock db methods + expect(mockSelect).toHaveBeenCalledWith('id', 'user_id', 'username', 'comment_content', 'created_at', 'reactions'); + expect(mockWhereIn).toHaveBeenCalledWith('id', commentIDs); + expect(mockOrderBy).toHaveBeenCalledWith('created_at', 'desc'); + expect(mockLimit).toHaveBeenCalledWith(limit); + expect(mockOffset).toHaveBeenCalledWith(offset); + }); + + // Test Case #2: Return empty array if commentIDs is empty + test('should return an empty array if commentIDs is empty', async () => { + const limit = 5; + const offset = 0; + const commentIDs: number[] = []; + + // Call the function under test + const result = await findComments(limit, offset, commentIDs); + + // Verify that the result is an empty array + expect(result).toEqual([]); + }); + + // Test Case #3: Handle database error + test('should throw an error if the database query fails', async () => { + // Mock the offset method to reject with an error + mockOffset.mockRejectedValueOnce(new Error('Database error')); + + const limit = 5; + const offset = 0; + const commentIDs = [1]; + + // Verify that the function throws the expected error + await expect(findComments(limit, offset, commentIDs)).rejects.toThrow('Failed to fetch comments'); + }); +}); diff --git a/backend/src/Models/Posts/__tests__/findPosts.test.ts b/backend/src/Models/Posts/__tests__/findPosts.test.ts new file mode 100644 index 0000000..7c626ff --- /dev/null +++ b/backend/src/Models/Posts/__tests__/findPosts.test.ts @@ -0,0 +1,73 @@ +import { findPosts } from '../post.model'; + +// Mock the db module with correct chaining behavior +const mockSelect = jest.fn().mockReturnThis(); +const mockOrderBy = jest.fn().mockReturnThis(); +const mockLimit = jest.fn().mockReturnThis(); +const mockOffset = jest.fn().mockReturnThis(); + +// Mocking the database module to return the mocked db methods +jest.mock('../../../db/db', () => ({ + __esModule: true, + default: jest.fn(() => ({ + select: mockSelect, + orderBy: mockOrderBy, + limit: mockLimit, + offset: mockOffset, + })), +})); + +// Clear all mocks after each test to ensure a clean slate for each test case +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('findPosts', () => { + // Test Case #1: Successfully fetch posts + test('should fetch posts successfully', async () => { + const mockPosts = [ + { + id: 1, + user_id: 123, + username: 'user1', + post_content: 'First Post', + created_at: new Date(), + comment_ids: [], + reactions: 0, + locationName: '', + latitude: 0, + longitude: 0, + }, + ]; + + // Mock the offset method to resolve with mockPosts + mockOffset.mockResolvedValueOnce(mockPosts); + + const limit = 5; + const offset = 0; + + // Call the function under test + const result = await findPosts(limit, offset); + + // Verify the result matches the mock data + expect(result).toEqual(mockPosts); + + // Verify interactions with the mock db methods + expect(mockSelect).toHaveBeenCalledWith('id', 'user_id', 'username', 'post_content', 'created_at', 'comment_ids', 'reactions', 'locationName', 'latitude', 'longitude'); + expect(mockOrderBy).toHaveBeenCalledWith('created_at', 'desc'); + expect(mockLimit).toHaveBeenCalledWith(limit); + expect(mockOffset).toHaveBeenCalledWith(offset); + }); + + // Test Case #2: Handle database error + test('should throw an error if the database query fails', async () => { + // Mock the offset method to reject with an error + mockOffset.mockRejectedValueOnce(new Error('Database error')); + + const limit = 5; + const offset = 0; + + // Verify that the function throws the expected error + await expect(findPosts(limit, offset)).rejects.toThrow('Failed to fetch posts with comments'); + }); +}); diff --git a/backend/src/Models/Users/__tests__/addUserFriend.test.ts b/backend/src/Models/Users/__tests__/addUserFriend.test.ts index 4b808ce..3cf70bc 100644 --- a/backend/src/Models/Users/__tests__/addUserFriend.test.ts +++ b/backend/src/Models/Users/__tests__/addUserFriend.test.ts @@ -1,5 +1,5 @@ -import { addUserFriend, findUserByEmail, getUserFriends } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Correct import path for User type +import { addUserFriend, findUserByEmail, getUserFriends } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database methods const mockWhere = jest.fn().mockReturnThis(); @@ -29,6 +29,7 @@ afterEach(() => { }); describe('addUserFriend', () => { + // Test Case #1: Successfully add a new user friend test('should add a new user friend successfully', async () => { const mockCurrentUser: User = { @@ -60,46 +61,35 @@ describe('addUserFriend', () => { friends: [2, 3, 4], }; - // Mock the findUserByEmail function to return the mockCurrentUser and mockNewUserFriend (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockCurrentUser]); (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockNewUserFriend]); - - // Mock the getUserFriends function to return the mockCurrentUser's friends list (getUserFriends as jest.Mock).mockResolvedValueOnce(mockCurrentUser.friends); - - // Mock the returning method to return the updatedUser - (mockReturning as jest.Mock).mockResolvedValueOnce([updatedUser]); + mockReturning.mockResolvedValueOnce([updatedUser]); - // Call the function to be tested const result = await addUserFriend('current.user@example.com', 'new.user@example.com'); - // Verify that the result matches the mock data expect(result).toEqual(updatedUser); }); - // Test Case #2: Empty username passed + // Test Case #2: Throw an error if currentUser or newUserFriend is empty test('should throw an error if currentUser or newUserFriend is empty', async () => { - // Verify that the function throws the expected error when currentUser or newUserFriend is empty await expect(addUserFriend('', 'new.user@example.com')).rejects.toThrow('Cannot add new friend, empty username passed'); await expect(addUserFriend('current.user@example.com', '')).rejects.toThrow('Cannot add new friend, empty username passed'); }); - // Test Case #3: Current user and new user friend are the same + // Test Case #3: Throw an error if currentUser and newUserFriend are the same test('should throw an error if currentUser and newUserFriend are the same', async () => { - // Verify that the function throws the expected error when currentUser and newUserFriend are the same await expect(addUserFriend('current.user@example.com', 'current.user@example.com')).rejects.toThrow('Current user and new user friend are the same'); }); - // Test Case #4: User not found + // Test Case #4: Throw an error if the user is not found test('should throw an error if the user is not found', async () => { - // Mock the findUserByEmail function to return null to simulate user not found - (findUserByEmail as jest.Mock).mockResolvedValueOnce(null); + (findUserByEmail as jest.Mock).mockResolvedValueOnce([]); - // Verify that the function throws the expected error await expect(addUserFriend('current.user@example.com', 'nonexistent@example.com')).rejects.toThrow('Unable to find user current.user@example.com'); }); - // Test Case #5: Users are already friends + // Test Case #5: Throw an error if users are already friends test('should throw an error if users are already friends', async () => { const mockCurrentUser: User = { id: 1, @@ -125,18 +115,14 @@ describe('addUserFriend', () => { friends: [], }; - // Mock the findUserByEmail function to return the mockCurrentUser and mockNewUserFriend (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockCurrentUser]); (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockNewUserFriend]); - - // Mock the getUserFriends function to return the mockCurrentUser's friends list (getUserFriends as jest.Mock).mockResolvedValueOnce(mockCurrentUser.friends); - // Verify that the function throws the expected error when users are already friends await expect(addUserFriend('current.user@example.com', 'new.user@example.com')).rejects.toThrow('new.user@example.com already friends with current.user@example.com, cannot add duplicate'); }); - // Test Case #6: Database failure + // Test Case #6: Throw an error on database failure test('should throw an error on database failure', async () => { const mockCurrentUser: User = { id: 1, @@ -162,17 +148,11 @@ describe('addUserFriend', () => { friends: [], }; - // Mock the findUserByEmail function to return the mockCurrentUser and mockNewUserFriend (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockCurrentUser]); (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockNewUserFriend]); - - // Mock the getUserFriends function to return the mockCurrentUser's friends list (getUserFriends as jest.Mock).mockResolvedValueOnce(mockCurrentUser.friends); - - // Mock the returning method to throw an error - (mockReturning as jest.Mock).mockRejectedValueOnce(new Error('Database error')); + mockReturning.mockRejectedValueOnce(new Error('Database error')); - // Verify that the function throws the expected error await expect(addUserFriend('current.user@example.com', 'new.user@example.com')).rejects.toThrow(`Unable to add user new.user@example.com to current.user@example.com's friends list`); }); }); diff --git a/backend/src/Models/Users/__tests__/createNewUserProfile.test.ts b/backend/src/Models/Users/__tests__/createNewUserProfile.test.ts index ba4f996..32d540f 100644 --- a/backend/src/Models/Users/__tests__/createNewUserProfile.test.ts +++ b/backend/src/Models/Users/__tests__/createNewUserProfile.test.ts @@ -1,5 +1,5 @@ -import { createNewUserProfile } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Importing User type +import { createNewUserProfile } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database insert and returning methods const mockInsert = jest.fn().mockReturnThis(); diff --git a/backend/src/Models/Users/__tests__/findFriendRecommendations.test.ts b/backend/src/Models/Users/__tests__/findFriendRecommendations.test.ts index ba0152b..2335185 100644 --- a/backend/src/Models/Users/__tests__/findFriendRecommendations.test.ts +++ b/backend/src/Models/Users/__tests__/findFriendRecommendations.test.ts @@ -1,5 +1,5 @@ -import { findFriendRecommendations, findUserByEmail } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Correct import path for User type +import { findFriendRecommendations, findUserByEmail } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database methods const mockWhereNot = jest.fn().mockReturnThis(); diff --git a/backend/src/Models/Users/__tests__/findUserByEmail.test.ts b/backend/src/Models/Users/__tests__/findUserByEmail.test.ts index ad92259..408afbc 100644 --- a/backend/src/Models/Users/__tests__/findUserByEmail.test.ts +++ b/backend/src/Models/Users/__tests__/findUserByEmail.test.ts @@ -1,5 +1,5 @@ -import { findUserByEmail } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Importing User type +import { findUserByEmail } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database select and where methods const mockSelect = jest.fn().mockReturnThis(); diff --git a/backend/src/Models/Users/__tests__/findUserByID.test.ts b/backend/src/Models/Users/__tests__/findUserByID.test.ts new file mode 100644 index 0000000..82df1e9 --- /dev/null +++ b/backend/src/Models/Users/__tests__/findUserByID.test.ts @@ -0,0 +1,61 @@ +import { findUserByID } from '../user.model'; +import { User } from '../../../../Types/users'; + +// Mock the db methods +const mockSelect = jest.fn().mockReturnThis(); +const mockWhere = jest.fn().mockReturnThis(); +const mockFirst = jest.fn(); + +// Mocking the database module to return the mocked db methods +jest.mock('../../../db/db', () => ({ + __esModule: true, + default: jest.fn(() => ({ + select: mockSelect, + where: mockWhere, + first: mockFirst, + })), +})); + +// Clear all mocks after each test to ensure a clean slate for each test case +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('findUserByID', () => { + // Test Case #1: Successfully find user by ID + test('should find a user by ID', async () => { + const mockUser: User = { + id: 1, + username: 'john_doe', + email: 'john.doe@example.com', + userPostIds: [], + locationName: 'New York', + longitude: 0, + latitude: 0, + created_at: new Date(), + friends: [], + }; + + // Mock the first method to resolve with the mockUser + mockFirst.mockResolvedValueOnce(mockUser); + + // Call the function under test + const result = await findUserByID(1); + + // Verify that the result matches the mock data + expect(result).toEqual(mockUser); + // Verify interactions with the mock db methods + expect(mockSelect).toHaveBeenCalledWith('*'); + expect(mockWhere).toHaveBeenCalledWith('id', 1); + expect(mockFirst).toHaveBeenCalled(); + }); + + // Test Case #2: Handle database error + test('should throw an error if the database query fails', async () => { + // Mock the first method to reject with an error + mockFirst.mockRejectedValueOnce(new Error('Database error')); + + // Verify that the function throws the expected error + await expect(findUserByID(1)).rejects.toThrow('Failed to fetch user by id'); + }); +}); diff --git a/backend/src/Models/Users/__tests__/getUserFriends.test.ts b/backend/src/Models/Users/__tests__/getUserFriends.test.ts index 8026cc3..44fe216 100644 --- a/backend/src/Models/Users/__tests__/getUserFriends.test.ts +++ b/backend/src/Models/Users/__tests__/getUserFriends.test.ts @@ -1,5 +1,5 @@ -import { getUserFriends, findUserByEmail } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Correct import path for User type +import { getUserFriends, findUserByEmail } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database methods const mockWhere = jest.fn().mockReturnThis(); @@ -28,6 +28,15 @@ afterEach(() => { }); describe('getUserFriends', () => { + beforeEach(() => { + console.log = jest.fn(); // Mock console.log + console.error = jest.fn(); // Mock console.error + }); + + afterEach(() => { + jest.clearAllMocks(); // Reset mocks after each test + }); + // Test Case #1: Successfully retrieve user friends test('should return the list of user friends', async () => { const mockUser: User = { @@ -45,31 +54,40 @@ describe('getUserFriends', () => { const mockFriendList = { friends: [2, 3, 4] }; // Mock the findUserByEmail function to return the mockUser - (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockUser]); - + (findUserByEmail as jest.Mock).mockResolvedValue([mockUser]); + // Mock the first method to return the mockFriendList - (mockFirst as jest.Mock).mockResolvedValueOnce(mockFriendList); + mockFirst.mockResolvedValue(mockFriendList); // Call the function to be tested const result = await getUserFriends('john.doe@example.com'); // Verify that the result matches the mock data expect(result).toEqual([2, 3, 4]); + + // Check the logs + expect(console.log).toHaveBeenCalledWith(`Getting john.doe@example.com's friend IDs`); }); // Test Case #2: User not found test('should throw an error if user is not found', async () => { // Mock the findUserByEmail function to return an empty array - (findUserByEmail as jest.Mock).mockResolvedValueOnce([]); + (findUserByEmail as jest.Mock).mockResolvedValue([]); // Verify that the function throws the expected error await expect(getUserFriends('nonexistent@example.com')).rejects.toThrow('User not found'); + + // Check the logs + expect(console.error).toHaveBeenCalledWith(`Error retrieving user friends Error: User not found`); }); // Test Case #3: Empty userEmail test('should throw an error if userEmail is empty', async () => { // Verify that the function throws the expected error when userEmail is empty await expect(getUserFriends('')).rejects.toThrow('Unable to get user friends, empty userEmail'); + + // Check the logs + expect(console.error).toHaveBeenCalledWith(`Error retrieving user friends Error: Unable to get user friends, empty userEmail`); }); // Test Case #4: Database failure @@ -87,12 +105,15 @@ describe('getUserFriends', () => { }; // Mock the findUserByEmail function to return the mockUser - (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockUser]); - + (findUserByEmail as jest.Mock).mockResolvedValue([mockUser]); + // Mock the first method to throw an error - (mockFirst as jest.Mock).mockRejectedValueOnce(new Error('Database error')); + mockFirst.mockRejectedValue(new Error('Database error')); // Verify that the function throws the expected error await expect(getUserFriends('john.doe@example.com')).rejects.toThrow('Failed to retrieve user friends'); + + // Check the logs + expect(console.error).toHaveBeenCalledWith(`Database error retrieving user friends: Error: Database error`); }); }); diff --git a/backend/src/Models/Users/__tests__/removeUserFriend.test.ts b/backend/src/Models/Users/__tests__/removeUserFriend.test.ts index 8ecccda..f43383b 100644 --- a/backend/src/Models/Users/__tests__/removeUserFriend.test.ts +++ b/backend/src/Models/Users/__tests__/removeUserFriend.test.ts @@ -1,5 +1,5 @@ -import { removeUserFriend, findUserByEmail, getUserFriends } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Correct import path for User type +import { removeUserFriend, findUserByEmail, getUserFriends } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database methods const mockWhere = jest.fn().mockReturnThis(); diff --git a/backend/src/Models/Users/__tests__/updateUserLocation.test.ts b/backend/src/Models/Users/__tests__/updateUserLocation.test.ts index 513ada4..5cd8b88 100644 --- a/backend/src/Models/Users/__tests__/updateUserLocation.test.ts +++ b/backend/src/Models/Users/__tests__/updateUserLocation.test.ts @@ -1,22 +1,18 @@ -import { updateUserLocation, findUserByEmail } from '../user.model'; // Adjust the path if needed -import { User } from '../../../../Types/users'; // Correct import path for User type +import { updateUserLocation } from '../user.model'; +import { User } from '../../../../Types/users'; // Mock the database methods const mockWhere = jest.fn().mockReturnThis(); +const mockFirst = jest.fn(); const mockUpdate = jest.fn().mockReturnThis(); const mockReturning = jest.fn().mockReturnThis(); -// Mock the findUserByEmail function -jest.mock('../user.model', () => ({ - ...jest.requireActual('../user.model'), - findUserByEmail: jest.fn(), -})); - // Mock the db instance jest.mock('../../../db/db', () => ({ __esModule: true, default: jest.fn(() => ({ where: mockWhere, + first: mockFirst, update: mockUpdate, returning: mockReturning, })), @@ -28,6 +24,16 @@ afterEach(() => { }); describe('updateUserLocation', () => { + let consoleErrorSpy: jest.SpyInstance; + + beforeAll(() => { + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterAll(() => { + consoleErrorSpy.mockRestore(); + }); + // Test Case #1: Successfully update user location test('should update user location successfully', async () => { const mockUser: User = { @@ -49,26 +55,39 @@ describe('updateUserLocation', () => { locationName: 'New York', }; - // Mock the findUserByEmail function to return the mockUser - (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockUser]); - + // Mock the first method to return the mockUser + mockFirst.mockResolvedValueOnce(mockUser); + // Mock the returning method to return the updatedUser - (mockReturning as jest.Mock).mockResolvedValueOnce([updatedUser]); + mockReturning.mockResolvedValueOnce([updatedUser]); // Call the function to be tested - const result = await updateUserLocation(1, 40.7128, -74.0060, 'New York'); + const result = await updateUserLocation(mockUser.id, 40.7128, -74.0060, 'New York'); // Verify that the result matches the mock data expect(result).toEqual(updatedUser); + + // Verify interactions with the mock db methods + expect(mockWhere).toHaveBeenCalledWith('id', mockUser.id); + expect(mockFirst).toHaveBeenCalled(); + expect(mockUpdate).toHaveBeenCalledWith({ + latitude: 40.7128, + longitude: -74.0060, + locationName: 'New York' + }); + expect(mockReturning).toHaveBeenCalledWith(['id', 'username', 'userPostIds', 'created_at', 'latitude', 'longitude', 'email', 'locationName', 'friends']); }); // Test Case #2: User not found test('should throw an error if user is not found', async () => { - // Mock the findUserByEmail function to return an empty array - (findUserByEmail as jest.Mock).mockResolvedValueOnce([]); + // Mock the first method to return null + mockFirst.mockResolvedValueOnce(null); // Verify that the function throws the expected error - await expect(updateUserLocation(999, 40.7128, -74.0060, 'New York')).rejects.toThrow('User nonexistent@example.com not found'); + await expect(updateUserLocation(999, 40.7128, -74.0060, 'New York')).rejects.toThrow('User with ID 999 does not exist.'); + + // Check that console.error was called + expect(consoleErrorSpy).toHaveBeenCalledWith('Error updating user location', new Error('User with ID 999 does not exist.')); }); // Test Case #3: Database failure during update @@ -85,13 +104,16 @@ describe('updateUserLocation', () => { friends: [], }; - // Mock the findUserByEmail function to return the mockUser - (findUserByEmail as jest.Mock).mockResolvedValueOnce([mockUser]); - + // Mock the first method to return the mockUser + mockFirst.mockResolvedValueOnce(mockUser); + // Mock the returning method to throw an error - (mockReturning as jest.Mock).mockRejectedValueOnce(new Error('Database error')); + mockReturning.mockRejectedValueOnce(new Error('Database error')); // Verify that the function throws the expected error await expect(updateUserLocation(1, 40.7128, -74.0060, 'New York')).rejects.toThrow('Failed to update user location'); + + // Check that console.error was called + expect(consoleErrorSpy).toHaveBeenCalledWith('Error updating user location', new Error('Database error')); }); }); diff --git a/package.json b/package.json index a299155..2c740f5 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,10 @@ "dependencies": { "knex": "^3.1.0", "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@stryker-mutator/core": "^8.7.1", + "@stryker-mutator/jest-runner": "^8.7.1", + "jest": "^29.7.0" } -} \ No newline at end of file +}