diff --git a/apps/visualizator-be/package.json b/apps/visualizator-be/package.json index 2fe0cf0c..5647699e 100644 --- a/apps/visualizator-be/package.json +++ b/apps/visualizator-be/package.json @@ -18,7 +18,8 @@ "test": "jest --passWithNoTests", "test:watch": "jest --watch", "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@apollo/server": "^4.10.4", @@ -45,9 +46,11 @@ "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/node": "^20.14.10", + "@types/supertest": "^6.0.2", "jest": "^29.7.0", "prettier": "^3.3.2", "source-map-support": "^0.5.21", + "supertest": "^7.0.0", "ts-jest": "^29.1.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", diff --git a/apps/visualizator-be/src/app/app.module.ts b/apps/visualizator-be/src/app/app.module.ts index 21c6a83d..5df88556 100644 --- a/apps/visualizator-be/src/app/app.module.ts +++ b/apps/visualizator-be/src/app/app.module.ts @@ -3,12 +3,12 @@ import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; -import { MessageModule } from 'src/messages/messages.module'; -import { Asset, Message } from 'src/messages/message.entity'; -import { Channel } from 'src/channels/channel.entity'; -import { ChannelModule } from 'src/channels/channels.module'; +import { MessageModule } from '../messages/messages.module'; +import { Asset, Message } from '../messages/message.entity'; +import { Channel } from '../channels/channel.entity'; +import { ChannelModule } from '../channels/channels.module'; import { ScheduleModule } from '@nestjs/schedule'; -import { TasksModule } from 'src/tasks/tasks.module'; +import { TasksModule } from '../tasks/tasks.module'; const typeOrmConfig = (config: ConfigService): TypeOrmModuleOptions => ({ type: 'postgres', diff --git a/apps/visualizator-be/test/app.e2e-spec.ts b/apps/visualizator-be/test/app.e2e-spec.ts new file mode 100644 index 00000000..174a6c47 --- /dev/null +++ b/apps/visualizator-be/test/app.e2e-spec.ts @@ -0,0 +1,519 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { AppModule } from '../src/app/app.module'; +import * as request from 'supertest'; + +describe('XCM API (e2e)', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + describe('Channels', () => { + describe('findAll', () => { + it('findAll channels within a specific time range', () => { + const startTime = new Date('2022-01-01T00:00:00Z'); + const endTime = new Date('2023-01-02T00:00:00Z'); + + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query channels($startTime: Timestamp!, $endTime: Timestamp!) { + channels(startTime: $startTime, endTime: $endTime) { + id + sender + recipient + message_count + } + } + `, + variables: { + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200) + .expect((res) => { + expect(res.body.data.channels).toBeInstanceOf(Array); + expect(res.body.data.channels.length).toBeGreaterThan(0); + }); + }); + + it('findAll channels within a time range with no expected data', () => { + const startTime = new Date('2024-07-01T00:00:00Z'); + const endTime = new Date('2024-07-02T00:00:00Z'); + + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query channels($startTime: Timestamp!, $endTime: Timestamp!) { + channels(startTime: $startTime, endTime: $endTime) { + id + sender + recipient + message_count + } + } + `, + variables: { + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200) + .expect((res) => { + expect(res.body.data.channels).toEqual([]); + }); + }); + }); + + describe('findOne', () => { + it('findOne channel with a valid ID', () => { + const channelId = 1; + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query GetChannel($id: Int!) { + channel(id: $id) { + id + sender + recipient + message_count + } + } + `, + variables: { + id: channelId, + }, + }) + .expect(200) + .expect((res) => { + expect(res.body.data.channel).toEqual({ + id: channelId, + sender: expect.any(Number), + recipient: expect.any(Number), + message_count: expect.any(Number), + }); + }); + }); + + it('findOne channel with an invalid ID', () => { + const invalidChannelId = 9999; + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query GetChannel($id: Int!) { + channel(id: $id) { + id + sender + recipient + message_count + } + } + `, + variables: { + id: invalidChannelId, + }, + }) + .expect(200) + .expect((res) => { + expect(res.body.errors).toBeInstanceOf(Array); + expect(res.body.data).toBeNull(); + }); + }); + }); + }); + + describe('Messages', () => { + describe('messageCounts', () => { + it('messageCounts with valid paraIds and time range', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-31T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query messageCounts($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + messageCounts(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + success + failed + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.messageCounts).toBeInstanceOf(Array); + expect(response.body.data.messageCounts.length).toBeGreaterThan(0); + response.body.data.messageCounts.forEach((count) => { + expect(count).toMatchObject({ + paraId: expect.any(Number), + success: expect.any(Number), + failed: expect.any(Number), + }); + }); + }); + + it('messageCounts with valid paraIds and no data expected in the time range', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2025-07-01T00:00:00Z'); + const endTime = new Date('2025-07-31T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query messageCounts($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + messageCounts(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + success + failed + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.messageCounts).toEqual([ + { + paraId: 2012, + success: 0, + failed: 0, + }, + { + paraId: 2004, + success: 0, + failed: 0, + }, + ]); + }); + }); + + describe('messageCountsByDay', () => { + it('messageCountsByDay with valid paraIds and known time range', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-07T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query messageCountsByDay($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + messageCountsByDay(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + date + messageCount + messageCountSuccess + messageCountFailed + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.messageCountsByDay).toBeInstanceOf(Array); + expect(response.body.data.messageCountsByDay.length).toBeGreaterThan(0); + response.body.data.messageCountsByDay.forEach((count) => { + expect(count).toMatchObject({ + paraId: expect.any(Number), + date: expect.any(String), + messageCount: expect.any(Number), + messageCountSuccess: expect.any(Number), + messageCountFailed: expect.any(Number), + }); + }); + }); + + it('messageCountsByDay with valid paraIds and known time range', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-07T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query messageCountsByDay($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + messageCountsByDay(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + date + messageCount + messageCountSuccess + messageCountFailed + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.messageCountsByDay).toBeInstanceOf(Array); + expect(response.body.data.messageCountsByDay.length).toBeGreaterThan(0); + response.body.data.messageCountsByDay.forEach((count) => { + expect(count).toMatchObject({ + paraId: expect.any(Number), + date: expect.any(String), + messageCount: expect.any(Number), + messageCountSuccess: expect.any(Number), + messageCountFailed: expect.any(Number), + }); + }); + }); + }); + + describe('assetCountsBySymbol', () => { + it('assetCountsBySymbol with valid paraIds and time range with expected data', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-07T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query assetCountsBySymbol($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + assetCountsBySymbol(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + symbol + count + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.assetCountsBySymbol).toBeInstanceOf(Array); + expect(response.body.data.assetCountsBySymbol.length).toBeGreaterThan( + 0, + ); + response.body.data.assetCountsBySymbol.forEach((count) => { + expect(count).toMatchObject({ + paraId: expect.any(Number), + symbol: expect.any(String), + count: expect.any(Number), + }); + }); + }); + + it('assetCountsBySymbol with valid paraIds and time range with no expected data', async () => { + const paraIds = [2012, 2004]; + const startTime = new Date('2025-01-01T00:00:00Z'); + const endTime = new Date('2025-01-07T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query assetCountsBySymbol($paraIds: [Int!], $startTime: Timestamp!, $endTime: Timestamp!) { + assetCountsBySymbol(paraIds: $paraIds, startTime: $startTime, endTime: $endTime) { + paraId + symbol + count + } + } + `, + variables: { + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.assetCountsBySymbol).toEqual([]); + }); + }); + + describe('accountCounts', () => { + it('accountCounts with accounts exceeding the specified threshold', async () => { + const threshold = 10; + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-31T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query accountCounts( + $threshold: Int! + $paraIds: [Int!] + $startTime: Timestamp! + $endTime: Timestamp! + ) { + accountCounts( + threshold: $threshold + paraIds: $paraIds + startTime: $startTime + endTime: $endTime + ) { + id + count + } + } + `, + variables: { + threshold: threshold, + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.accountCounts).toBeInstanceOf(Array); + expect(response.body.data.accountCounts.length).toBeGreaterThan(0); + response.body.data.accountCounts.forEach((account) => { + expect(account).toMatchObject({ + id: expect.any(String), + count: expect.any(Number), + }); + expect(account.count).toBeGreaterThan(threshold); + }); + }); + + it('accountCounts with no accounts exceeding the specified threshold', async () => { + const threshold = 100; + const paraIds = [2012, 2004]; + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-02T23:59:59Z'); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query accountCounts( + $threshold: Int! + $paraIds: [Int!] + $startTime: Timestamp! + $endTime: Timestamp! + ) { + accountCounts( + threshold: $threshold + paraIds: $paraIds + startTime: $startTime + endTime: $endTime + ) { + id + count + } + } + `, + variables: { + threshold: threshold, + paraIds: paraIds, + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + }, + }) + .expect(200); + + expect(response.body.data.accountCounts).toEqual([]); + }); + }); + + describe('totalMessageCounts', () => { + it('totalMessageCounts over a specified period with data', async () => { + const startTime = new Date('2023-01-01T00:00:00Z'); + const endTime = new Date('2023-01-31T23:59:59Z'); + const countBy = 'ORIGIN'; + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query totalMessageCounts($startTime: Timestamp!, $endTime: Timestamp!, $countBy: CountOption!) { + totalMessageCounts(startTime: $startTime, endTime: $endTime, countBy: $countBy) { + paraId + totalCount + } + } + `, + variables: { + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + countBy: countBy, + }, + }) + .expect(200); + + console.log(response.body); + + expect(response.body.data.totalMessageCounts).toBeInstanceOf(Array); + expect(response.body.data.totalMessageCounts.length).toBeGreaterThan(0); + response.body.data.totalMessageCounts.forEach((count) => { + expect(count).toMatchObject({ + paraId: expect.any(Number), + totalCount: expect.any(Number), + }); + expect(count.totalCount).toBeGreaterThan(0); + }); + }); + + it('totalMessageCounts over a specified period with no expected data', async () => { + const startTime = new Date('2025-01-01T00:00:00Z'); + const endTime = new Date('2025-01-31T23:59:59Z'); + const countBy = 'ORIGIN'; + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query totalMessageCounts($startTime: Timestamp!, $endTime: Timestamp!, $countBy: CountOption!) { + totalMessageCounts(startTime: $startTime, endTime: $endTime, countBy: $countBy) { + paraId + totalCount + } + } + `, + variables: { + startTime: startTime.getTime() / 1000, + endTime: endTime.getTime() / 1000, + countBy: countBy, + }, + }) + .expect(200); + + expect(response.body.data.totalMessageCounts).toEqual([]); + }); + }); + }); +}); diff --git a/apps/visualizator-be/test/jest-e2e.json b/apps/visualizator-be/test/jest-e2e.json new file mode 100644 index 00000000..e9d912f3 --- /dev/null +++ b/apps/visualizator-be/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c6eed8f..e0e622d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,6 +252,9 @@ importers: '@types/node': specifier: ^20.14.10 version: 20.14.10 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.6.5)(@types/node@20.14.10)(typescript@5.3.3)) @@ -261,6 +264,9 @@ importers: source-map-support: specifier: ^0.5.21 version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.0.0 ts-jest: specifier: ^29.1.5 version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.6.5)(@types/node@20.14.10)(typescript@5.3.3)))(typescript@5.3.3)