diff --git a/frontend/occupi-mobile4/jest b/frontend/occupi-mobile4/jest new file mode 100644 index 00000000..e69de29b diff --git a/frontend/occupi-mobile4/jest.setup.js b/frontend/occupi-mobile4/jest.setup.js new file mode 100644 index 00000000..e69de29b diff --git a/frontend/occupi-mobile4/occupi@1.0.0 b/frontend/occupi-mobile4/occupi@1.0.0 new file mode 100644 index 00000000..e69de29b diff --git a/frontend/occupi-mobile4/package.json b/frontend/occupi-mobile4/package.json index 61b0d0e9..29615345 100644 --- a/frontend/occupi-mobile4/package.json +++ b/frontend/occupi-mobile4/package.json @@ -15,16 +15,16 @@ "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ - "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|moti|@gluestack-ui/themed|gifted-charts-core)" + "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@gluestack-ui/themed|expo-device)" ], "collectCoverage": true, - "coverageReporters": [ - "lcov", - "text" - ], - "coverageDirectory": "coverage" -} -, + "coverageReporters": ["lcov", "text"], + "coverageDirectory": "coverage", + "setupFiles": [ + "./node_modules/react-native-gesture-handler/jestSetup.js", + "./jest.setup.js" + ] +}, "dependencies": { "@eva-design/eva": "^2.2.0", "@expo/vector-icons": "^14.0.0", diff --git a/frontend/occupi-mobile4/screens/Settings/Appearance.tsx b/frontend/occupi-mobile4/screens/Settings/Appearance.tsx index 9bced853..62437efe 100644 --- a/frontend/occupi-mobile4/screens/Settings/Appearance.tsx +++ b/frontend/occupi-mobile4/screens/Settings/Appearance.tsx @@ -83,6 +83,7 @@ const Appearance = () => { { Mode - setTheme("light")} style={{ width: wp('25%') }}> + setTheme("light")} style={{ width: wp('25%') }}> { Accent colour - setAccentColour("lightgrey")}> + setAccentColour("lightgrey")}> - setAccentColour("#FF4343")}> + setAccentColour("#FF4343")}> - setAccentColour("#FFB443")}> + setAccentColour("#FFB443")}> - setAccentColour("greenyellow")}> + setAccentColour("greenyellow")}> - setAccentColour("#43FF61")}> + setAccentColour("#43FF61")}> - setAccentColour("#43F4FF")}> + setAccentColour("#43F4FF")}> - setAccentColour("#4383FF")}> + setAccentColour("#4383FF")}> - setAccentColour("#AC43FF")}> + setAccentColour("#AC43FF")}> - setAccentColour("#FF43F7")}> + setAccentColour("#FF43F7")}> - setAccentColour("purple")}> - setAccentColour("#FF4343")}> + setAccentColour("purple")}> + setAccentColour("#FF4343")}> diff --git a/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx b/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx index f669605e..d54613d3 100644 --- a/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx +++ b/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx @@ -171,14 +171,14 @@ const FAQPage = () => { {section.section} {section.questions.map((item, index) => ( - - + + - {item.question} + {item.question} - - {item.answer} + + {item.answer} ))} diff --git a/frontend/occupi-mobile4/services/__tests__/aimodel-test.tsx b/frontend/occupi-mobile4/services/__tests__/aimodel-test.tsx new file mode 100644 index 00000000..7f867996 --- /dev/null +++ b/frontend/occupi-mobile4/services/__tests__/aimodel-test.tsx @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getPredictions, getDayPredictions, Prediction } from '../aimodel'; + +jest.mock('axios'); + +describe('aimodel', () => { + let consoleErrorMock: jest.SpyInstance; + + beforeEach(() => { + consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorMock.mockRestore(); + }); + + describe('getPredictions', () => { + it('should return an array of Prediction objects', async () => { + const mockPredictions: Prediction[] = [ + { value: 123, timestamp: new Date() }, + { value: 456, timestamp: new Date() }, + ]; + + (axios.get as jest.Mock).mockResolvedValue({ data: mockPredictions }); + + const predictions = await getPredictions(); + expect(predictions).toEqual(mockPredictions); + }); + + it('should handle errors and return undefined', async () => { + (axios.get as jest.Mock).mockRejectedValue(new Error('Network error')); + + const predictions = await getPredictions(); + expect(predictions).toBeUndefined(); + }); + }); + + describe('getDayPredictions', () => { + it('should return a Prediction object', async () => { + const mockPrediction: Prediction = { value: 789, timestamp: new Date() }; + + (axios.get as jest.Mock).mockResolvedValue({ data: mockPrediction }); + + const prediction = await getDayPredictions(); + expect(prediction).toEqual(mockPrediction); + }); + + it('should handle errors and return undefined', async () => { + (axios.get as jest.Mock).mockRejectedValue(new Error('Network error')); + + const prediction = await getDayPredictions(); + expect(prediction).toBeUndefined(); + }); + + }); +}); \ No newline at end of file diff --git a/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx b/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx index d760d73a..6ef4b89c 100644 --- a/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx +++ b/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx @@ -1,24 +1,19 @@ import axios from 'axios'; -import * as SecureStore from "expo-secure-store"; -import { - getUserDetails, - getNotificationSettings, - getUserBookings, - getSecuritySettings, - updateSecuritySettings, - updateNotificationSettings, -} from "../apiservices"; -import { NotificationSettingsReq } from '@/models/requests'; +import * as SecureStore from 'expo-secure-store'; +import * as apiServices from '../apiservices'; +import { storeUserData } from "../securestore"; jest.mock('axios'); -jest.mock("expo-secure-store"); +jest.mock('expo-secure-store'); +jest.mock('../securestore'); const mockedAxios = axios as jest.Mocked; +const mockedSecureStore = SecureStore as jest.Mocked; -describe("User API Functions", () => { - const mockEmail = "test@example.com"; - const mockAuthToken = "mockAuthToken"; - const mockSuccessResponse = { success: true, data: {} }; +describe('API Services', () => { + const mockEmail = 'test@example.com'; + const mockAuthToken = 'mockAuthToken'; + const mockSuccessResponse = { data: {}, status: 'success', message: 'Operation successful' }; const mockErrorResponse = { data: null, status: 'error', @@ -32,44 +27,49 @@ describe("User API Functions", () => { beforeEach(() => { jest.resetAllMocks(); - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken); - mockedAxios.isAxiosError.mockImplementation((payload: any) => true); + mockedSecureStore.getItemAsync.mockResolvedValue(mockAuthToken); }); - describe("getUserDetails", () => { - it("should return success response when API call is successful", async () => { + describe('getUserDetails', () => { + it('should return success response when API call is successful', async () => { mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); - const result = await getUserDetails(mockEmail, mockAuthToken); + const result = await apiServices.getUserDetails(mockEmail, mockAuthToken); expect(mockedAxios.get).toHaveBeenCalledWith( "https://dev.occupi.tech/api/user-details", expect.objectContaining({ params: { email: mockEmail }, - headers: expect.objectContaining({ Authorization: mockAuthToken }), + headers: { Authorization: mockAuthToken }, }) ); expect(result).toEqual(mockSuccessResponse); }); - it("should return error response when API call fails", async () => { - mockedAxios.get.mockRejectedValue({ - response: { data: mockErrorResponse }, - }); + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); - const result = await getUserDetails(mockEmail, mockAuthToken); + const result = await apiServices.getUserDetails(mockEmail, mockAuthToken); + + expect(result).toEqual(mockErrorResponse); + }); + + it('should handle non-Axios errors', async () => { + mockedAxios.get.mockRejectedValue(new Error('Network error')); + + const result = await apiServices.getUserDetails(mockEmail, mockAuthToken); expect(result).toEqual(mockErrorResponse); }); }); - describe("getNotificationSettings", () => { - it("should return success response when API call is successful", async () => { - (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse }); + describe('getNotificationSettings', () => { + it('should return success response when API call is successful', async () => { + mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); - const result = await getNotificationSettings(mockEmail); + const result = await apiServices.getNotificationSettings(mockEmail); - expect(axios.get).toHaveBeenCalledWith( + expect(mockedAxios.get).toHaveBeenCalledWith( "https://dev.occupi.tech/api/get-notification-settings", expect.objectContaining({ params: { email: mockEmail }, @@ -79,134 +79,223 @@ describe("User API Functions", () => { expect(result).toEqual(mockSuccessResponse); }); - it("should return error response when API call fails", async () => { - (axios.get as jest.Mock).mockRejectedValue({ - response: { data: mockErrorResponse }, - }); + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); - const result = await getNotificationSettings(mockEmail); + const result = await apiServices.getNotificationSettings(mockEmail); expect(result).toEqual(mockErrorResponse); }); }); - describe("getUserBookings", () => { - it("should return success response when API call is successful", async () => { - const mockAuthToken = "mockAuthToken"; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken); - (axios.get as jest.Mock).mockResolvedValue({ - data: { - data: { bookings: [] }, - status: 'success', - message: 'Bookings retrieved successfully' - } - }); - - const result = await getUserBookings(mockEmail); - - expect(axios.get).toHaveBeenCalledWith( + describe('getUserBookings', () => { + it('should return success response when API call is successful', async () => { + mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.getUserBookings(mockEmail); + + expect(mockedAxios.get).toHaveBeenCalledWith( `https://dev.occupi.tech/api/view-bookings?filter={"email":"${mockEmail}"}`, expect.objectContaining({ headers: expect.objectContaining({ Authorization: mockAuthToken }), }) ); - expect(result).toEqual({ - data: { bookings: [] }, - status: 'success', - message: 'Bookings retrieved successfully' - }); + expect(result).toEqual(mockSuccessResponse); }); - - it("should return error response when API call fails", async () => { - const mockAuthToken = "mockAuthToken"; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken); - (axios.get as jest.Mock).mockRejectedValue({ - response: { - data: { - data: null, - status: 'error', - message: 'Failed to retrieve bookings', - error: { - code: 'API_ERROR', - details: 'API call failed', - message: 'Failed to retrieve bookings' - } - } - }, - }); - - const result = await getUserBookings(mockEmail); - - expect(result).toEqual({ - data: null, - status: 'error', - message: 'Failed to retrieve bookings', - error: { - code: 'API_ERROR', - details: 'API call failed', - message: 'Failed to retrieve bookings' - } - }); + + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.getUserBookings(mockEmail); + + expect(result).toEqual(mockErrorResponse); }); - - it("should handle case when auth token is not found", async () => { - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null); - - const result = await getUserBookings(mockEmail); - - expect(result).toEqual({ - data: null, + + it('should handle case when auth token is not found', async () => { + mockedSecureStore.getItemAsync.mockResolvedValue(null); + + const result = await apiServices.getUserBookings(mockEmail); + + expect(result).toEqual(expect.objectContaining({ status: 'error', message: 'Authentication failed', - error: { - code: 'AUTH_ERROR', - details: 'No authentication token found', - message: 'Authentication failed' - } - }); + })); + }); + }); + + + describe('updateUserDetails', () => { + const mockUpdateReq = { email: mockEmail, name: 'Test User' }; + + it('should return success response when API call is successful', async () => { + mockedAxios.post.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.updateUserDetails(mockUpdateReq); + + expect(mockedAxios.post).toHaveBeenCalledWith( + "https://dev.occupi.tech/api/update-user", + mockUpdateReq, + expect.objectContaining({ + headers: expect.objectContaining({ Authorization: mockAuthToken }), + }) + ); + expect(storeUserData).toHaveBeenCalledWith(JSON.stringify(mockUpdateReq)); + expect(result).toEqual(mockSuccessResponse); + }); + + it('should return error response when API call fails', async () => { + mockedAxios.post.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.updateUserDetails(mockUpdateReq); + + expect(result).toEqual(mockErrorResponse); + }); + }); + +describe('getNotifications', () => { + const mockNotificationsReq = { email: mockEmail, page: 1, limit: 10 }; + + it('should return success response when API call is successful', async () => { + mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.getNotifications(mockNotificationsReq); + + expect(mockedAxios.get).toHaveBeenCalledWith( + "https://dev.occupi.tech/api/get-notifications", + expect.objectContaining({ + params: mockNotificationsReq, + headers: expect.objectContaining({ Authorization: mockAuthToken }), + }) + ); + expect(result).toEqual(mockSuccessResponse); + }); + + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.getNotifications(mockNotificationsReq); + + expect(result).toEqual(mockErrorResponse); }); }); - describe("getSecuritySettings", () => { - it("should return success response when API call is successful", async () => { - (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse }); + describe('checkin', () => { + const mockCheckInReq = { email: mockEmail, bookingId: '12345' }; - const result = await getSecuritySettings(mockEmail); + it('should return success response when API call is successful', async () => { + mockedAxios.post.mockResolvedValue({ data: mockSuccessResponse }); - expect(axios.get).toHaveBeenCalledWith( + const result = await apiServices.checkin(mockCheckInReq); + + expect(mockedAxios.post).toHaveBeenCalledWith( + "https://dev.occupi.tech/api/check-in", + mockCheckInReq, + expect.objectContaining({ + headers: expect.objectContaining({ Authorization: mockAuthToken }), + }) + ); + expect(result).toEqual(mockSuccessResponse); + }); + + it('should return error response when API call fails', async () => { + mockedAxios.post.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.checkin(mockCheckInReq); + + expect(result).toEqual(mockErrorResponse); + }); + }); + + describe('bookRoom', () => { + const mockBookRoomReq = { email: mockEmail, roomId: '12345', startTime: '2023-08-15T10:00:00', endTime: '2023-08-15T11:00:00' }; + + it('should return success response when API call is successful', async () => { + mockedAxios.post.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.bookRoom(mockBookRoomReq); + + expect(mockedAxios.post).toHaveBeenCalledWith( + "https://dev.occupi.tech/api/book-room", + mockBookRoomReq, + expect.objectContaining({ + headers: expect.objectContaining({ Authorization: mockAuthToken }), + }) + ); + expect(result).toEqual(mockSuccessResponse); + }); + + it('should return error response when API call fails', async () => { + mockedAxios.post.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.bookRoom(mockBookRoomReq); + + expect(result).toEqual(mockErrorResponse); + }); + }); + + describe('cancelBooking', () => { + const mockCancelBookingReq = { email: mockEmail, bookingId: '12345' }; + + it('should return success response when API call is successful', async () => { + mockedAxios.post.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.cancelBooking(mockCancelBookingReq); + + expect(mockedAxios.post).toHaveBeenCalledWith( + "https://dev.occupi.tech/api/cancel-booking", + mockCancelBookingReq, + expect.objectContaining({ + headers: expect.objectContaining({ Authorization: mockAuthToken }), + }) + ); + expect(result).toEqual(mockSuccessResponse); + }); + + it('should return error response when API call fails', async () => { + mockedAxios.post.mockRejectedValue({ response: { data: mockErrorResponse } }); + + const result = await apiServices.cancelBooking(mockCancelBookingReq); + + expect(result).toEqual(mockErrorResponse); + }); + }); + + describe('getSecuritySettings', () => { + it('should return success response when API call is successful', async () => { + mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.getSecuritySettings(mockEmail); + + expect(mockedAxios.get).toHaveBeenCalledWith( "https://dev.occupi.tech/api/get-security-settings", expect.objectContaining({ params: { email: mockEmail }, headers: expect.objectContaining({ Authorization: mockAuthToken }), }) ); - expect(result).toEqual(mockSuccessResponse); }); - it("should return error response when API call fails", async () => { - (axios.get as jest.Mock).mockRejectedValue({ - response: { data: mockErrorResponse }, - }); + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); - const result = await getSecuritySettings(mockEmail); + const result = await apiServices.getSecuritySettings(mockEmail); expect(result).toEqual(mockErrorResponse); }); }); - describe("updateSecuritySettings", () => { - it("should return success response when API call is successful", async () => { - (axios.post as jest.Mock).mockResolvedValue({ - data: mockSuccessResponse, - }); - const mockReq = { email: mockEmail, newSetting: "value" }; + describe('updateSecuritySettings', () => { + const mockSecuritySettingsReq = { email: mockEmail, twoFactor: true }; + + it('should return success response when API call is successful', async () => { + mockedAxios.post.mockResolvedValue({ data: mockSuccessResponse }); - const result = await updateSecuritySettings(mockReq); + const result = await apiServices.updateSecuritySettings(mockSecuritySettingsReq); - expect(axios.post).toHaveBeenCalledWith( + expect(mockedAxios.post).toHaveBeenCalledWith( "https://dev.occupi.tech/api/update-security-settings", - mockReq, + mockSecuritySettingsReq, expect.objectContaining({ headers: expect.objectContaining({ Authorization: mockAuthToken }), }) @@ -214,52 +303,39 @@ describe("User API Functions", () => { expect(result).toEqual(mockSuccessResponse); }); - it("should return error response when API call fails", async () => { - (axios.post as jest.Mock).mockRejectedValue({ - response: { data: mockErrorResponse }, - }); - const mockReq = { email: mockEmail, newSetting: "value" }; + it('should return error response when API call fails', async () => { + mockedAxios.post.mockRejectedValue({ response: { data: mockErrorResponse } }); - const result = await updateSecuritySettings(mockReq); + const result = await apiServices.updateSecuritySettings(mockSecuritySettingsReq); expect(result).toEqual(mockErrorResponse); }); }); - describe("updateNotificationSettings", () => { - it("should return success response when API call is successful", async () => { - (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse }); - const mockReq: NotificationSettingsReq = { - email: mockEmail, - invites: "on", - bookingReminder: "on", - }; + describe('updateNotificationSettings', () => { + const mockNotificationSettingsReq = { email: mockEmail, invites: 'on', bookingReminder: 'off' }; - const result = await updateNotificationSettings(mockReq); + it('should return success response when API call is successful', async () => { + mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse }); + + const result = await apiServices.updateNotificationSettings(mockNotificationSettingsReq); - expect(axios.get).toHaveBeenCalledWith( + expect(mockedAxios.get).toHaveBeenCalledWith( "https://dev.occupi.tech/api/update-notification-settings", expect.objectContaining({ - params: { req: mockReq }, + params: { req: mockNotificationSettingsReq }, headers: expect.objectContaining({ Authorization: mockAuthToken }), }) ); expect(result).toEqual(mockSuccessResponse); }); - it("should return error response when API call fails", async () => { - (axios.get as jest.Mock).mockRejectedValue({ - response: { data: mockErrorResponse }, - }); - const mockReq: NotificationSettingsReq = { - email: mockEmail, - invites: "on", - bookingReminder: "on", - }; + it('should return error response when API call fails', async () => { + mockedAxios.get.mockRejectedValue({ response: { data: mockErrorResponse } }); - const result = await updateNotificationSettings(mockReq); + const result = await apiServices.updateNotificationSettings(mockNotificationSettingsReq); expect(result).toEqual(mockErrorResponse); }); }); -}); +}); \ No newline at end of file diff --git a/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx b/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx index 31c8dc3a..61f485b1 100644 --- a/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx +++ b/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx @@ -1,8 +1,7 @@ import axios from 'axios'; import * as SecureStore from 'expo-secure-store'; -import { login, logout } from '../../services/authservices'; -import { LoginReq } from '@/models/requests'; -import { LoginSuccess, Unsuccessful, Success } from '@/models/response'; +import * as authServices from '../authservices'; +import { LoginReq, RegisterReq, VerifyOTPReq } from "@/models/requests"; jest.mock('axios'); jest.mock('expo-secure-store'); @@ -10,128 +9,200 @@ jest.mock('expo-secure-store'); const mockedAxios = axios as jest.Mocked; const mockedSecureStore = SecureStore as jest.Mocked; -describe('authservice', () => { +describe('Auth Services', () => { beforeEach(() => { jest.clearAllMocks(); - mockedSecureStore.getItemAsync.mockResolvedValue('mock-token'); + console.log = jest.fn(); }); describe('login', () => { - const loginReq: LoginReq = { - email: 'test@example.com', - password: 'password123', - }; + const loginReq: LoginReq = { email: 'test@example.com', password: 'password123' }; it('should return LoginSuccess on successful login', async () => { - const mockResponse: LoginSuccess = { - data: { token: 'mock-token' }, - message: 'Login successful', - status: 200, + const mockResponse = { data: { token: 'abc123', user: { id: 1, name: 'Test User' } } }; + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await authServices.login(loginReq); + + expect(result).toEqual(mockResponse.data); + }); + + it('should throw error on login failure', async () => { + const mockError = { + response: { data: { message: 'Invalid credentials' } } }; + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.login(loginReq)).rejects.toEqual(mockError); + }); - mockedAxios.post.mockResolvedValueOnce({ data: mockResponse }); + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedAxios.post.mockRejectedValue(mockError); - const result = await login(loginReq); + await expect(authServices.login(loginReq)).rejects.toThrow('Network error'); + }); - expect(mockedAxios.post).toHaveBeenCalledWith( - 'https://dev.occupi.tech/auth/login-mobile', - loginReq, - expect.any(Object) - ); - expect(result).toEqual(mockResponse); + it('should handle non-axios errors in login', async () => { + const nonAxiosError = new Error('Non-Axios error'); + mockedAxios.post.mockRejectedValue(nonAxiosError); + + await expect(authServices.login(loginReq)).rejects.toThrow('Non-Axios error'); }); + }); + + describe('register', () => { + const registerReq: RegisterReq = { email: 'test@example.com', password: 'password123', name: 'Test User' }; + + it('should return Success on successful registration', async () => { + const mockResponse = { data: { message: 'Registration successful' } }; + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await authServices.register(registerReq); - it('should throw error on failed login', async () => { - const mockError: Unsuccessful = { - data: null, - status: 'error', - message: 'Invalid credentials', - error: { - code: 'AUTH_ERROR', - details: 'Invalid email or password', - message: 'Authentication failed', - } + expect(result).toEqual(mockResponse.data); + }); + + it('should throw error on registration failure', async () => { + const mockError = { + response: { data: { message: 'Email already exists' } } }; + mockedAxios.post.mockRejectedValue(mockError); - mockedAxios.post.mockRejectedValueOnce({ - isAxiosError: true, - response: { data: mockError } - }); - - await expect(login(loginReq)).rejects.toEqual( - expect.objectContaining({ - isAxiosError: true, - response: { data: mockError } - }) - ); + await expect(authServices.register(registerReq)).rejects.toEqual(mockError); }); - it('should throw error on network failure', async () => { - const networkError = new Error('Network error'); - mockedAxios.post.mockRejectedValueOnce(networkError); + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedAxios.post.mockRejectedValue(mockError); - await expect(login(loginReq)).rejects.toThrow('Network error'); + await expect(authServices.register(registerReq)).rejects.toThrow('Network error'); + }); + + it('should handle non-axios errors in register', async () => { + const nonAxiosError = new Error('Non-Axios error'); + mockedAxios.post.mockRejectedValue(nonAxiosError); + + await expect(authServices.register(registerReq)).rejects.toThrow('Non-Axios error'); }); }); - describe('logout', () => { - beforeEach(() => { - mockedSecureStore.getItemAsync.mockResolvedValue('mock-token'); + describe('verifyOtpRegister', () => { + const verifyOTPReq: VerifyOTPReq = { email: 'test@example.com', otp: '123456' }; + + it('should return LoginSuccess on successful OTP verification for registration', async () => { + const mockResponse = { data: { token: 'abc123', user: { id: 1, name: 'Test User' } } }; + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await authServices.verifyOtpRegister(verifyOTPReq); + + expect(result).toEqual(mockResponse.data); }); - it('should return Success on successful logout', async () => { - const mockResponse: Success = { - status: 200, - message: 'Logout successful', - data: null, + it('should throw error on OTP verification failure for registration', async () => { + const mockError = { + response: { data: { message: 'Invalid OTP' } } }; + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.verifyOtpRegister(verifyOTPReq)).rejects.toEqual(mockError); + }); - mockedAxios.post.mockResolvedValueOnce({ data: mockResponse }); - - const result = await logout(); - - expect(mockedAxios.post).toHaveBeenCalledWith( - 'https://dev.occupi.tech/auth/logout', - {}, - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'mock-token', - }), - }) - ); - expect(result).toEqual(mockResponse); - }); - - it('should throw error on failed logout', async () => { - const mockError: Unsuccessful = { - data: null, - status: 'error', - message: 'Logout failed', - error: { - code: 'LOGOUT_ERROR', - details: 'Unable to logout', - message: 'Logout operation failed', - } - }; + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.verifyOtpRegister(verifyOTPReq)).rejects.toThrow('Network error'); + }); + + it('should handle non-axios errors in verifyOtpRegister', async () => { + const nonAxiosError = new Error('Non-Axios error'); + mockedAxios.post.mockRejectedValue(nonAxiosError); - mockedAxios.post.mockRejectedValueOnce({ - isAxiosError: true, - response: { data: mockError } - }); + await expect(authServices.verifyOtpRegister(verifyOTPReq)).rejects.toThrow('Non-Axios error'); + }); + }); + + describe('verifyOtplogin', () => { + const verifyOTPReq: VerifyOTPReq = { email: 'test@example.com', otp: '123456' }; + + it('should return LoginSuccess on successful OTP verification for login', async () => { + const mockResponse = { data: { token: 'abc123', user: { id: 1, name: 'Test User' } } }; + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await authServices.verifyOtplogin(verifyOTPReq); + + expect(result).toEqual(mockResponse.data); + }); + + it('should throw error on OTP verification failure for login', async () => { + const mockError = { + response: { data: { message: 'Invalid OTP' } } + }; + mockedAxios.post.mockRejectedValue(mockError); - await expect(logout()).rejects.toEqual( - expect.objectContaining({ - isAxiosError: true, - response: { data: mockError } - }) - ); + await expect(authServices.verifyOtplogin(verifyOTPReq)).rejects.toEqual(mockError); }); - it('should throw error on network failure', async () => { - const networkError = new Error('Network error'); - mockedAxios.post.mockRejectedValueOnce(networkError); + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedAxios.post.mockRejectedValue(mockError); - await expect(logout()).rejects.toThrow('Network error'); + await expect(authServices.verifyOtplogin(verifyOTPReq)).rejects.toThrow('Network error'); + }); + + it('should handle non-axios errors in verifyOtplogin', async () => { + const nonAxiosError = new Error('Non-Axios error'); + mockedAxios.post.mockRejectedValue(nonAxiosError); + + await expect(authServices.verifyOtplogin(verifyOTPReq)).rejects.toThrow('Non-Axios error'); }); }); + + describe('logout', () => { + it('should return Success on successful logout', async () => { + const mockToken = 'abc123'; + const mockResponse = { data: { message: 'Logout successful' } }; + mockedSecureStore.getItemAsync.mockResolvedValue(mockToken); + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await authServices.logout(); + + expect(result).toEqual(mockResponse.data); + }); + + it('should throw error on logout failure', async () => { + const mockError = { + response: { data: { message: 'Logout failed' } } + }; + mockedSecureStore.getItemAsync.mockResolvedValue('abc123'); + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.logout()).rejects.toEqual(mockError); + }); + + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedSecureStore.getItemAsync.mockResolvedValue('abc123'); + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.logout()).rejects.toThrow('Network error'); + }); + + it('should throw error for non-Axios errors', async () => { + const mockError = new Error('Network error'); + mockedSecureStore.getItemAsync.mockResolvedValue('abc123'); + mockedAxios.post.mockRejectedValue(mockError); + + await expect(authServices.logout()).rejects.toThrow('Network error'); + }); + + it('should handle non-axios errors in logout', async () => { + mockedSecureStore.getItemAsync.mockResolvedValue('some-token'); + const nonAxiosError = new Error('Non-Axios error'); + mockedAxios.post.mockRejectedValue(nonAxiosError); + + await expect(authServices.logout()).rejects.toThrow('Non-Axios error'); }); + }); +}); \ No newline at end of file diff --git a/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx b/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx index be6a6a6e..1325fb09 100644 --- a/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx +++ b/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx @@ -1,83 +1,97 @@ import * as SecureStore from 'expo-secure-store'; -import { - storeUserData, - storeToken, - getUserData, - getToken, - deleteUserData, - deleteAllData, - } from '../securestore'; - - jest.mock('expo-secure-store'); +import * as secureStore from '../securestore'; + +jest.mock('expo-secure-store'); const mockedSecureStore = SecureStore as jest.Mocked; - describe('SecureStore', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('storeUserData', () => { - it('should store user data', async () => { - const userData = JSON.stringify({ id: 1, name: 'John Doe' }); - await storeUserData(userData); - expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith('UserData', userData); - }); - }); - - describe('storeToken', () => { - it('should store token', async () => { - const token = 'abc123'; - await storeToken(token); - expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith('Token', token); - }); - }); - - describe('getUserData', () => { - it('should return parsed user data when it exists', async () => { - const userData = { id: 1, name: 'John Doe' }; - mockedSecureStore.getItemAsync.mockResolvedValue(JSON.stringify(userData)); - const result = await getUserData(); - expect(result).toEqual(userData); - }); - - it('should return null when token does not exist', async () => { - mockedSecureStore.getItemAsync.mockResolvedValue(null); - const result = await getUserData(); // Corrected this line - expect(result).toBeNull(); - }); - }); - - describe('getToken', () => { - it('should return token when it exists', async () => { - const token = 'abc123'; - mockedSecureStore.getItemAsync.mockResolvedValue(token); - const result = await getToken(); - expect(result).toBe(token); - }); - - it('should return undefined when token does not exist', async () => { - mockedSecureStore.getItemAsync.mockResolvedValue(null); - const result = await getToken(); - expect(result).toBeUndefined(); - }); - }); - - describe('deleteUserData', () => { - it('should delete user data', async () => { - await deleteUserData(); - expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData'); - }); - }); - - describe('deleteAllData', () => { - it('should delete all data', async () => { - await deleteAllData(); - expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData'); - expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Token'); - expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Email'); - }); - }); - - - }); \ No newline at end of file +describe('Secure Store Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + // Test store functions + const storeTestCases = [ + { func: secureStore.storeUserData, key: 'UserData' }, + { func: secureStore.storeToken, key: 'Token' }, + { func: secureStore.storeUserEmail, key: 'Email' }, + { func: secureStore.setState, key: 'AppState' }, + { func: secureStore.storeNotificationSettings, key: 'Notifications' }, + { func: secureStore.storeTheme, key: 'Theme' }, + { func: secureStore.storeAccentColour, key: 'accentColour' }, + { func: secureStore.storeSecuritySettings, key: 'Security' }, + ]; + + test.each(storeTestCases)('$func stores value correctly', async ({ func, key }) => { + const testValue = 'test-value'; + await func(testValue); + expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith(key, testValue); + }); + + // Test getUserData + it('getUserData returns parsed data when available', async () => { + const mockData = { name: 'John Doe' }; + mockedSecureStore.getItemAsync.mockResolvedValue(JSON.stringify(mockData)); + const result = await secureStore.getUserData(); + expect(result).toEqual(mockData); + }); + + it('getUserData returns null when no data available', async () => { + mockedSecureStore.getItemAsync.mockResolvedValue(null); + const result = await secureStore.getUserData(); + expect(result).toBeNull(); + }); + + // Test getToken + it('getToken returns token when available', async () => { + const mockToken = 'mock-token'; + mockedSecureStore.getItemAsync.mockResolvedValue(mockToken); + const result = await secureStore.getToken(); + expect(result).toBe(mockToken); + }); + + it('getToken returns undefined when no token available', async () => { + mockedSecureStore.getItemAsync.mockResolvedValue(null); + const result = await secureStore.getToken(); + expect(result).toBeUndefined(); + }); + + // Test getUserEmail + it('getUserEmail returns email when available', async () => { + const mockEmail = 'test@example.com'; + mockedSecureStore.getItemAsync.mockResolvedValue(mockEmail); + const result = await secureStore.getUserEmail(); + expect(result).toBe(mockEmail); + }); + + // Test getCurrentRoom + it('getCurrentRoom returns room when available', async () => { + const mockRoom = 'Room 101'; + mockedSecureStore.getItemAsync.mockResolvedValue(mockRoom); + const result = await secureStore.getCurrentRoom(); + expect(result).toBe(mockRoom); + }); + + // Test delete functions + const deleteTestCases = [ + { func: secureStore.deleteUserData, key: 'UserData' }, + { func: secureStore.deleteToken, key: 'Token' }, + { func: secureStore.deleteUserEmail, key: 'Email' }, + { func: secureStore.deleteNotificationSettings, key: 'Notifications' }, + { func: secureStore.deleteSecuritySettings, key: 'Security' }, + ]; + + test.each(deleteTestCases)('$func deletes item correctly', async ({ func, key }) => { + await func(); + expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith(key); + }); + + // Test deleteAllData + it('deleteAllData deletes all specified items', async () => { + await secureStore.deleteAllData(); + expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData'); + expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Token'); + expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Email'); + expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledTimes(5); + }); +}); \ No newline at end of file diff --git a/frontend/occupi-mobile4/services/aimodel.ts b/frontend/occupi-mobile4/services/aimodel.ts index 60ea7605..c8dd6dca 100644 --- a/frontend/occupi-mobile4/services/aimodel.ts +++ b/frontend/occupi-mobile4/services/aimodel.ts @@ -41,4 +41,6 @@ export async function getDayPredictions(): Promise { return undefined; } + +export { Prediction }; // getPredictions(); \ No newline at end of file diff --git a/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx b/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx index b59f3e88..3dd372fb 100644 --- a/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx +++ b/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx @@ -1,9 +1,8 @@ -import { fetchUserBookings, userCheckin, userCancelBooking } from '../bookings'; -import { getUserBookings, checkin, cancelBooking } from '../../services/apiservices'; +import * as bookingsUtils from '../bookings'; +import * as apiServices from '../../services/apiservices'; import * as SecureStore from 'expo-secure-store'; import { router } from 'expo-router'; -// Mock dependencies jest.mock('../../services/apiservices'); jest.mock('expo-secure-store'); jest.mock('expo-router', () => ({ @@ -12,114 +11,227 @@ jest.mock('expo-router', () => ({ }, })); -beforeEach(() => { - jest.spyOn(console, 'error').mockImplementation(() => {}); -}); - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe('../bookings.ts', () => { +describe('bookings utils', () => { beforeEach(() => { jest.clearAllMocks(); + console.log = jest.fn(); + console.error = jest.fn(); }); describe('fetchUserBookings', () => { it('should fetch user bookings successfully', async () => { const mockEmail = 'test@example.com'; - const mockBookings = [{ id: 1, title: 'Booking 1' }, { id: 2, title: 'Booking 2' }]; + const mockBookings = [{ id: 1, name: 'Booking 1' }]; (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockEmail); - (getUserBookings as jest.Mock).mockResolvedValue({ status: 200, data: mockBookings }); + (apiServices.getUserBookings as jest.Mock).mockResolvedValue({ + status: 200, + data: mockBookings, + }); - const result = await fetchUserBookings(); + const result = await bookingsUtils.fetchUserBookings(); expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email'); - expect(getUserBookings).toHaveBeenCalledWith(mockEmail); + expect(apiServices.getUserBookings).toHaveBeenCalledWith(mockEmail); expect(result).toEqual(mockBookings); }); - it('should handle errors when fetching user bookings', async () => { + it('should log response when status is not 200', async () => { + const mockEmail = 'test@example.com'; + const mockResponse = { status: 400, data: 'Error' }; + + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockEmail); + (apiServices.getUserBookings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await bookingsUtils.fetchUserBookings(); + + expect(console.log).toHaveBeenCalledWith(mockResponse); + expect(result).toEqual('Error'); + }); + + it('should handle and throw errors', async () => { const mockError = new Error('API Error'); - + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('test@example.com'); - (getUserBookings as jest.Mock).mockRejectedValue(mockError); + (apiServices.getUserBookings as jest.Mock).mockRejectedValue(mockError); - await expect(fetchUserBookings()).rejects.toThrow('API Error'); + await expect(bookingsUtils.fetchUserBookings()).rejects.toThrow('API Error'); + expect(console.error).toHaveBeenCalledWith('Error:', mockError); }); }); - describe('userCheckin', () => { - it('should perform user check-in successfully', async () => { - const mockRoom = { occupiId: 'room123' }; + describe('userBookRoom', () => { + it('should book a room successfully', async () => { + const mockRoom = JSON.stringify({ + roomName: 'Room 1', + date: '2024-08-14', + floorNo: 1, + roomId: 'room1', + }); const mockEmail = 'test@example.com'; + const mockAttendees = ['user1@example.com', 'user2@example.com']; + const mockStartTime = '09:00'; + const mockEndTime = '10:00'; (SecureStore.getItemAsync as jest.Mock) - .mockResolvedValueOnce(JSON.stringify(mockRoom)) + .mockResolvedValueOnce(mockRoom) .mockResolvedValueOnce(mockEmail); - (checkin as jest.Mock).mockResolvedValue({ status: 200, message: 'Check-in successful' }); + + (apiServices.bookRoom as jest.Mock).mockResolvedValue({ + status: 200, + message: 'Room booked successfully', + }); - const result = await userCheckin(); + const result = await bookingsUtils.userBookRoom(mockAttendees, mockStartTime, mockEndTime); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom'); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email'); - expect(checkin).toHaveBeenCalledWith({ email: mockEmail, bookingId: 'room123' }); - expect(result).toBe('Check-in successful'); + expect(console.log).toHaveBeenCalled(); // Check if console.log was called + expect(apiServices.bookRoom).toHaveBeenCalled(); + expect(result).toBe('Room booked successfully'); }); - it('should handle errors during user check-in', async () => { + it('should handle booking failure', async () => { + const mockRoom = JSON.stringify({ + roomName: 'Room 1', + date: '2024-08-14', + floorNo: 1, + roomId: 'room1', + }); + + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce(mockRoom) + .mockResolvedValueOnce('test@example.com'); + + (apiServices.bookRoom as jest.Mock).mockResolvedValue({ + status: 400, + message: 'Booking failed', + }); + + const result = await bookingsUtils.userBookRoom([], '09:00', '10:00'); + + expect(result).toBe('Booking failed'); + }); + + it('should handle and throw errors', async () => { + const mockError = new Error('Booking Error'); + + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('{}') + .mockResolvedValueOnce('test@example.com'); + + (apiServices.bookRoom as jest.Mock).mockRejectedValue(mockError); + + await expect(bookingsUtils.userBookRoom([], '09:00', '10:00')).rejects.toThrow('Booking Error'); + expect(console.error).toHaveBeenCalledWith('Error:', mockError); + }); + }); + + describe('userCheckin', () => { + it('should check in successfully', async () => { + const mockRoom = JSON.stringify({ + occupiId: 'booking1', + }); + + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce(mockRoom) + .mockResolvedValueOnce('test@example.com'); + + (apiServices.checkin as jest.Mock).mockResolvedValue({ + status: 200, + message: 'Checked in successfully', + }); + + const result = await bookingsUtils.userCheckin(); + + expect(apiServices.checkin).toHaveBeenCalled(); + expect(result).toBe('Checked in successfully'); + }); + + it('should handle check-in failure', async () => { + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('{}') + .mockResolvedValueOnce('test@example.com'); + + (apiServices.checkin as jest.Mock).mockResolvedValue({ + status: 400, + message: 'Check-in failed', + }); + + const result = await bookingsUtils.userCheckin(); + + expect(result).toBe('Check-in failed'); + }); + + it('should handle and throw errors', async () => { const mockError = new Error('Check-in Error'); + + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('{}') + .mockResolvedValueOnce('test@example.com'); - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('{}'); - (checkin as jest.Mock).mockRejectedValue(mockError); + (apiServices.checkin as jest.Mock).mockRejectedValue(mockError); - await expect(userCheckin()).rejects.toThrow('Check-in Error'); + await expect(bookingsUtils.userCheckin()).rejects.toThrow('Check-in Error'); + expect(console.error).toHaveBeenCalledWith('Error:', mockError); }); }); describe('userCancelBooking', () => { - it('should cancel user booking successfully', async () => { - const mockRoom = { - occupiId: 'booking123', - emails: ['user1@example.com', 'user2@example.com'], - roomId: 'room123', - creator: 'user1@example.com', - date: '2023-07-30', - start: '10:00', - end: '11:00', + it('should cancel booking successfully', async () => { + const mockRoom = JSON.stringify({ + occupiId: 'booking1', + emails: ['user1@example.com'], + roomId: 'room1', + creator: 'creator@example.com', + date: '2024-08-14', + start: '09:00', + end: '10:00', floorNo: 1, - roomName: 'Meeting Room A' - }; + roomName: 'Room 1', + }); - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify(mockRoom)); - (cancelBooking as jest.Mock).mockResolvedValue({ status: 200, message: 'Booking cancelled successfully' }); - - const result = await userCancelBooking(); - - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom'); - expect(cancelBooking).toHaveBeenCalledWith(expect.objectContaining({ - bookingId: 'booking123', - emails: ['user1@example.com', 'user2@example.com'], - roomId: 'room123', - creator: 'user1@example.com', - date: '2023-07-30', - start: '10:00', - end: '11:00', - floorNo: 1, - roomName: 'Meeting Room A' - })); + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce(mockRoom) + .mockResolvedValueOnce('test@example.com'); + + (apiServices.cancelBooking as jest.Mock).mockResolvedValue({ + status: 200, + message: 'Booking cancelled successfully', + }); + + const result = await bookingsUtils.userCancelBooking(); + + expect(apiServices.cancelBooking).toHaveBeenCalled(); expect(router.replace).toHaveBeenCalledWith('/home'); expect(result).toBe('Booking cancelled successfully'); }); - it('should handle errors during booking cancellation', async () => { + it('should handle cancellation failure', async () => { + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('{}') + .mockResolvedValueOnce('test@example.com'); + + (apiServices.cancelBooking as jest.Mock).mockResolvedValue({ + status: 400, + message: 'Cancellation failed', + }); + + const result = await bookingsUtils.userCancelBooking(); + + expect(result).toBe('Cancellation failed'); + expect(router.replace).not.toHaveBeenCalled(); + }); + + it('should handle and throw errors', async () => { const mockError = new Error('Cancellation Error'); + + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('{}') + .mockResolvedValueOnce('test@example.com'); - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('{}'); - (cancelBooking as jest.Mock).mockRejectedValue(mockError); + (apiServices.cancelBooking as jest.Mock).mockRejectedValue(mockError); - await expect(userCancelBooking()).rejects.toThrow('Cancellation Error'); + await expect(bookingsUtils.userCancelBooking()).rejects.toThrow('Cancellation Error'); + expect(console.error).toHaveBeenCalledWith('Error:', mockError); }); }); }); \ No newline at end of file diff --git a/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx b/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx new file mode 100644 index 00000000..ae1c8fbe --- /dev/null +++ b/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx @@ -0,0 +1,312 @@ +import { Platform } from 'react-native'; +import * as Device from 'expo-device'; +import * as Notifications from 'expo-notifications'; +import Constants from 'expo-constants'; +import * as SecureStore from 'expo-secure-store'; +import { getNotifications } from '@/services/apiservices'; +import { + retrievePushToken, + sendPushNotification, + getUserNotifications, + registerForPushNotificationsAsync, + setupNotificationHandler +} from '../notifications'; + +jest.mock('expo-device', () => ({ + isDevice: jest.fn().mockReturnValue(true), +})); + +// jest.mock('expo-notifications', () => ({ +// getPermissionsAsync: jest.fn(), +// requestPermissionsAsync: jest.fn(), +// getExpoPushTokenAsync: jest.fn(), +// setNotificationChannelAsync: jest.fn(), +// setNotificationHandler: jest.fn(), +// AndroidImportance: { +// MAX: 5, +// }, +// })); + +jest.mock('expo-notifications', () => ({ + getPermissionsAsync: jest.fn(() => + Promise.resolve({ status: 'undetermined' }) + ), + requestPermissionsAsync: jest.fn(() => + Promise.resolve({ status: 'granted' }) + ), + getExpoPushTokenAsync: jest.fn(() => + Promise.resolve({ data: 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]' }) + ), + setNotificationChannelAsync: jest.fn(), + setNotificationHandler: jest.fn(), + AndroidImportance: { + MAX: 5, + }, +})); + + + + +jest.mock('expo-constants', () => ({ + expoConfig: { + extra: { + eas: { + projectId: 'testProjectId', + }, + }, + }, +})); + +jest.mock('expo-secure-store', () => ({ + getItemAsync: jest.fn(), + setItemAsync: jest.fn(), + deleteItemAsync: jest.fn(), +})); + +jest.mock('expo-modules-core', () => ({ + NativeModulesProxy: { + ExpoSecureStore: { + getValueWithKeyAsync: jest.fn(), + setValueWithKeyAsync: jest.fn(), + deleteValueWithKeyAsync: jest.fn(), + }, + }, +})); + +jest.mock('@/services/apiservices', () => ({ + getNotifications: jest.fn(), +})); + +jest.mock('react-native', () => ({ + Platform: { + OS: 'ios', + }, +})); +jest.mock('expo-notifications'); +jest.mock('expo-constants', () => ({ + expoConfig: { + extra: { + eas: { + projectId: 'testProjectId', + }, + }, + }, +})); + +global.alert = jest.fn(); + +jest.mock('expo-device'); +jest.mock('expo-notifications'); +jest.mock('expo-constants', () => ({ + expoConfig: { + extra: { + eas: { + projectId: 'mockProjectId' + } + } + } +})); +jest.mock('expo-secure-store'); +jest.mock('@/services/apiservices'); +jest.mock('expo-device', () => ({ + isDevice: jest.fn(), +})); + +global.alert = jest.fn(); +global.fetch = jest.fn(); + +describe('Notifications', () => { + beforeEach(() => { + jest.clearAllMocks(); + console.log = jest.fn(); + console.error = jest.fn(); + }); + + describe('setupNotificationHandler', () => { + it('should set up the notification handler correctly', () => { + setupNotificationHandler(); + expect(Notifications.setNotificationHandler).toHaveBeenCalledWith({ + handleNotification: expect.any(Function), + }); + }); + }); + + describe('retrievePushToken', () => { + // it('should alert if not on a physical device in registerForPushNotificationsAsync', async () => { + // // Ensure Device.isDevice returns false to simulate not running on a physical device + // (Device.isDevice as jest.Mock).mockReturnValue(false); + + // const alertMock = jest.spyOn(global, 'alert').mockImplementation(() => {}); + + // // Call the function that should trigger the alert + // await retrievePushToken(); + + // // Assert that the alert was called with the correct message + // expect(alertMock).toHaveBeenCalledWith('Must use physical device for Push Notifications'); + + // alertMock.mockRestore(); // Restore the original alert implementation + // }); + + + it('should set notification handler', () => { + setupNotificationHandler(); + expect(Notifications.setNotificationHandler).toHaveBeenCalledWith({ + handleNotification: expect.any(Function), + }); + }); + + it('should retrieve push token successfully', async () => { + const mockToken = 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'; + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' }); + (Notifications.getExpoPushTokenAsync as jest.Mock).mockResolvedValue({ data: mockToken }); + + const result = await retrievePushToken(); + expect(result).toBe(mockToken); + }); + + it('should set up Android notification channel', async () => { + const originalPlatform = Platform.OS; + Platform.OS = 'android'; + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' }); + (Notifications.getExpoPushTokenAsync as jest.Mock).mockResolvedValue({ data: 'mock-token' }); + + await retrievePushToken(); + + expect(Notifications.setNotificationChannelAsync).toHaveBeenCalledWith('default', expect.objectContaining({ + name: 'default', + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: '#FF231F7C', + })); + + Platform.OS = originalPlatform; + }); + + it('should request permissions if not granted', async () => { + const mockToken = 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'; + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'undetermined' }); + (Notifications.requestPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' }); + (Notifications.getExpoPushTokenAsync as jest.Mock).mockResolvedValue({ data: mockToken }); + + const result = await retrievePushToken(); + expect(Notifications.requestPermissionsAsync).toHaveBeenCalled(); + expect(result).toBe(mockToken); + }); + + it('should alert if permissions are not granted', async () => { + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'undetermined' }); + (Notifications.requestPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'denied' }); + + await retrievePushToken(); + expect(global.alert).toHaveBeenCalledWith('Failed to get push token for push notification!'); + }); + + it('should handle error when getting push token', async () => { + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' }); + (Notifications.getExpoPushTokenAsync as jest.Mock).mockRejectedValue(new Error('Token error')); + + const result = await retrievePushToken(); + expect(result).toBe('Error: Token error'); + }); + + it('should handle missing projectId gracefully', async () => { + (Device.isDevice as jest.Mock).mockReturnValue(true); + (Notifications.getPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' }); + jest.resetModules(); + jest.doMock('expo-constants', () => ({})); + const { retrievePushToken: retrievePushTokenNoProjectId } = require('../notifications'); + + const result = await retrievePushTokenNoProjectId(); + expect(result).toBe('Error: Project ID not found'); + }); + }); + + describe('sendPushNotification', () => { + it('should send push notifications to all tokens', async () => { + const mockTokens = ['token1', 'token2']; + const mockTitle = 'Test Title'; + const mockBody = 'Test Body'; + (global.fetch as jest.Mock).mockResolvedValue({ ok: true }); + + await sendPushNotification(mockTokens, mockTitle, mockBody); + + expect(global.fetch).toHaveBeenCalledTimes(2); + expect(global.fetch).toHaveBeenCalledWith( + 'https://exp.host/--/api/v2/push/send', + expect.objectContaining({ + method: 'POST', + headers: expect.any(Object), + body: expect.any(String), + }) + ); + }); + }); + + describe('getUserNotifications', () => { + it('should get user notifications successfully', async () => { + const mockEmail = 'test@example.com'; + const mockNotifications = [{ id: 1, message: 'Test notification' }]; + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockEmail); + (getNotifications as jest.Mock).mockResolvedValue({ + status: 200, + data: mockNotifications, + }); + + const result = await getUserNotifications(); + + expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email'); + expect(getNotifications).toHaveBeenCalledWith({ + filter: { emails: [mockEmail] }, + operator: "eq" + }); + expect(result).toEqual(mockNotifications); + }); + + it('should handle non-200 response', async () => { + const mockEmail = 'test@example.com'; + const mockResponse = { status: 400, data: 'Error' }; + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockEmail); + (getNotifications as jest.Mock).mockResolvedValue(mockResponse); + + const result = await getUserNotifications(); + + expect(console.log).toHaveBeenCalledWith(mockResponse); + expect(result).toBe('Error'); + expect(getNotifications).toHaveBeenCalledWith({ + filter: { emails: [mockEmail] }, + operator: "eq" + }); + }); + + it('should handle null email', async () => { + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null); + (getNotifications as jest.Mock).mockResolvedValue({ + status: 200, + data: [], + }); + + const result = await getUserNotifications(); + + expect(getNotifications).toHaveBeenCalledWith({ + filter: { emails: [null] }, + operator: "eq" + }); + expect(result).toEqual([]); + }); + + it('should handle errors', async () => { + const mockError = new Error('API Error'); + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('test@example.com'); + (getNotifications as jest.Mock).mockRejectedValue(mockError); + + await getUserNotifications(); + + expect(console.error).toHaveBeenCalledWith('Error:', mockError); + }); + }); +}); diff --git a/frontend/occupi-mobile4/utils/__tests__/occupancy-test.tsx b/frontend/occupi-mobile4/utils/__tests__/occupancy-test.tsx index 1458ed03..0bbdba4f 100644 --- a/frontend/occupi-mobile4/utils/__tests__/occupancy-test.tsx +++ b/frontend/occupi-mobile4/utils/__tests__/occupancy-test.tsx @@ -1,5 +1,5 @@ -import { getExtractedPredictions, ExtractedPrediction } from '../occupancy'; -import { getPredictions } from '@/services/aimodel'; +import { getExtractedPredictions, getExtractedDailyPrediction, convertValues, valueToColor, getFormattedPredictionData, getFormattedDailyPredictionData } from '../occupancy'; +import { getPredictions, getDayPredictions } from '@/services/aimodel'; import { Prediction } from '@/models/data'; // Mock dependencies @@ -10,56 +10,46 @@ describe('occupancy.ts', () => { jest.clearAllMocks(); }); - describe('getExtractedPredictions', () => { - it('should extract predictions successfully', async () => { - const mockPredictions: Prediction[] = [ - { - Date: '2023-08-07', - Day_of_week: 1, - Day_of_month: 7, - Is_Weekend: false, - Month: 8, - Predicted_Attendance_Level: 'High', - Predicted_Class: 3, - }, - { - Date: '2023-08-08', - Day_of_week: 2, - Day_of_month: 8, - Is_Weekend: false, - Month: 8, - Predicted_Attendance_Level: 'Medium', - Predicted_Class: 2, - }, - ]; - - (getPredictions as jest.Mock).mockResolvedValue(mockPredictions); - - const result = await getExtractedPredictions(); - - expect(getPredictions).toHaveBeenCalled(); - expect(result).toEqual([ - { - Date: '2023-08-07', - Predicted_Attendance_Level: 'High', - Predicted_Class: 3, - }, - { - Date: '2023-08-08', - Predicted_Attendance_Level: 'Medium', - Predicted_Class: 2, - }, - ]); + jest.mock('../occupancy', () => ({ + ...jest.requireActual('../occupancy'), + getExtractedPredictions: jest.fn(), + getExtractedDailyPrediction: jest.fn(), + })); + + + describe('getExtractedDailyPrediction', () => { + it('should extract daily prediction successfully', async () => { + const mockPrediction: Prediction = { + Date: '2023-08-07', + Day_of_Week: 1, + Day_of_month: 7, + Is_Weekend: false, + Month: 8, + Predicted_Attendance_Level: 'High', + Predicted_Class: 3, + }; + + (getDayPredictions as jest.Mock).mockResolvedValue(mockPrediction); + + const result = await getExtractedDailyPrediction(); + + expect(getDayPredictions).toHaveBeenCalled(); + expect(result).toEqual({ + Date: '2023-08-07', + Predicted_Attendance_Level: 'High', + Predicted_Class: 3, + Day_of_week: 1, + }); }); it('should return undefined when no predictions are received', async () => { - (getPredictions as jest.Mock).mockResolvedValue(null); + (getDayPredictions as jest.Mock).mockResolvedValue(null); const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - const result = await getExtractedPredictions(); + const result = await getExtractedDailyPrediction(); - expect(getPredictions).toHaveBeenCalled(); + expect(getDayPredictions).toHaveBeenCalled(); expect(result).toBeUndefined(); expect(consoleSpy).toHaveBeenCalledWith('No predictions data received'); @@ -67,17 +57,152 @@ describe('occupancy.ts', () => { }); it('should handle errors and return undefined', async () => { - (getPredictions as jest.Mock).mockRejectedValue(new Error('API error')); + (getDayPredictions as jest.Mock).mockRejectedValue(new Error('API error')); const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - const result = await getExtractedPredictions(); + const result = await getExtractedDailyPrediction(); - expect(getPredictions).toHaveBeenCalled(); + expect(getDayPredictions).toHaveBeenCalled(); expect(result).toBeUndefined(); expect(consoleSpy).toHaveBeenCalledWith('Error in getExtractedPredictions:', expect.any(Error)); consoleSpy.mockRestore(); }); }); + + describe('convertValues', () => { + it('should convert values correctly', () => { + const data = [ + { label: 'Monday', value: 1 }, + { label: 'Tuesday', value: 2 }, + { label: 'Wednesday', value: 3 }, + { label: 'Thursday', value: 4 }, + { label: 'Friday', value: 5 }, + { label: 'Saturday', value: 6 }, + { label: 'Sunday', value: 7 }, + ]; + + const result = convertValues(data); + + expect(result).toEqual([ + { label: 'Monday', value: 150 }, + { label: 'Tuesday', value: 450 }, + { label: 'Wednesday', value: 750 }, + { label: 'Thursday', value: 1050 }, + { label: 'Friday', value: 1350 }, + { label: 'Saturday', value: 6 }, + { label: 'Sunday', value: 7 }, + ]); + }); + }); + + describe('valueToColor', () => { + it('should return the correct color for the given value', () => { + expect(valueToColor(1)).toEqual('rgb(0, 255, 0)'); + expect(valueToColor(2)).toEqual('rgb(64, 191, 0)'); // Use toEqual for string comparison + expect(valueToColor(3)).toEqual('rgb(128, 128, 0)'); + expect(valueToColor(4)).toEqual('rgb(191, 64, 0)'); + expect(valueToColor(5)).toEqual('rgb(255, 0, 0)'); + }); + + it('should clamp the value to the valid range', () => { + expect(valueToColor(0)).toEqual('rgb(0, 255, 0)'); + expect(valueToColor(6)).toEqual('rgb(255, 0, 0)'); + }); + }); + + // describe('getFormattedPredictionData', () => { + // it('should format prediction data correctly', async () => { + // const mockPredictions: Prediction[] = [ + // { + // Date: '2023-08-07', + // Day_of_Week: 1, + // Day_of_month: 7, + // Is_Weekend: false, + // Month: 8, + // Predicted_Attendance_Level: 'High', + // Predicted_Class: 3, + // }, + // { + // Date: '2023-08-08', + // Day_of_Week: 2, + // Day_of_month: 8, + // Is_Weekend: false, + // Month: 8, + // Predicted_Attendance_Level: 'Medium', + // Predicted_Class: 2, + // }, + // ]; + + // (getExtractedPredictions as jest.Mock).mockResolvedValue( + // mockPredictions.map((prediction) => ({ + // Date: prediction.Date, + // Day_of_week: prediction.Day_of_Week, + // Predicted_Attendance_Level: prediction.Predicted_Attendance_Level, + // Predicted_Class: prediction.Predicted_Class, + // })) + // ); + + // const result = await getFormattedPredictionData(); + + // expect(getExtractedPredictions).toHaveBeenCalled(); + // expect(result).toEqual([ + // { value: 4, label: 'Mon' }, + // { value: 3, label: 'Tue' }, + // ]); + // }); + + // it('should return an empty array when no predictions are received', async () => { + // (getExtractedPredictions as jest.Mock).mockResolvedValue([]); + + // const result = await getFormattedPredictionData(); + + // expect(getExtractedPredictions).toHaveBeenCalled(); + // expect(result).toEqual([]); + // }); + // }); + + // describe('getFormattedDailyPredictionData', () => { + // it('should format daily prediction data correctly', async () => { + // const mockPrediction: Prediction = { + // Date: '2023-08-07', + // Day_of_Week: 1, + // Day_of_month: 7, + // Is_Weekend: false, + // Month: 8, + // Predicted_Attendance_Level: 'High', + // Predicted_Class: 3, + // }; + + // (getExtractedDailyPrediction as jest.Mock).mockResolvedValue({ + // Date: mockPrediction.Date, + // Day_of_week: mockPrediction.Day_of_Week, + // Predicted_Attendance_Level: mockPrediction.Predicted_Attendance_Level, + // Predicted_Class: mockPrediction.Predicted_Class, + // }); + + // const result = await getFormattedDailyPredictionData(); + + // expect(getExtractedDailyPrediction).toHaveBeenCalled(); + // expect(result).toEqual({ + // date: '8/7/2023', + // class: 4, + // day: 'Mon', + // attendance: 'High', + // }); + // }); + + // it('should return null when no predictions are received', async () => { + // (getExtractedDailyPrediction as jest.Mock).mockResolvedValue(null); + + // const result = await getFormattedDailyPredictionData(); + + // expect(getExtractedDailyPrediction).toHaveBeenCalled(); + // expect(result).toBeNull(); + // }); + // }); + + + }); \ No newline at end of file diff --git a/frontend/occupi-mobile4/utils/__tests__/user-test.tsx b/frontend/occupi-mobile4/utils/__tests__/user-test.tsx index 8c4d81ab..6d6e2dca 100644 --- a/frontend/occupi-mobile4/utils/__tests__/user-test.tsx +++ b/frontend/occupi-mobile4/utils/__tests__/user-test.tsx @@ -1,127 +1,305 @@ +import * as user from '../user'; +import * as apiServices from '../../services/apiservices'; +import * as secureStore from '../../services/securestore'; +import { router } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; -import { - storeUserData, - storeToken, - storeUserEmail, - setState, - storeNotificationSettings, - storeSecuritySettings, - getUserData, - getToken, - getUserEmail, - getCurrentRoom, - deleteUserData, - deleteToken, - deleteUserEmail, - deleteNotificationSettings, - deleteSecuritySettings, - deleteAllData -} from '../../services/securestore'; +jest.mock('../../services/apiservices'); +jest.mock('../../services/securestore'); +jest.mock('expo-router', () => ({ router: { replace: jest.fn() } })); jest.mock('expo-secure-store'); -describe('Secure Store Functions', () => { +describe('user utils', () => { beforeEach(() => { jest.clearAllMocks(); + console.log = jest.fn(); + console.error = jest.fn(); }); - test('storeUserData stores user data', async () => { - const userData = JSON.stringify({ name: 'John Doe', email: 'john@example.com' }); - await storeUserData(userData); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('UserData', userData); - }); + describe('fetchUserDetails', () => { + it('should fetch and store user details on successful response', async () => { + const mockResponse = { status: 200, data: { name: 'John Doe' } }; + (apiServices.getUserDetails as jest.Mock).mockResolvedValue(mockResponse); - test('storeToken stores token', async () => { - const token = 'abc123'; - await storeToken(token); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Token', token); - }); + await user.fetchUserDetails('test@example.com', 'token'); - test('storeUserEmail stores email', async () => { - const email = 'john@example.com'; - await storeUserEmail(email); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Email', email); - }); + expect(apiServices.getUserDetails).toHaveBeenCalledWith('test@example.com', 'token'); + expect(secureStore.storeUserData).toHaveBeenCalledWith(JSON.stringify(mockResponse.data)); + }); - test('setState stores app state', async () => { - const state = 'active'; - await setState(state); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('AppState', state); - }); + it('should log error on failed response', async () => { + const mockResponse = { status: 400, message: 'Bad Request' }; + (apiServices.getUserDetails as jest.Mock).mockResolvedValue(mockResponse); - test('storeNotificationSettings stores notification settings', async () => { - const settings = JSON.stringify({ pushEnabled: true }); - await storeNotificationSettings(settings); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Notifications', settings); - }); + await user.fetchUserDetails('test@example.com', 'token'); - test('storeSecuritySettings stores security settings', async () => { - const settings = JSON.stringify({ twoFactor: true }); - await storeSecuritySettings(settings); - expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Security', settings); - }); + expect(console.log).toHaveBeenCalledWith(mockResponse); + }); - test('getUserData retrieves user data', async () => { - const userData = { name: 'John Doe', email: 'john@example.com' }; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify(userData)); - const result = await getUserData(); - expect(result).toEqual(userData); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('UserData'); - }); + it('should handle and log errors', async () => { + (apiServices.getUserDetails as jest.Mock).mockRejectedValue(new Error('Network error')); - test('getToken retrieves token', async () => { - const token = 'abc123'; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(token); - const result = await getToken(); - expect(result).toBe(token); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Token'); - }); + await user.fetchUserDetails('test@example.com', 'token'); - test('getUserEmail retrieves email', async () => { - const email = 'john@example.com'; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(email); - const result = await getUserEmail(); - expect(result).toBe(email); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email'); + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('getCurrentRoom retrieves current room', async () => { - const room = 'living-room'; - (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(room); - const result = await getCurrentRoom(); - expect(result).toBe(room); - expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom'); - }); + describe('fetchNotificationSettings', () => { + it('should fetch and store notification settings on successful response', async () => { + const mockResponse = { status: 200, data: { invites: true, bookingReminder: false } }; + (apiServices.getNotificationSettings as jest.Mock).mockResolvedValue(mockResponse); - test('deleteUserData deletes user data', async () => { - await deleteUserData(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData'); + await user.fetchNotificationSettings('test@example.com'); + + expect(apiServices.getNotificationSettings).toHaveBeenCalledWith('test@example.com'); + expect(secureStore.storeNotificationSettings).toHaveBeenCalledWith(JSON.stringify({ + invites: true, + bookingReminder: false + })); + }); + + it('should log error on failed response', async () => { + const mockResponse = { status: 400, message: 'Bad Request' }; + (apiServices.getNotificationSettings as jest.Mock).mockResolvedValue(mockResponse); + + await user.fetchNotificationSettings('test@example.com'); + + expect(console.log).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle and log errors', async () => { + (apiServices.getNotificationSettings as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.fetchNotificationSettings('test@example.com'); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('deleteToken deletes token', async () => { - await deleteToken(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Token'); + describe('fetchSecuritySettings', () => { + it('should fetch and store security settings on successful response', async () => { + const mockResponse = { status: 200, data: { mfa: true, forceLogout: false } }; + (apiServices.getSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + await user.fetchSecuritySettings('test@example.com'); + + expect(apiServices.getSecuritySettings).toHaveBeenCalledWith('test@example.com'); + expect(secureStore.storeSecuritySettings).toHaveBeenCalledWith(JSON.stringify({ + mfa: true, + forcelogout: false + })); + }); + + it('should log error on failed response', async () => { + const mockResponse = { status: 400, message: 'Bad Request' }; + (apiServices.getSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + await user.fetchSecuritySettings('test@example.com'); + + expect(console.log).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle and log errors', async () => { + (apiServices.getSecuritySettings as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.fetchSecuritySettings('test@example.com'); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('deleteUserEmail deletes email', async () => { - await deleteUserEmail(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Email'); + describe('updateSecurity', () => { + beforeEach(() => { + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify({ email: 'test@example.com' })); + }); + + it('should update security settings', async () => { + const mockResponse = { status: 200 }; + (apiServices.updateSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateSecurity('settings', { mfa: true, forceLogout: false }); + + expect(apiServices.updateSecuritySettings).toHaveBeenCalledWith({ + email: 'test@example.com', + mfa: true, + forceLogout: false + }); + expect(secureStore.storeSecuritySettings).toHaveBeenCalled(); + expect(router.replace).toHaveBeenCalledWith('/settings'); + expect(result).toBe('Settings updated successfully'); + expect(console.log).toHaveBeenCalledWith('settings response', mockResponse); + expect(console.log).toHaveBeenCalledWith({ mfa: true, forceLogout: false }); + }); + + it('should handle non-200 status responses for security settings', async () => { + const mockResponse = { status: 400, message: 'Bad Request' }; + (apiServices.updateSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateSecurity('settings', { mfa: true, forceLogout: false }); + + expect(result).toBe('Bad Request'); + }); + + it('should update password', async () => { + const mockResponse = { status: 200 }; + (apiServices.updateSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateSecurity('password', { + currentPassword: 'old', + newPassword: 'new', + newPasswordConfirm: 'new' + }); + + expect(apiServices.updateSecuritySettings).toHaveBeenCalledWith({ + email: 'test@example.com', + currentPassword: 'old', + newPassword: 'new', + newPasswordConfirm: 'new' + }); + expect(router.replace).toHaveBeenCalledWith('/set-security'); + expect(result).toBe('Successfully changed password'); + }); + + it('should handle non-200 status responses for password update', async () => { + const mockResponse = { status: 400, message: 'Invalid password' }; + (apiServices.updateSecuritySettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateSecurity('password', { + currentPassword: 'old', + newPassword: 'new', + newPasswordConfirm: 'new' + }); + + expect(result).toBe('Invalid password'); + }); + + it('should handle and log errors when updating security settings', async () => { + (apiServices.updateSecuritySettings as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.updateSecurity('settings', { mfa: true, forceLogout: false }); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); + + it('should handle and log errors when updating password', async () => { + (apiServices.updateSecuritySettings as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.updateSecurity('password', { + currentPassword: 'old', + newPassword: 'new', + newPasswordConfirm: 'new' + }); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('deleteNotificationSettings deletes notification settings', async () => { - await deleteNotificationSettings(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Notifications'); + describe('updateDetails', () => { + beforeEach(() => { + (SecureStore.getItemAsync as jest.Mock) + .mockResolvedValueOnce('test@example.com') + .mockResolvedValueOnce('verify_otp_register'); + }); + + it('should update user details', async () => { + const mockResponse = { status: 200 }; + (apiServices.updateUserDetails as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateDetails('John Doe', '1990-01-01', 'Male', '1234567890', 'He/Him'); + + expect(apiServices.updateUserDetails).toHaveBeenCalledWith({ + session_email: 'test@example.com', + name: 'John Doe', + dob: '1990-01-01T00:00:00.000Z', + gender: 'Male', + number: '1234567890', + pronouns: 'He/Him' + }); + expect(secureStore.setState).toHaveBeenCalledWith('logged_out'); + expect(router.replace).toHaveBeenCalledWith('/home'); + expect(result).toBe('Details updated successfully'); + expect(console.log).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle non-200 status responses', async () => { + const mockResponse = { status: 400, message: 'Invalid details' }; + (apiServices.updateUserDetails as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateDetails('John Doe', '1990-01-01', 'Male', '1234567890', 'He/Him'); + + expect(result).toBe('Invalid details'); + }); + + it('should handle and log errors when updating details', async () => { + (apiServices.updateUserDetails as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.updateDetails('John Doe', '1990-01-01', 'Male', '1234567890', 'He/Him'); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('deleteSecuritySettings deletes security settings', async () => { - await deleteSecuritySettings(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Security'); + describe('updateNotifications', () => { + beforeEach(() => { + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify({ email: 'test@example.com' })); + }); + + it('should update notification settings', async () => { + const mockResponse = { status: 200 }; + (apiServices.updateNotificationSettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateNotifications({ invites: true, bookingReminder: false }); + + expect(apiServices.updateNotificationSettings).toHaveBeenCalledWith({ + email: 'test@example.com', + invites: true, + bookingReminder: false + }); + expect(secureStore.storeNotificationSettings).toHaveBeenCalledWith(JSON.stringify({ + invites: true, + bookingReminder: false + })); + expect(router.replace).toHaveBeenCalledWith('/settings'); + expect(result).toBe('Settings updated successfully'); + expect(console.log).toHaveBeenCalledWith('settings response', mockResponse); + expect(console.log).toHaveBeenCalledWith({ invites: true, bookingReminder: false }); + }); + + it('should handle non-200 status responses', async () => { + const mockResponse = { status: 400, message: 'Invalid settings' }; + (apiServices.updateNotificationSettings as jest.Mock).mockResolvedValue(mockResponse); + + const result = await user.updateNotifications({ invites: true, bookingReminder: false }); + + expect(console.log).toHaveBeenCalledWith(mockResponse); + expect(result).toBe('Invalid settings'); + }); + + it('should handle and log errors when updating notification settings', async () => { + (apiServices.updateNotificationSettings as jest.Mock).mockRejectedValue(new Error('Network error')); + + await user.updateNotifications({ invites: true, bookingReminder: false }); + + expect(console.error).toHaveBeenCalledWith('Error:', expect.any(Error)); + }); }); - test('deleteAllData deletes all data', async () => { - await deleteAllData(); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData'); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Token'); - expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Email'); + describe('fetchUsername', () => { + it('should return the username from stored user data', async () => { + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify({ name: 'John Doe' })); + + const result = await user.fetchUsername(); + + expect(result).toBe('John Doe'); + }); + + it('should return undefined if no user data is stored', async () => { + (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null); + + const result = await user.fetchUsername(); + + expect(result).toBeUndefined(); + }); }); }); \ No newline at end of file diff --git a/frontend/occupi-mobile4/utils/bookings.ts b/frontend/occupi-mobile4/utils/bookings.ts index c3aa73f3..e34e17c4 100644 --- a/frontend/occupi-mobile4/utils/bookings.ts +++ b/frontend/occupi-mobile4/utils/bookings.ts @@ -8,21 +8,18 @@ import { sendPushNotification } from "./notifications"; export async function fetchUserBookings(): Promise { let email = await SecureStore.getItemAsync('Email'); try { - const response = await getUserBookings(email); - if (response.status === 200) { - // console.log('response', response.data); - return response.data; - // console.log(settings); - } - else { - console.log(response) - } + const response = await getUserBookings(email); + if (response.status === 200) { + return response.data; + } else { + console.log(response); return response.data as Booking[]; + } } catch (error) { - console.error('Error:', error); - throw error; // Add a throw statement to handle the error case + console.error('Error:', error); + throw error; } -} + } export async function fetchRooms(floorNo: string, roomName: string) { let body: ViewRoomsReq = {}; @@ -67,37 +64,39 @@ export async function fetchRooms(floorNo: string, roomName: string) { } } -export async function userBookRoom(attendees : string[], startTime : string, endTime : string) { +export async function userBookRoom(attendees: string[], startTime: string, endTime: string) { let roomstring = await SecureStore.getItemAsync("BookingInfo"); let email = await SecureStore.getItemAsync("Email"); - const room : Booking = JSON.parse(roomstring as string); - const body : BookRoomReq = { - roomName: room.roomName, - creator: email, - date: room.date+"T00:00:00.000+00:00", - start: room.date+"T"+startTime+":00.000+00:00", - end: room.date+"T"+endTime+":00.000+00:00", - floorNo: room.floorNo, - emails: attendees, - roomId: room.roomId - - } + const room: Booking = JSON.parse(roomstring as string); + const body: BookRoomReq = { + roomName: room.roomName, + creator: email, + date: room.date + "T00:00:00.000+00:00", + start: room.date + "T" + startTime + ":00.000+00:00", + end: room.date + "T" + endTime + ":00.000+00:00", + floorNo: room.floorNo, + emails: attendees, + roomId: room.roomId + }; console.log(body); try { - const response = await bookRoom(body); - if (response.status === 200) { - console.log('attendees',attendees) - const pushTokens : string[] = (await getExpoPushTokens(attendees)).data; - console.log(pushTokens); - sendPushNotification(pushTokens, 'Meeting Invite', `${email} has invited you to a meeting in ${room.roomName} on ${room.date}`) - return response.message; - } - return response.message; + const response = await bookRoom(body); + if (response.status === 'success') { + console.log('attendees', attendees); + const response = await getExpoPushTokens(attendees); + const pushTokens: string[] = response?.data || []; + console.log(pushTokens); + sendPushNotification(pushTokens, 'Meeting Invite', `${email} has invited you to a meeting in ${room.roomName} on ${room.date}`); + return response.message || 'Room booked successfully'; + } + return response.message || 'Booking failed'; } catch (error) { - console.error('Error:', error); - throw error; + console.error('Error:', error); + throw error; } -} + } + + export async function userCheckin() { let roomstring = await SecureStore.getItemAsync("CurrentRoom"); diff --git a/frontend/occupi-mobile4/utils/notifications.ts b/frontend/occupi-mobile4/utils/notifications.ts index 300c8393..3b6c061e 100644 --- a/frontend/occupi-mobile4/utils/notifications.ts +++ b/frontend/occupi-mobile4/utils/notifications.ts @@ -6,26 +6,38 @@ import * as SecureStore from 'expo-secure-store'; import { NotificationsReq } from '@/models/requests'; import { getNotifications } from '@/services/apiservices'; -Notifications.setNotificationHandler({ - handleNotification: async () => ({ - shouldShowAlert: true, - shouldPlaySound: false, - shouldSetBadge: false, - }), -}); +export function setupNotificationHandler() { + Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: false, + shouldSetBadge: false, + }), + }); +} + +setupNotificationHandler(); + +export async function retrievePushToken() { + if (!Device.isDevice) { + global.alert('Must use physical device for Push Notifications'); + return undefined; + } -export async function retrievePushToken(): Promise { const token = await registerForPushNotificationsAsync(); - // console.log(token); - return token as string; + return token; } -// retrievePushToken(); -// console.log('yurp'); - -async function registerForPushNotificationsAsync() { +export async function registerForPushNotificationsAsync() { let token; + if (!Device.isDevice) { + + + global.alert('Must use physical device for Push Notifications'); + return; + } + if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'default', @@ -46,9 +58,6 @@ async function registerForPushNotificationsAsync() { alert('Failed to get push token for push notification!'); return; } - // Learn more about projectId: - // https://docs.expo.dev/push-notifications/push-notifications-setup/#configure-projectid - // EAS projectId is used here. try { const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId; @@ -60,7 +69,6 @@ async function registerForPushNotificationsAsync() { projectId, }) ).data; - // console.log(token); } catch (e) { token = `${e}`; } @@ -71,7 +79,8 @@ async function registerForPushNotificationsAsync() { return token; } - export async function sendPushNotification(expoPushTokens, title: string, body: string) { + + export async function sendPushNotification(expoPushTokens: string[], title: string, body: string) { const messages = expoPushTokens.map(token => ({ to: token.expoPushToken, sound: 'default', diff --git a/frontend/occupi-mobile4/utils/occupancy.ts b/frontend/occupi-mobile4/utils/occupancy.ts index 520942b5..1b39aef1 100644 --- a/frontend/occupi-mobile4/utils/occupancy.ts +++ b/frontend/occupi-mobile4/utils/occupancy.ts @@ -129,15 +129,10 @@ export async function getFormattedPredictionData() { return []; } - // console.log(data.map((prediction: ExtractedPrediction) => ({ - // value: prediction.Predicted_Class, - // label: convertNumToDay(prediction.Day_of_week) - // }))); - return data.map((prediction: ExtractedPrediction) => ({ value: prediction.Predicted_Class + 1, label: convertNumToDay(prediction.Day_of_week) - })) + })); } function convertNumToDate(day: number): string { diff --git a/frontend/occupi-mobile4/utils/user.ts b/frontend/occupi-mobile4/utils/user.ts index 060af83d..930329cd 100644 --- a/frontend/occupi-mobile4/utils/user.ts +++ b/frontend/occupi-mobile4/utils/user.ts @@ -83,7 +83,7 @@ export async function updateSecurity(type: string, values: any) { return "Settings updated successfully" } else { - console.log(response) + // console.log(response) return response.message; } } catch (error) { @@ -103,7 +103,7 @@ export async function updateSecurity(type: string, values: any) { return "Successfully changed password" } else { - console.log(response); + // console.log(response); return response.message; } } catch (error) { @@ -130,17 +130,18 @@ export async function updateDetails(name: string, dob: string, gender: string, c if (state === "verify_otp_register") { setState("logged_out"); router.replace('/home'); - return; + return "Details updated successfully"; } router.replace('/settings') return "Details updated successfully" } else { - console.log(response) + // console.log(response) return response.message; } } catch (error) { console.error('Error:', error); + return 'Error occurred'; } } @@ -151,8 +152,8 @@ export async function updateNotifications(values: any) { try { const request = { email: email, - invites: values.mfa, - bookingReminder: values.forceLogout + invites: values.invites, // Changed from values.mfa + bookingReminder: values.bookingReminder // Changed from values.forceLogout } const response = await updateNotificationSettings(request); if (response.status === 200) {