diff --git a/apps/mobile/src/api/__tests__/notifications.test.ts b/apps/mobile/src/api/__tests__/notifications.test.ts new file mode 100644 index 0000000..220afe4 --- /dev/null +++ b/apps/mobile/src/api/__tests__/notifications.test.ts @@ -0,0 +1,120 @@ +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { + deleteNotification, + fetchNotifications, + fetchUnreadCount, + markAllAsRead, + markAsRead, + savePushToken, +} from "../notifications"; +import { apiClient, withAuth } from "../client"; + +jest.mock("../client", () => ({ + apiClient: { + get: jest.fn(), + put: jest.fn(), + post: jest.fn(), + delete: jest.fn(), + }, + withAuth: jest.fn((token?: string | null) => + token ? { headers: { Authorization: `Bearer ${token}` } } : {}, + ), +})); + +describe("notifications api", () => { + const getMock = apiClient.get as any; + const putMock = apiClient.put as any; + const postMock = apiClient.post as any; + const deleteMock = apiClient.delete as any; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("returns notifications and normalizes createdAt", async () => { + getMock.mockResolvedValue({ + data: { + success: true, + data: [ + { + id: "n_1", + userId: "u_1", + title: "Hi", + message: "Welcome", + type: "system", + read: false, + createdAt: "2026-03-29T10:00:00.000Z", + }, + ], + }, + }); + + const result = await fetchNotifications("token_1"); + + expect(result).toHaveLength(1); + expect(result[0].createdAt).toBeInstanceOf(Date); + expect(withAuth).toHaveBeenCalledWith("token_1"); + }); + + it("returns unread count from response wrapper", async () => { + getMock.mockResolvedValue({ + data: { + success: true, + data: { count: 3 }, + }, + }); + + const count = await fetchUnreadCount(null); + expect(count).toBe(3); + }); + + it("marks notification as read", async () => { + putMock.mockResolvedValue({ + data: { + success: true, + data: { + id: "n_1", + userId: "u_1", + title: "Hi", + message: "Welcome", + type: "system", + read: true, + createdAt: "2026-03-29T10:00:00.000Z", + }, + }, + }); + + const result = await markAsRead("n_1", "token_1"); + + expect(result.read).toBe(true); + expect(putMock).toHaveBeenCalledWith( + "/api/notifications/n_1", + {}, + expect.any(Object), + ); + }); + + it("calls mark-all, delete and save-token endpoints", async () => { + postMock.mockResolvedValue({}); + deleteMock.mockResolvedValue({}); + + await markAllAsRead("token_1"); + await deleteNotification("n_2", "token_1"); + await savePushToken("expo-token", "android", "token_1"); + + expect(postMock).toHaveBeenCalledWith( + "/api/notifications/mark-all-read", + {}, + expect.any(Object), + ); + expect(deleteMock).toHaveBeenCalledWith( + "/api/notifications/n_2", + expect.any(Object), + ); + expect(postMock).toHaveBeenCalledWith( + "/api/notifications/save-token", + { expoPushToken: "expo-token", deviceType: "android" }, + expect.any(Object), + ); + }); +});