From 237e1712f52d97a4efcfc3f5865c1fc38b3ef2cd Mon Sep 17 00:00:00 2001 From: Michael Absolon Date: Sun, 7 Jul 2024 23:46:30 +0200 Subject: [PATCH] test(visualizator-be): Add unit tests for service methods --- apps/visualizator-be/package.json | 2 +- .../src/channels/channels.service.test.ts | 125 ++++++ .../src/messages/count-option.ts | 5 + .../src/messages/messages.resolver.ts | 3 +- .../src/messages/messages.service.test.ts | 413 ++++++++++++++++++ .../src/messages/messages.service.ts | 7 +- .../messages/models/message-count.model.ts | 7 +- 7 files changed, 548 insertions(+), 14 deletions(-) create mode 100644 apps/visualizator-be/src/channels/channels.service.test.ts create mode 100644 apps/visualizator-be/src/messages/count-option.ts create mode 100644 apps/visualizator-be/src/messages/messages.service.test.ts diff --git a/apps/visualizator-be/package.json b/apps/visualizator-be/package.json index 094ae1db..2fe0cf0c 100644 --- a/apps/visualizator-be/package.json +++ b/apps/visualizator-be/package.json @@ -60,7 +60,7 @@ "ts" ], "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", + "testRegex": ".*\\.test\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, diff --git a/apps/visualizator-be/src/channels/channels.service.test.ts b/apps/visualizator-be/src/channels/channels.service.test.ts new file mode 100644 index 00000000..7d463884 --- /dev/null +++ b/apps/visualizator-be/src/channels/channels.service.test.ts @@ -0,0 +1,125 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Channel } from './channel.entity'; +import { ChannelService } from './channels.service'; + +describe('ChannelService', () => { + let service: ChannelService; + let mockRepository: Partial, jest.Mock>>; + + beforeEach(async () => { + mockRepository = { + query: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ChannelService, + { + provide: getRepositoryToken(Channel), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(ChannelService); + }); + + describe('findAll', () => { + const startTime = 1633046400; + const endTime = 1633132800; + + it('should return an array of channels on successful fetch', async () => { + const expectedResponse = [ + { id: 1, senderId: 101, recipientId: 201, totalCount: 5 }, + { id: 2, senderId: 102, recipientId: 202, totalCount: 3 }, + ]; + + mockRepository.query.mockResolvedValue(expectedResponse); + + const result = await service.findAll(startTime, endTime); + + expect(result).toEqual( + expectedResponse.map((channel) => ({ + id: channel.id, + sender: channel.senderId, + recipient: channel.recipientId, + message_count: channel.totalCount, + })), + ); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + ]); + }); + + it('should return an empty array when no channels are found', async () => { + mockRepository.query.mockResolvedValue([]); + + const result = await service.findAll(startTime, endTime); + + expect(result).toEqual([]); + expect(mockRepository.query).toHaveBeenCalled(); + }); + + it('should throw an error when the query execution fails', async () => { + mockRepository.query.mockRejectedValue( + new Error('Query execution failed'), + ); + + await expect(service.findAll(startTime, endTime)).rejects.toThrow( + 'Query execution failed', + ); + }); + }); + + describe('findOne', () => { + const channelId = 1; + + it('should return a channel when it exists', async () => { + const expectedResponse = [ + { + id: channelId, + senderId: 101, + recipientId: 201, + totalCount: 5, + status: 'accepted', + }, + ]; + + mockRepository.query.mockResolvedValue(expectedResponse); + + const result = await service.findOne(channelId); + + expect(result).toEqual({ + id: channelId, + sender: expectedResponse[0].senderId, + recipient: expectedResponse[0].recipientId, + message_count: expectedResponse[0].totalCount, + status: expectedResponse[0].status, + }); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + channelId, + ]); + }); + + it('should throw an error when no channel is found', async () => { + mockRepository.query.mockResolvedValue([]); + + await expect(service.findOne(channelId)).rejects.toThrow( + `Channel with ID ${channelId} not found.`, + ); + }); + + it('should throw an error when the query execution fails', async () => { + mockRepository.query.mockRejectedValue( + new Error('Query execution failed'), + ); + + await expect(service.findOne(channelId)).rejects.toThrow( + 'Query execution failed', + ); + }); + }); +}); diff --git a/apps/visualizator-be/src/messages/count-option.ts b/apps/visualizator-be/src/messages/count-option.ts new file mode 100644 index 00000000..8b60ec21 --- /dev/null +++ b/apps/visualizator-be/src/messages/count-option.ts @@ -0,0 +1,5 @@ +export enum CountOption { + ORIGIN = 'origin_para_id', + DESTINATION = 'dest_para_id', + BOTH = 'both', +} diff --git a/apps/visualizator-be/src/messages/messages.resolver.ts b/apps/visualizator-be/src/messages/messages.resolver.ts index d0e604ce..036f0fe3 100644 --- a/apps/visualizator-be/src/messages/messages.resolver.ts +++ b/apps/visualizator-be/src/messages/messages.resolver.ts @@ -5,7 +5,8 @@ import { AccountXcmCountType } from './models/account-msg-count.model'; import { AssetCount } from './models/asset-count.model'; import { MessageCountByDay } from './models/message-count-by-day.model'; import { MessageCountByStatus } from './models/message-count-by-status.model'; -import { CountOption, MessageCount } from './models/message-count.model'; +import { MessageCount } from './models/message-count.model'; +import { CountOption } from './count-option'; @Resolver(() => Message) export class MessageResolver { diff --git a/apps/visualizator-be/src/messages/messages.service.test.ts b/apps/visualizator-be/src/messages/messages.service.test.ts new file mode 100644 index 00000000..d97414b7 --- /dev/null +++ b/apps/visualizator-be/src/messages/messages.service.test.ts @@ -0,0 +1,413 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Message } from './message.entity'; +import { MessageService } from './messages.service'; +import { CountOption } from './count-option'; + +describe('MessageService', () => { + let service: MessageService; + let mockRepository: Partial, jest.Mock>>; + + beforeEach(async () => { + mockRepository = { + count: jest.fn(), + createQueryBuilder: jest.fn(), + query: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MessageService, + { + provide: getRepositoryToken(Message), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(MessageService); + }); + + describe('countMessagesByStatus', () => { + const startTime = 1633046400; + const endTime = 1633132800; + const paraIds = [101, 102]; + + it('should return status counts for each paraId when paraIds are provided', async () => { + mockRepository.count.mockImplementation( + ({ where: { _origin_para_id, status } }) => + Promise.resolve(status === 'success' ? 3 : 1), + ); + + const results = await service.countMessagesByStatus( + paraIds, + startTime, + endTime, + ); + + expect(results).toEqual([ + { paraId: 101, success: 3, failed: 1 }, + { paraId: 102, success: 3, failed: 1 }, + ]); + expect(mockRepository.count).toHaveBeenCalledTimes(4); + expect(mockRepository.count).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + origin_para_id: 101, + status: 'success', + }), + }), + ); + expect(mockRepository.count).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + origin_para_id: 102, + status: 'failed', + }), + }), + ); + }); + + it('should return aggregated status counts when no paraIds are provided', async () => { + mockRepository.count.mockImplementation(({ where: { status } }) => + Promise.resolve(status === 'success' ? 10 : 5), + ); + + const results = await service.countMessagesByStatus( + [], + startTime, + endTime, + ); + + expect(results).toEqual([{ success: 10, failed: 5 }]); + expect(mockRepository.count).toHaveBeenCalledTimes(2); + expect(mockRepository.count).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ status: 'success' }), + }), + ); + }); + }); + + describe('countMessagesByDay', () => { + const startTime = 1633046400; + const endTime = 1633132800; + const paraIds = [101, 102]; + + it('should return message counts by day for each paraId', async () => { + mockRepository.createQueryBuilder.mockReturnValueOnce({ + select: jest.fn().mockReturnThis(), + addSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getRawMany: jest.fn().mockResolvedValue([ + { + paraId: 101, + date: '2023-09-01', + message_count_success: '3', + message_count_failed: '1', + }, + { + paraId: 101, + date: '2023-09-02', + message_count_success: '2', + message_count_failed: '0', + }, + { + paraId: 102, + date: '2023-09-01', + message_count_success: '5', + message_count_failed: '2', + }, + ]), + }); + + const results = await service.countMessagesByDay( + paraIds, + startTime, + endTime, + ); + + expect(results).toEqual([ + { + paraId: 101, + date: '2023-09-01', + messageCount: 4, + messageCountSuccess: 3, + messageCountFailed: 1, + }, + { + paraId: 101, + date: '2023-09-02', + messageCount: 2, + messageCountSuccess: 2, + messageCountFailed: 0, + }, + { + paraId: 102, + date: '2023-09-01', + messageCount: 7, + messageCountSuccess: 5, + messageCountFailed: 2, + }, + ]); + + expect(mockRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + }); + + it('should return message counts by day when no paraIds are provided', async () => { + mockRepository.createQueryBuilder.mockReturnValueOnce({ + select: jest.fn().mockReturnThis(), + addSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getRawMany: jest.fn().mockResolvedValue([ + { + date: '2023-09-01', + message_count_success: '8', + message_count_failed: '3', + }, + { + date: '2023-09-02', + message_count_success: '6', + message_count_failed: '1', + }, + ]), + }); + + const results = await service.countMessagesByDay([], startTime, endTime); + + expect(results).toEqual([ + { + date: '2023-09-01', + messageCount: 11, + messageCountSuccess: 8, + messageCountFailed: 3, + }, + { + date: '2023-09-02', + messageCount: 7, + messageCountSuccess: 6, + messageCountFailed: 1, + }, + ]); + + expect(mockRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + }); + }); + + describe('getTotalMessageCounts', () => { + const startTime = 1633046400; + const endTime = 1633132800; + const mockCounts = [ + { paraId: 101, totalCount: '10' }, + { paraId: 102, totalCount: '5' }, + ]; + + type MockQueryBuilder = { + select: jest.Mock; + addSelect: jest.Mock; + where: jest.Mock; + groupBy: jest.Mock; + getRawMany: jest.Mock; + addGroupBy: jest.Mock; + }; + + it.each([ + [CountOption.ORIGIN, 'message.origin_para_id', 'paraId'], + [CountOption.DESTINATION, 'message.dest_para_id', 'paraId'], + ])( + 'should return message counts for %s', + async (countBy, selectColumn, alias) => { + const mockQueryBuilder: MockQueryBuilder = { + select: jest.fn().mockReturnThis(), + addSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + getRawMany: jest.fn().mockResolvedValue(mockCounts), + addGroupBy: jest.fn().mockReturnThis(), + }; + mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); + + const results = await service.getTotalMessageCounts( + startTime, + endTime, + countBy, + ); + + expect(results).toEqual( + mockCounts.map((item) => ({ + paraId: item.paraId, + totalCount: parseInt(item.totalCount), + })), + ); + expect(mockRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(mockQueryBuilder.select).toHaveBeenCalledWith( + selectColumn, + alias, + ); + }, + ); + + it('should combine counts for both origin and destination', async () => { + const mockQueryBuilderOrigin: MockQueryBuilder = { + select: jest.fn().mockReturnThis(), + addSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + getRawMany: jest + .fn() + .mockResolvedValue([{ paraId: 101, totalCount: '5' }]), + addGroupBy: jest.fn().mockReturnThis(), + }; + const mockQueryBuilderDestination: MockQueryBuilder = { + select: jest.fn().mockReturnThis(), + addSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + getRawMany: jest.fn().mockResolvedValue([ + { paraId: 101, totalCount: '7' }, + { paraId: 102, totalCount: '3' }, + ]), + addGroupBy: jest.fn().mockReturnThis(), + }; + mockRepository.createQueryBuilder + .mockReturnValueOnce(mockQueryBuilderOrigin) + .mockReturnValueOnce(mockQueryBuilderDestination); + + const results = await service.getTotalMessageCounts( + startTime, + endTime, + CountOption.BOTH, + ); + + expect(results).toEqual([ + { paraId: 101, totalCount: 12 }, + { paraId: 102, totalCount: 3 }, + ]); + expect(mockRepository.createQueryBuilder).toHaveBeenCalledTimes(2); + }); + }); + + describe('countAssetsBySymbol', () => { + const startTime = 1633046400; + const endTime = 1633132800; + const paraIds = [101, 102]; + + it('should return asset counts by symbol for each paraId when paraIds are provided', async () => { + const mockResult = [ + { origin_para_id: 101, symbol: 'GOLD', count: '3' }, + { origin_para_id: 102, symbol: 'SILVER', count: '5' }, + ]; + mockRepository.query.mockResolvedValue(mockResult); + + const results = await service.countAssetsBySymbol( + paraIds, + startTime, + endTime, + ); + + expect(results).toEqual([ + { paraId: 101, symbol: 'GOLD', count: 3 }, + { paraId: 102, symbol: 'SILVER', count: 5 }, + ]); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + paraIds, + ]); + }); + + it('should return asset counts by symbol when no paraIds are provided', async () => { + const mockResult = [ + { symbol: 'GOLD', count: '10' }, + { symbol: 'SILVER', count: '7' }, + ]; + mockRepository.query.mockResolvedValue(mockResult); + + const results = await service.countAssetsBySymbol([], startTime, endTime); + + expect(results).toEqual([ + { symbol: 'GOLD', count: 10 }, + { symbol: 'SILVER', count: 7 }, + ]); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + ]); + }); + }); + + describe('getAccountXcmCounts', () => { + const startTime = 1633046400; + const endTime = 1633132800; + const paraIds = [101, 102]; + const threshold = 5; + + it('should return account message counts when paraIds are provided', async () => { + const mockResult = [ + { from_account_id: 'account1', message_count: '6' }, + { from_account_id: 'account2', message_count: '7' }, + ]; + mockRepository.query.mockResolvedValue(mockResult); + + const results = await service.getAccountXcmCounts( + paraIds, + threshold, + startTime, + endTime, + ); + + expect(results).toEqual([ + { id: 'account1', count: 6 }, + { id: 'account2', count: 7 }, + ]); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + ...paraIds, + threshold, + ]); + }); + + it('should return account message counts when no paraIds are provided', async () => { + const mockResult = [{ from_account_id: 'account3', message_count: '8' }]; + mockRepository.query.mockResolvedValue(mockResult); + + const results = await service.getAccountXcmCounts( + [], + threshold, + startTime, + endTime, + ); + + expect(results).toEqual([{ id: 'account3', count: 8 }]); + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + threshold, + ]); + }); + + it('should handle exceptions', async () => { + mockRepository.query.mockRejectedValue(new Error('Database error')); + + await expect( + service.getAccountXcmCounts(paraIds, threshold, startTime, endTime), + ).rejects.toThrow('Database error'); + + expect(mockRepository.query).toHaveBeenCalledWith(expect.any(String), [ + startTime, + endTime, + ...paraIds, + threshold, + ]); + }); + }); +}); diff --git a/apps/visualizator-be/src/messages/messages.service.ts b/apps/visualizator-be/src/messages/messages.service.ts index 2490aade..b35e812b 100644 --- a/apps/visualizator-be/src/messages/messages.service.ts +++ b/apps/visualizator-be/src/messages/messages.service.ts @@ -12,12 +12,7 @@ import { MessageCountByDay } from './models/message-count-by-day.model'; import { MessageCountByStatus } from './models/message-count-by-status.model'; import { AssetCount } from './models/asset-count.model'; import { MessageCount } from './models/message-count.model'; - -enum CountOption { - ORIGIN = 'origin_para_id', - DESTINATION = 'dest_para_id', - BOTH = 'both', -} +import { CountOption } from './count-option'; @Injectable() export class MessageService { diff --git a/apps/visualizator-be/src/messages/models/message-count.model.ts b/apps/visualizator-be/src/messages/models/message-count.model.ts index 4a4b4f8b..18c1825a 100644 --- a/apps/visualizator-be/src/messages/models/message-count.model.ts +++ b/apps/visualizator-be/src/messages/models/message-count.model.ts @@ -1,10 +1,5 @@ import { ObjectType, Field, Int, registerEnumType } from '@nestjs/graphql'; - -export enum CountOption { - ORIGIN = 'origin_para_id', - DESTINATION = 'dest_para_id', - BOTH = 'both', -} +import { CountOption } from '../count-option'; registerEnumType(CountOption, { name: 'CountOption',