Skip to content

Commit 11b78c1

Browse files
authored
Merge pull request #226 from samagra-comms/develop
Develop merge
2 parents 5c577eb + a490eaf commit 11b78c1

File tree

7 files changed

+377
-22
lines changed

7 files changed

+377
-22
lines changed

.node-xmlhttprequest-sync-3980100

Whitespace-only changes.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"reflect-metadata": "^0.1.13",
7474
"rimraf": "^3.0.2",
7575
"rxjs": "^7.2.0",
76+
"socket.io-client": "^4.7.2",
7677
"swagger-ui-express": "^4.3.0",
7778
"undici": "^5.0.0",
7879
"uuid": "^8.3.2",

src/health/health.service.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '@nestjs/terminus';
1111
import { PrismaService } from '../global-services/prisma.service';
1212
import { FormService } from '../modules/form/form.service';
13+
import { io } from 'socket.io-client';
1314

1415
@Injectable()
1516
export class HealthService extends HealthIndicator{
@@ -28,6 +29,7 @@ export class HealthService extends HealthIndicator{
2829
() => this.checkUciCoreHealth(),
2930
() => this.checkDatabaseHealth(),
3031
() => this.checkFormServiceHealth(),
32+
() => this.checkTransportSocketHealth(),
3133
]);
3234
}
3335

@@ -53,4 +55,104 @@ export class HealthService extends HealthIndicator{
5355
throw new HealthCheckError("FormService failed to connect!", this.getStatus('FormService', false, {message: e.message}));
5456
});
5557
}
58+
59+
async checkTransportSocketHealth(): Promise<any> {
60+
const baseUrl = this.configService.get('SOCKET_URL');
61+
const connOptions = {
62+
transportOptions: {
63+
polling: {
64+
extraHeaders: {
65+
Authorization: `Bearer ${this.configService.get(
66+
'SOCKET_AUTH_TOKEN',
67+
)}`,
68+
channel: this.configService.get('SOCKET_CONNECTION_CHANNEL'),
69+
},
70+
},
71+
},
72+
query: {
73+
deviceId: this.configService.get('SOCKET_TO'),
74+
},
75+
autoConnect: false,
76+
};
77+
78+
const payload: any = {
79+
content: {
80+
text: '*',
81+
appId: this.configService.get('SOCKET_APP_ID'),
82+
channel: this.configService.get('SOCKET_CHANNEL'),
83+
context: null,
84+
accessToken: null,
85+
},
86+
to: this.configService.get('SOCKET_TO'),
87+
};
88+
try {
89+
const socket = await this.connectSocket(baseUrl, connOptions);
90+
if (!socket) {
91+
return new HealthCheckError(
92+
'Socket connection timed out',
93+
this.getStatus('TransportSocketService', false, {
94+
message: 'Socket connection timed out',
95+
}),
96+
);
97+
}
98+
99+
const responseReceived = await this.sendBotRequest(socket, payload);
100+
101+
if (responseReceived) {
102+
socket.disconnect();
103+
return this.getStatus('TransportSocketService', true);
104+
} else {
105+
return new HealthCheckError(
106+
'Bot response timed out',
107+
this.getStatus('TransportSocketService', false, {
108+
message: 'Bot response timed out',
109+
}),
110+
);
111+
}
112+
} catch (error) {
113+
return new HealthCheckError(
114+
'An error occurred',
115+
this.getStatus('TransportSocketService', false, {
116+
message: 'An error occurred',
117+
}),
118+
);
119+
}
120+
}
121+
122+
private async connectSocket(baseUrl: string, connOptions: any): Promise<any> {
123+
return new Promise(async (resolve) => {
124+
const socket = await io(baseUrl, connOptions);
125+
126+
socket.connect();
127+
socket.on('connect', function () {
128+
resolve(socket);
129+
});
130+
socket.on('connect_error', () => {
131+
resolve(false);
132+
});
133+
setTimeout(async () => {
134+
resolve(false);
135+
}, this.configService.get('SOCKET_TIMEOUT_TIME') || 20000);
136+
});
137+
}
138+
139+
private async sendBotRequest(socket: any, payload: any): Promise<boolean> {
140+
const newPayload = { ...payload };
141+
return new Promise(async (resolve) => {
142+
socket.on('session', async (session) => {
143+
const socketID = session.socketID;
144+
const userID = session.userID;
145+
newPayload.content.from = socketID;
146+
newPayload.content.userId = userID;
147+
socket.emit('botRequest', newPayload);
148+
});
149+
150+
socket.on('botResponse', (data) => {
151+
resolve(true);
152+
});
153+
setTimeout(() => {
154+
resolve(false);
155+
}, this.configService.get('SOCKET_TIMEOUT_TIME') || 20000); // Wait for 20 seconds for bot response
156+
});
157+
}
56158
}

