diff --git a/src/server/CacheServiceHTTPServer.test.ts b/src/server/CacheServiceHTTPServer.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/server/NotificationServiceHTTPServer.test.ts b/src/server/NotificationServiceHTTPServer.test.ts new file mode 100644 index 0000000..c92d67a --- /dev/null +++ b/src/server/NotificationServiceHTTPServer.test.ts @@ -0,0 +1,74 @@ +import * as http from 'http'; +import { NotificationServiceHTTPServer } from './NotificationServiceHTTPServer'; + +describe('NotificationServiceHTTPServer', () => { + let server: NotificationServiceHTTPServer; + + beforeEach(() => { + server = new NotificationServiceHTTPServer(8080, console); + }); + + afterEach(() => { + // Clean up any resources used by the server + server.server.close(); + }); + + it('should set up the server and connect to the cache service', async () => { + await server.setupServer(8080); + + expect(server.server).toBeInstanceOf(http.Server); + expect(server.cacheService).toBeDefined(); + }); + + it('should handle incoming requests', async () => { + const request: http.IncomingMessage = {} as http.IncomingMessage; + const response: http.ServerResponse = {} as http.ServerResponse; + + await server.request_handler(request, response); + + // Add your assertions here + }); + + it('should handle notification POST requests', async () => { + const request: http.IncomingMessage = {} as http.IncomingMessage; + const response: http.ServerResponse = {} as http.ServerResponse; + + await server.handleNotificationPostRequest(request, response); + + // Add your assertions here + }); + + it('should handle client GET requests', async () => { + const request: http.IncomingMessage = {} as http.IncomingMessage; + const response: http.ServerResponse = {} as http.ServerResponse; + + await server.handleClientGetRequest(request, response); + + // Add your assertions here + }); + + it('should handle notification DELETE requests', async () => { + const request: http.IncomingMessage = {} as http.IncomingMessage; + const response: http.ServerResponse = {} as http.ServerResponse; + + await server.handleNotificationDeleteRequest(request, response); + + // Add your assertions here + }); + + it('should send a message to the WebSocket server', () => { + const message = 'Hello, WebSocket server!'; + + server.send_to_websocket_server(message); + + // Add your assertions here + }); + + it('should connect to the WebSocket server', async () => { + const wss_url = 'ws://localhost:8081'; + + await server.connect_to_websocket_server(wss_url); + + // Add your assertions here + }); +}); diff --git a/src/server/NotificationServiceHTTPServer.ts b/src/server/NotificationServiceHTTPServer.ts index dd6091a..e8f43dc 100644 --- a/src/server/NotificationServiceHTTPServer.ts +++ b/src/server/NotificationServiceHTTPServer.ts @@ -11,8 +11,8 @@ import { WebSocketServerHandler } from './WebSocketServerHandler'; * @class NotificationServiceHTTPServer */ export class NotificationServiceHTTPServer { - private readonly cacheService: CacheService; - private readonly server: http.Server; + public cacheService: CacheService; + public server: http.Server; public connection: any; public client: any; public logger: any; @@ -23,7 +23,6 @@ export class NotificationServiceHTTPServer { /** * Creates an instance of NotificationServiceHTTPServer. * @param {number} port - The port where the HTTP server will listen. - * @param {string[]} pod_url - The location of the Solid Pod from which the notifications are retrieved. * @param {*} logger - The logger object. * @memberof NotificationServiceHTTPServer */ @@ -40,7 +39,7 @@ export class NotificationServiceHTTPServer { this.websocket_handler = new WebSocketServerHandler(this.websocket_server); this.setupServer(port); this.connect_to_websocket_server('ws://localhost:8085/'); - this.websocket_handler.handle_communication(this.cacheService); + this.websocket_handler.handle_communication(); } /** @@ -49,7 +48,7 @@ export class NotificationServiceHTTPServer { * @param {number} port - The port where the HTTP server will listen. * @memberof NotificationServiceHTTPServer */ - private async setupServer(port: number) { + public async setupServer(port: number) { this.server.listen(port, () => { this.logger.info(`Server listening on port ${port}`); }); @@ -85,7 +84,7 @@ export class NotificationServiceHTTPServer { * @returns {Promise} * @memberof NotificationServiceHTTPServer */ - private async handleNotificationPostRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { + public async handleNotificationPostRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { let body = ''; request.on('data', (chunk) => { body += chunk.toString(); @@ -134,7 +133,7 @@ export class NotificationServiceHTTPServer { * @returns {Promise} * @memberof NotificationServiceHTTPServer */ - private async handleClientGetRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { + public async handleClientGetRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { this.logger.info(`GET request received for ${request.url}`) console.log(`GET request received for ${request.url}`); const parsed_url = url.parse(request.url!, true); @@ -156,7 +155,7 @@ export class NotificationServiceHTTPServer { * @returns {Promise} - A promise which responses nothing. * @memberof NotificationServiceHTTPServer */ - private async handleNotificationDeleteRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { + public async handleNotificationDeleteRequest(request: http.IncomingMessage, response: http.ServerResponse): Promise { const parsed_url = url.parse(request.url!, true); const query_parameters = parsed_url.query; const event_time = query_parameters.event_time as string | undefined || 'Anonymous'; @@ -165,6 +164,11 @@ export class NotificationServiceHTTPServer { response.end('OK'); } + /** + * Sends a message to the WebSocket server. + * @param {string} message - The message to send. + * @memberof NotificationServiceHTTPServer + */ public send_to_websocket_server(message: string) { if (this.connection.connected) { this.connection.sendUTF(message); @@ -175,7 +179,11 @@ export class NotificationServiceHTTPServer { }); } } - + /** + * Connects to the WebSocket server. + * @param {string} wss_url - The URL of the WebSocket server. + * @memberof NotificationServiceHTTPServer + */ public async connect_to_websocket_server(wss_url: string) { this.client.connect(wss_url, 'solid-stream-notifications-aggregator'); this.client.on('connect', (connection: typeof websocket.connection) => { diff --git a/src/server/WebSocketServerHandler.test.ts b/src/server/WebSocketServerHandler.test.ts new file mode 100644 index 0000000..0e037d0 --- /dev/null +++ b/src/server/WebSocketServerHandler.test.ts @@ -0,0 +1,53 @@ +// import { WebSocketServerHandler } from './WebSocketServerHandler'; + +// describe('WebSocketServerHandler', () => { +// let websocketServerHandler: WebSocketServerHandler; + +// beforeEach(() => { +// // Create a mock WebSocket server +// const websocketServerMock: any = { +// on: jest.fn(), +// }; + +// websocketServerHandler = new WebSocketServerHandler(websocketServerMock); +// }); + +// it('should handle communication for the WebSocket server', async () => { +// // Mock WebSocket server events +// const connectCallback = websocketServerHandler.websocket_server.on.mock.calls[0][1]; +// const requestCallback = websocketServerHandler.websocket_server.on.mock.calls[1][1]; + +// // Mock WebSocket connection and message events +// const connectionMock: any = { +// on: jest.fn(), +// }; +// const messageMock: any = { +// type: 'utf8', +// utf8Data: JSON.stringify({ subscribe: ['stream1', 'stream2'] }), +// }; + +// // Call the handle_communication method +// await websocketServerHandler.handle_communication(); + +// // Simulate WebSocket server events +// connectCallback(); +// requestCallback({ accept: () => connectionMock }); + +// // Simulate WebSocket connection message event +// connectionMock.on.mock.calls[0][1](messageMock); + +// // Expectations +// expect(websocketServerHandler.subscribe_notification.subscribe).toHaveBeenCalledTimes(2); +// expect(websocketServerHandler.set_connections).toHaveBeenCalledTimes(2); +// expect(connectionMock.send).toHaveBeenCalledTimes(1); +// }); + +// it('should set connections for the WebSocket server', () => { +// const subscribedStream = 'stream1'; +// const connectionMock: any = {}; + +// websocketServerHandler.set_connections(subscribedStream, connectionMock); + +// expect(websocketServerHandler.websocket_connections.get(subscribedStream)).toBe(connectionMock); +// }); +// }); \ No newline at end of file diff --git a/src/server/WebSocketServerHandler.ts b/src/server/WebSocketServerHandler.ts index 007e7e3..9fc5198 100644 --- a/src/server/WebSocketServerHandler.ts +++ b/src/server/WebSocketServerHandler.ts @@ -1,53 +1,84 @@ import * as WebSocket from 'websocket'; -import { CacheService } from '../service/CacheService'; import { SubscribeNotification } from '../service/SubscribeNotification'; - +/** + * This class is used to handle the WebSocket server. + * @class WebSocketServerHandler + */ export class WebSocketServerHandler { public websocket_server: any; - public websocket_connections: Map; + public websocket_connections: Map; public subscribe_notification: SubscribeNotification; - - + /** + * Creates an instance of WebSocketServerHandler. + * @param {WebSocket.server} websocket_server - The WebSocket server. + * @memberof WebSocketServerHandler + */ constructor(websocket_server: WebSocket.server) { this.websocket_server = websocket_server; - this.websocket_connections = new Map(); + this.websocket_connections = new Map(); this.subscribe_notification = new SubscribeNotification(); } - - public async handle_communication(cache_service: CacheService) { + /** + * Handles the communication for the WebSocket server, including subscribing to the notification server and sending messages to the client. + * @memberof WebSocketServerHandler + */ + public async handle_communication() { console.log(`Handling the communication for the WebSocket server.`); - this.websocket_server.on('connect', (connection: any) => { - console.log(`Connection received from the client with address: ${connection.remoteAddress}`); + this.websocket_server.on('connect', () => { + console.log(`Connected to the WebSocket server.`); }); this.websocket_server.on('request', (request: any) => { const connection = request.accept('solid-stream-notifications-aggregator', request.origin); - connection.on('message', (message: any) => { + connection.on('message', async (message: any) => { if (message.type === 'utf8') { const message_utf8 = message.utf8Data; const ws_message = JSON.parse(message_utf8); if (Object.keys(ws_message).includes('subscribe')) { - console.log(`Received a subscribe message from the client.`); - let stream_to_subscribe = ws_message.subscribe; - for (let stream of stream_to_subscribe){ - this.subscribe_notification.subscribe(stream); - console.log(`Subscribed to the stream: ${stream}`); - this.set_connections(stream, connection); + const streams_to_subscribe = ws_message.subscribe; + for (const stream_to_subscribe of streams_to_subscribe){ + this.set_connections(stream_to_subscribe, connection); } } - else if (Object.keys(ws_message).includes('event')) { - let connection = this.websocket_connections.get(ws_message.stream); - if (connection !== undefined) { - connection.send(JSON.stringify(ws_message)); + else if (Object.keys(ws_message).includes('event')){ + console.log(`Received an event message from the notification server.`); + for (const [stream, connection] of this.websocket_connections){ + if(ws_message.stream === stream){ + for(const conn of connection){ + conn.send(JSON.stringify(ws_message)); + } + } } } } }); + connection.on('close', (reasonCode: number, description: string) => { + console.log(`Peer ${connection.remoteAddress} disconnected.`); + console.log(`Reason code: ${reasonCode}`); + console.log(`Description: ${description}`); + + }); }); } - + /** + * Sets the connections for the WebSocket server's Map. + * @param {string} subscribed_stream - The stream to subscribe to. + * @param {WebSocket} connection - The WebSocket connection. + * @memberof WebSocketServerHandler + */ public set_connections(subscribed_stream: string, connection: WebSocket): void { - this.websocket_connections.set(subscribed_stream, connection); + if (!this.websocket_connections.has(subscribed_stream)) { + this.subscribe_notification.subscribe(subscribed_stream); + this.websocket_connections.set(subscribed_stream, [connection]); + } + else { + const connections = this.websocket_connections.get(subscribed_stream); + if (connections !== undefined) { + connections.push(connection); + this.websocket_connections.set(subscribed_stream, connections); + } + + } } } \ No newline at end of file diff --git a/src/service/CacheService.test.ts b/src/service/CacheService.test.ts index cf47801..5942f47 100644 --- a/src/service/CacheService.test.ts +++ b/src/service/CacheService.test.ts @@ -29,15 +29,4 @@ describe('CacheService', () => { expect(is_disconnected).toBe(true); }); - it('should_describe_the_cache', async () => { - const status = await cacheService.get_status(); - expect(status).toBe('wait'); - }); - - it('test_number_of_runs', async () => { - await cacheService.set('key', 'value'); - for (let i = 0; i < 10000; i++) { - console.log(await cacheService.get('key')); - } - }); }); \ No newline at end of file diff --git a/src/service/CacheService.ts b/src/service/CacheService.ts index 7bb119f..efea512 100644 --- a/src/service/CacheService.ts +++ b/src/service/CacheService.ts @@ -118,7 +118,13 @@ export class CacheService { } - async get_recent_key_value(ldes_stream: string): Promise<{ key: string, value: string }> { + /** + * Get the most recent key-value pair from the Redis cache. + * @param {string} ldes_stream - The LDES stream to get the most recent key-value pair for. + * @returns {Promise<{ key: string, value: string }>} - A promise that resolves to an object containing the most recent key-value pair in the cache. + * @memberof CacheService + */ + async get_recent_key_value(ldes_stream: string): Promise<{ key: string, value: string } | undefined> { try { // example of a key is, stream:http://localhost:3000/aggregation_pod/aggregation/:1710250027636 const pattern = `stream:${ldes_stream}:*`; @@ -129,25 +135,33 @@ export class CacheService { const b_timestamp = parseInt(b.split(":")[1]); return b_timestamp - a_timestamp; }); - - console.log(sortedKeys); - const most_recent_key = sortedKeys[sortedKeys.length - 1]; const value = await this.client.get(most_recent_key); - - return { key: most_recent_key, value: value }; + if (value !== null) { + return { key: most_recent_key, value: value }; + } } finally { - await this.client.quit(); + // await this.client.quit(); } } - - + + /** + * Sets the time to live for a key in the Redis cache. + * @param {string} key - The key to set the time to live for. + * @param {number} time_to_live - The time to live for the key in seconds. + * @memberof CacheService + */ async setTimeToLive(key: string, time_to_live: number) { // setting the time to live for the key in seconds. await this.client.expire(key, time_to_live); } + + /** + * Clears the Redis cache. + * @memberof CacheService + */ async clearCache() { await this.client.flushall(); } diff --git a/src/service/SubscribeNotification.test.ts b/src/service/SubscribeNotification.test.ts index e112a99..61f986e 100644 --- a/src/service/SubscribeNotification.test.ts +++ b/src/service/SubscribeNotification.test.ts @@ -1,91 +1,63 @@ -// import { SubscribeNotification } from './SubscribeNotification'; -// import { extract_ldp_inbox, extract_subscription_server } from '../utils/Util'; - -// jest.mock('../utils/Util', () => ({ -// extract_ldp_inbox: jest.fn(), -// extract_subscription_server: jest.fn(), -// })); - -// describe('SubscribeNotification', () => { -// afterEach(() => { -// jest.clearAllMocks(); -// }); - -// it('should subscribe successfully', async () => { -// const streams = ['stream1', 'stream2']; -// const subscribeNotification = new SubscribeNotification(streams); - -// (extract_ldp_inbox as jest.Mock).mockResolvedValueOnce('inbox1'); -// (extract_subscription_server as jest.Mock).mockResolvedValueOnce({ -// location: 'http://subscription-server1', -// }); - -// const fetchMock = jest.fn().mockResolvedValueOnce({ status: 200 }); -// global.fetch = fetchMock; - -// const result = await subscribeNotification.subscribe(); - -// expect(result).toBe(true); -// expect(extract_ldp_inbox).toHaveBeenCalledWith('stream1'); -// expect(extract_subscription_server).toHaveBeenCalledWith('inbox1'); -// expect(fetchMock).toHaveBeenCalledWith('http://subscription-server1', { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/ld+json', -// }, -// body: JSON.stringify({ -// '@context': ['https://www.w3.org/ns/solid/notification/v1'], -// type: 'http://www.w3.org/ns/solid/notifications#WebhookChannel2023', -// topic: 'inbox1', -// sendTo: 'http://localhost:8085/', -// }), -// }); -// }); - -// it('should throw an error if subscription server is undefined', async () => { -// const streams = ['stream1']; -// const subscribeNotification = new SubscribeNotification(streams); - -// (extract_ldp_inbox as jest.Mock).mockResolvedValueOnce('inbox1'); -// (extract_subscription_server as jest.Mock).mockResolvedValueOnce(undefined); - -// await expect(subscribeNotification.subscribe()).rejects.toThrow( -// 'Subscription server is undefined.' -// ); - -// expect(extract_ldp_inbox).toHaveBeenCalledWith('stream1'); -// expect(extract_subscription_server).toHaveBeenCalledWith('inbox1'); -// }); - -// it('should throw an error if subscription to the notification server fails', async () => { -// const streams = ['stream1']; -// const subscribeNotification = new SubscribeNotification(streams); - -// (extract_ldp_inbox as jest.Mock).mockResolvedValueOnce('inbox1'); -// (extract_subscription_server as jest.Mock).mockResolvedValueOnce({ -// location: 'http://subscription-server1', -// }); - -// const fetchMock = jest.fn().mockResolvedValueOnce({ status: 500 }); -// global.fetch = fetchMock; - -// await expect(subscribeNotification.subscribe()).rejects.toThrow( -// 'The subscription to the notification server failed.' -// ); - -// expect(extract_ldp_inbox).toHaveBeenCalledWith('stream1'); -// expect(extract_subscription_server).toHaveBeenCalledWith('inbox1'); -// expect(fetchMock).toHaveBeenCalledWith('http://subscription-server1', { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/ld+json', -// }, -// body: JSON.stringify({ -// '@context': ['https://www.w3.org/ns/solid/notification/v1'], -// type: 'http://www.w3.org/ns/solid/notifications#WebhookChannel2023', -// topic: 'inbox1', -// sendTo: 'http://localhost:8085/', -// }), -// }); -// }); -// }); +import { SubscribeNotification } from './SubscribeNotification'; +import { extract_ldp_inbox, extract_subscription_server } from '../utils/Util'; + +jest.mock('../utils/Util', () => ({ + extract_ldp_inbox: jest.fn(), + extract_subscription_server: jest.fn() +})); + +describe('SubscribeNotification', () => { + describe('subscribe method', () => { + it('should subscribe to the notification server successfully', async () => { + // Mocking dependencies + const ldes_stream = 'example_ldes_stream'; + const inbox = 'example_inbox'; + const subscriptionServer = { location: 'example_location' }; + (extract_ldp_inbox as jest.Mock).mockResolvedValue(inbox); + (extract_subscription_server as jest.Mock).mockResolvedValue(subscriptionServer); + + // Mocking fetch + global.fetch = jest.fn().mockResolvedValue({ status: 200 }); + + const subscribeNotification = new SubscribeNotification(); + const result = await subscribeNotification.subscribe(ldes_stream); + + expect(result).toBe(true); + expect(extract_ldp_inbox).toHaveBeenCalledWith(ldes_stream); + expect(extract_subscription_server).toHaveBeenCalledWith(inbox); + expect(fetch).toHaveBeenCalledWith(subscriptionServer.location, expect.any(Object)); + }); + + it('should throw an error if subscription server is undefined', async () => { + // Mocking dependencies + const ldes_stream = 'example_ldes_stream'; + (extract_ldp_inbox as jest.Mock).mockResolvedValue('example_inbox'); + (extract_subscription_server as jest.Mock).mockResolvedValue(undefined); + + const subscribeNotification = new SubscribeNotification(); + + await expect(subscribeNotification.subscribe(ldes_stream)).rejects.toThrowError('Subscription server is undefined.'); + expect(extract_ldp_inbox).toHaveBeenCalledWith(ldes_stream); + expect(extract_subscription_server).toHaveBeenCalledWith('example_inbox'); + }); + + it('should throw an error if subscription to the notification server fails', async () => { + // Mocking dependencies + const ldes_stream = 'example_ldes_stream'; + const inbox = 'example_inbox'; + const subscriptionServer = { location: 'example_location' }; + (extract_ldp_inbox as jest.Mock).mockResolvedValue(inbox); + (extract_subscription_server as jest.Mock).mockResolvedValue(subscriptionServer); + + // Mocking fetch + global.fetch = jest.fn().mockResolvedValue({ status: 400 }); + + const subscribeNotification = new SubscribeNotification(); + + await expect(subscribeNotification.subscribe(ldes_stream)).rejects.toThrowError('The subscription to the notification server failed.'); + expect(extract_ldp_inbox).toHaveBeenCalledWith(ldes_stream); + expect(extract_subscription_server).toHaveBeenCalledWith(inbox); + expect(fetch).toHaveBeenCalledWith(subscriptionServer.location, expect.any(Object)); + }); + }); +}); diff --git a/src/service/SubscribeNotification.ts b/src/service/SubscribeNotification.ts index a933de7..9189045 100644 --- a/src/service/SubscribeNotification.ts +++ b/src/service/SubscribeNotification.ts @@ -1,4 +1,5 @@ import { extract_ldp_inbox, extract_subscription_server } from "../utils/Util"; +import * as WebSocket from 'websocket'; /** * This class is used to subscribe to the notification server for real-time notifications. @@ -7,7 +8,6 @@ import { extract_ldp_inbox, extract_subscription_server } from "../utils/Util"; export class SubscribeNotification { /** * Creates an instance of SubscribeNotification. - * @param {string[]} streams - An array of LDES streams to subscribe to, for real-time notifications. * @memberof SubscribeNotification */ constructor() { @@ -15,34 +15,61 @@ export class SubscribeNotification { /** * Subscribes to the notification server for each LDES stream in the constructor, using the inbox and subscription server. + * @param {string} ldes_stream - The LDES stream to subscribe to. * @returns {(Promise)} - Returns a promise with a boolean or undefined. If the subscription is successful, it returns true. If the subscription fails, it throws an error. * @memberof SubscribeNotification */ public async subscribe(ldes_stream: string): Promise { - const inbox = await extract_ldp_inbox(ldes_stream) as string; - const subscription_server = await extract_subscription_server(inbox); - if (subscription_server === undefined) { - throw new Error("Subscription server is undefined."); - } else { - const response = await fetch(subscription_server.location, { - method: 'POST', - headers: { - 'Content-Type': 'application/ld+json' - }, - body: JSON.stringify({ - "@context": ["https://www.w3.org/ns/solid/notification/v1"], - "type": "http://www.w3.org/ns/solid/notifications#WebhookChannel2023", - "topic": inbox, - "sendTo": "http://localhost:8085/" - }) - }); - if (response.status === 200) { - return true; - } - else { - throw new Error("The subscription to the notification server failed."); - } + const inbox = await extract_ldp_inbox(ldes_stream) as string; + const subscription_server = await extract_subscription_server(inbox); + if (subscription_server === undefined) { + throw new Error("Subscription server is undefined."); + } else { + const response = await fetch(subscription_server.location, { + method: 'POST', + headers: { + 'Content-Type': 'application/ld+json' + }, + body: JSON.stringify({ + "@context": ["https://www.w3.org/ns/solid/notification/v1"], + "type": "http://www.w3.org/ns/solid/notifications#WebhookChannel2023", + "topic": inbox, + "sendTo": "http://localhost:8085/" + }) + }); + if (response.status === 200) { + return true; } - + else { + throw new Error("The subscription to the notification server failed."); + } + } + + } + /** + * Checks if the LDES stream is already subscribed to the notification server. + * @param {string} ldes_stream - The LDES stream to check if it is already subscribed. + * @param {Map} websocket_connections - The WebSocket connections. + * @returns {Promise} - Returns a promise with a boolean. If the LDES stream is already subscribed, it returns true. If the LDES stream is not subscribed, it returns false. + * @memberof SubscribeNotification + */ + public async check_if_aleady_subscribed(ldes_stream: string, websocket_connections: Map): Promise { + if (websocket_connections.has(ldes_stream)) { + return true; + } + else { + return false; + } + } + + /** + * Sets the connections for the WebSocket server's Map. + * @param {string} ldes_stream - The LDES stream to subscribe to. + * @param {Map} websocket_connections - The WebSocket connections. + * @returns {(Promise)} - Returns a promise with a WebSocket or undefined. If the connection is set, it returns the WebSocket connection. If the connection is not set, it returns undefined. + * @memberof SubscribeNotification + */ + public async get_connection(ldes_stream: string, websocket_connections: Map): Promise { + return websocket_connections.get(ldes_stream); } } diff --git a/src/utils/FetchUtil.test.ts b/src/utils/FetchUtil.test.ts index e69de29..85608e0 100644 --- a/src/utils/FetchUtil.test.ts +++ b/src/utils/FetchUtil.test.ts @@ -0,0 +1,73 @@ +import { getMembers } from './FetchUtil'; // Assuming the module file is named 'yourModule.js' +import { Readable } from 'stream'; + +describe('getMembers function', () => { + it('should return a readable stream of members within the specified date range', async () => { + // Mocking CacheService + const mockDatabase = { + '1646822400000': 'Member 1', // March 9, 2022 + '1646908800000': 'Member 2', // March 10, 2022 + '1646995200000': 'Member 3', // March 11, 2022 + '1647081600000': 'Member 4', // March 12, 2022 + }; + const mockCacheService = { + read_whole_database: jest.fn().mockResolvedValue(mockDatabase) + }; + jest.mock('../service/CacheService', () => ({ + CacheService: jest.fn(() => mockCacheService) + })); + + // Test parameters + const from = new Date('2022-03-10T00:00:00Z'); + const until = new Date('2022-03-12T23:59:59Z'); + + // Call the function + const result = await getMembers({ from, until }); + + // Validate result + expect(result instanceof Readable).toBe(true); + + // Read stream and validate members + const members: any[] = []; + result.on('data', data => members.push(data)); + result.on('end', () => { + expect(members).toHaveLength(3); + expect(members).toContain('Member 2'); + expect(members).toContain('Member 3'); + expect(members).toContain('Member 4'); + }); + }); + + it('should return an empty stream if no members found within the specified date range', async () => { + // Mocking CacheService + const mockDatabase = { + '1646822400000': 'Member 1', // March 9, 2022 + '1646908800000': 'Member 2', // March 10, 2022 + '1646995200000': 'Member 3', // March 11, 2022 + '1647081600000': 'Member 4', // March 12, 2022 + }; + const mockCacheService = { + read_whole_database: jest.fn().mockResolvedValue(mockDatabase) + }; + jest.mock('../service/CacheService', () => ({ + CacheService: jest.fn(() => mockCacheService) + })); + + // Test parameters + const from = new Date('2022-03-13T00:00:00Z'); + const until = new Date('2022-03-14T23:59:59Z'); + + // Call the function + const result = await getMembers({ from, until }); + + // Validate result + expect(result instanceof Readable).toBe(true); + + // Read stream and validate members + const members: any[] = []; + result.on('data', data => members.push(data)); + result.on('end', () => { + expect(members).toHaveLength(0); + }); + }); +});