src/modules/bot/bot.controller.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { diskStorage } from 'multer';
3030
import { Request } from 'express';
3131
import { extname } from 'path';
3232
import fs from 'fs';
33+
import { DeleteBotsDTO } from './dto/delete-bot-dto';
3334

3435

3536
const editFileName = (req: Request, file: Express.Multer.File, callback) => {
@@ -335,15 +336,26 @@ export class BotController {
335336
return this.botService.update(id, updateBotDto);
336337
}
337338

338-
@Delete(':id')
339+
@Delete()
339340
@UseInterceptors(
340341
AddResponseObjectInterceptor,
341342
AddAdminHeaderInterceptor,
342343
AddOwnerInfoInterceptor,
343344
AddROToResponseInterceptor,
344345
)
345-
remove(@Param('id') id: string) {
346-
return this.botService.remove(id);
346+
async remove(@Body() body: DeleteBotsDTO) {
347+
return await this.botService.remove(body);
348+
}
349+
350+
@Delete(':botId')
351+
@UseInterceptors(
352+
AddResponseObjectInterceptor,
353+
AddAdminHeaderInterceptor,
354+
AddOwnerInfoInterceptor,
355+
AddROToResponseInterceptor,
356+
)
357+
async removeOne(@Param('botId') botId: string) {
358+
return await this.botService.removeOne(botId);
347359
}
348360

349361
@Get(':botId/broadcastReport')

src/modules/bot/bot.service.spec.ts

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,31 @@ const MockPrismaService = {
4444
}
4545
},
4646
count: () => 10,
47-
update: jest.fn()
48-
}
47+
update: jest.fn(),
48+
deleteMany: (filter) => {
49+
deletedIds.push({'bot': filter.where.id.in});
50+
}
51+
},
52+
service: {
53+
deleteMany: (filter) => {
54+
deletedIds.push({'service': filter.where.id.in});
55+
}
56+
},
57+
userSegment: {
58+
deleteMany: (filter) => {
59+
deletedIds.push({'userSegment': filter.where.id.in});
60+
}
61+
},
62+
transformerConfig: {
63+
deleteMany: (filter) => {
64+
deletedIds.push({'transformerConfig': filter.where.id.in});
65+
}
66+
},
67+
conversationLogic: {
68+
deleteMany: (filter) => {
69+
deletedIds.push({'conversationLogic': filter.where.id.in});
70+
}
71+
},
4972
}
5073

5174
class MockConfigService {
@@ -320,6 +343,9 @@ const mockConfig = {
320343
"totalRecords": 1
321344
};
322345

346+
// Used for delete bot testing
347+
let deletedIds: any[] = []
348+
323349
describe('BotService', () => {
324350
let botService: BotService;
325351
let configService: ConfigService;
@@ -367,6 +393,17 @@ describe('BotService', () => {
367393
.toThrowError(new ConflictException("Bot already exists with the following name or starting message!"));
368394
});
369395

396+
it('create bot trims bot name properly', async () => {
397+
const mockCreateBotDtoCopy: CreateBotDto & { ownerID: string; ownerOrgID: string } = JSON.parse(JSON.stringify(mockCreateBotDto));
398+
mockCreateBotDtoCopy.name = ' testBotExistingName ';
399+
expect(botService.create(mockCreateBotDtoCopy, mockFile)).rejects
400+
.toThrowError(new ConflictException("Bot already exists with the following name or starting message!"));
401+
const mockCreateBotDtoCopy2: CreateBotDto & { ownerID: string; ownerOrgID: string } = JSON.parse(JSON.stringify(mockCreateBotDto));
402+
mockCreateBotDtoCopy2.startingMessage = ' testBotExistingStartingMessage';
403+
expect(botService.create(mockCreateBotDtoCopy2, mockFile)).rejects
404+
.toThrowError(new ConflictException("Bot already exists with the following name or starting message!"));
405+
});
406+
370407
it('get bot all data test', async () => {
371408
fetchMock.getOnce(`${configService.get<string>('MINIO_GET_SIGNED_FILE_URL')}?fileName=testImageFile`,
372409
'testImageUrl'
@@ -506,7 +543,7 @@ describe('BotService', () => {
506543
});
507544

508545
it('bot update throws NotFoundException when non existent bot is updated',async () => {
509-
fetchMock.getOnce(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
546+
fetchMock.deleteOnce(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
510547
true
511548
);
512549
expect(botService.update('testBotIdNotExisting', {
@@ -605,4 +642,97 @@ describe('BotService', () => {
605642
).toBe(true);
606643
fetchMock.restore();
607644
});
645+
646+
it('bot delete with bot id list works as expected', async () => {
647+
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
648+
true
649+
);
650+
mockBotsDb[0].status = BotStatus.DISABLED;
651+
await botService.remove({ids: ['testId'], endDate: null});
652+
expect(deletedIds).toEqual(
653+
[
654+
{'service': ['testId']},
655+
{'userSegment': ['testUserId']},
656+
{'transformerConfig': ['testTransformerId']},
657+
{'conversationLogic': ['testLogicId']},
658+
{'bot': ['testId']},
659+
]
660+
);
661+
deletedIds = [];
662+
await botService.remove({ids: ['nonExisting'], endDate: null});
663+
expect(deletedIds).toEqual(
664+
[
665+
{'service': []},
666+
{'userSegment': []},
667+
{'transformerConfig': []},
668+
{'conversationLogic': []},
669+
{'bot': []},
670+
]
671+
);
672+
deletedIds = [];
673+
expect(fetchMock.called(
674+
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
675+
))
676+
.toBe(true);
677+
mockBotsDb[0].status = BotStatus.ENABLED;
678+
fetchMock.restore();
679+
});
680+
681+
it('bot delete with endDate works as expected', async () => {
682+
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
683+
true
684+
);
685+
mockBotsDb[0].status = BotStatus.DISABLED;
686+
await botService.remove({ids: null, endDate: '2025-12-01'});
687+
expect(deletedIds).toEqual(
688+
[
689+
{'service': ['testId']},
690+
{'userSegment': ['testUserId']},
691+
{'transformerConfig': ['testTransformerId']},
692+
{'conversationLogic': ['testLogicId']},
693+
{'bot': ['testId']},
694+
]
695+
);
696+
deletedIds = [];
697+
await botService.remove({ids: null, endDate: '2023-12-01'});
698+
expect(deletedIds).toEqual(
699+
[
700+
{'service': []},
701+
{'userSegment': []},
702+
{'transformerConfig': []},
703+
{'conversationLogic': []},
704+
{'bot': []},
705+
]
706+
);
707+
expect(fetchMock.called(
708+
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
709+
))
710+
.toBe(true);
711+
deletedIds = [];
712+
mockBotsDb[0].status = BotStatus.ENABLED;
713+
fetchMock.restore();
714+
});
715+
716+
it('bot delete only deletes disabled bots', async () => {
717+
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
718+
true
719+
);
720+
mockBotsDb[0].status = BotStatus.ENABLED;
721+
await botService.remove({ids: ['testId'], endDate: null});
722+
expect(deletedIds).toEqual(
723+
[
724+
{'service': []},
725+
{'userSegment': []},
726+
{'transformerConfig': []},
727+
{'conversationLogic': []},
728+
{'bot': []},
729+
]
730+
);
731+
expect(fetchMock.called(
732+
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
733+
))
734+
.toBe(true);
735+
deletedIds = [];
736+
fetchMock.restore();
737+
});
608738
});

0 commit comments

Comments
 (0)