How do I instantiate a turnContext using the adapter and request as parameters - botframework

I would like to instantiate a turnContext to be used in integration testing. How would I be able to instantiate one without calling on the processActivity() method of the adapter?
I am looking at the documentation but it shows that I would need the request of the post call as the parameter. I would like my testing to be independant of the post call. I would then assume that I would need to instantiate the request? How would I go about doing so?
Image of documentation

This is a bit hard to answer without knowing how you are planning to use the code. That being said, it's not that hard to create a new turnContext and also bypass the processActivity(). Given how you are referencing turnContext and processActivity(), I'm assuming you are using the Node SDK. Implementing in C# wouldn't be too different.
Here are two options, both utilizing the creation of a new adapter, however you can also pass in an already established turnContext, if desired:
Use .createContext in server.post in the index.js file, or
Maintain the processActivity() method in the server.post. This calls a new "onTurn" method in the bot.js file. In doing so, this allows you to control when and how the new "onTurn" is accessed.
Option 1: In the index.js file, you will want to create a new adapter or make a copy of the first depending on your needs:
const adapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
const newAdapter = adapter;
or
const adapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
const newAdapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
Include the onTurnError code to catch errors:
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
console.error(`\n [onTurnError]: ${ error }`);
await context.sendActivity(`Oops. Something went wrong!`);
};
// Catch-all for errors.
newAdapter.onTurnError = async (context, error) => {
console.error(`\n [onTurnError]: ${ error }`);
await context.sendActivity(`Oops. Something went wrong!`);
};
Then, set the new adapters and create the new turnContext:
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (turnContext) => {
await bot.onTurn(turnContext);
});
newAdapter.createContext(req, res);
});
Options 2: In the index.js file, building off of the above code, set the adapters to await the individual "onTurn" methods:
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (turnContext) => {
await bot.onTurn(turnContext);
});
newAdapter.processActivity(req, res, async (turnContext) => {
await bot.newOnTurn(turnContext);
});
});
In the bot.js file, you will have your two "onTurn" methods. In this example, the different "onTurn" methods are called based on whether a message is sent or I am deleting user data (I am sending this event via the Emulator => Conversation menu item). What you decide to match on is up to you.
async newOnTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.DeleteUserData) {
const dc = await this.dialogs.createContext(turnContext);
await dc.context.sendActivity(`Looks like you deleted some user data.`);
}
}
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
const dc = await this.dialogs.createContext(turnContext);
await dc.context.sendActivity(`Looks like you sent a message.`);
}
}
Hope of help!

Related

AWS.ApiGatewayManagementApi() postToConnection no initial response detected on client

I have a lambda function that returns a message to the client.
function replyToMessage (messageText,connectionId) {
const data = {message:messageText}
const params = {
ConnectionId : connectionId,
Data: Buffer.from(JSON.stringify(data))
}
return api.postToConnection(params).promise()
.then(data => {})
.catch(error => {console.log("error",error)})
}
This code is called once when the connection is made and I get a response to my client. When I call the function again with a different endpoint, it doesn't send a response to my client. However, when I call it a third time, I get the response to my client from the second call. Here's my switch when the Lambda function is called.
switch(route) {
case "$connect":
break
case "$disconnect":
break
case "connectTo":
await connectToService(JSON.parse(event.body).eventId,connectionId)
await replyToMessage("Connected eventId to connId",connectionId)
break
case "disconnectFrom":
await disConnectToService(JSON.parse(event.body).eventId,connectionId)
break
case "project":
responseItems = await getBroadcastIds (JSON.parse(event.body).eventId,JSON.parse(event.body).sourceId,connectionId)
console.log(responseItems)
responseItems.Items.forEach(async function(item) {
await replyToMessage(JSON.parse(event.body).sourceId,item.connectionId)
})
responseItems = []
break
default :
console.log("Unknown route", route)
The issue appears to be the async forEach loop. Switching to the following resolves the issue.
for (const item of responseItems.Items) {
console.log("Sending to:",item.connectionId);
await replyToMessage(JSON.parse(event.body).sourceId,item.connectionId)
}
See this post for the answer that led to this resolution. Using async/await with a forEach loop

Unit testing NestJS Observable Http Retry

I'm making a request to a 3rd party API via NestJS's built in HttpService. I'm trying to simulate a scenario where the initial call to one of this api's endpoints might return an empty array on the first try. I'd like to use RxJS's retryWhen to hit the api again after a delay of 1 second. I'm currently unable to get the unit test to mock the second response however:
it('Retries view account status if needed', (done) => {
jest.spyOn(httpService, 'post')
.mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
.mockReturnValueOnce(of(successfulView));
const accountId = '0812081208';
const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
const response =client.viewAccountStatus(accountId, batchNo);
response.subscribe(
data => {
expect(data[0].accountNo)
.toBe('0812081208');
expect(data[0].companyName)
.toBe('Some company name');
done();
},
)
});
My implementation is:
viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
const verificationRequest = new VerificationRequest();
verificationRequest.accountNo = accountId;
verificationRequest.batchNo = batchNo;
this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const response = this.httpService.post(url, verificationRequest, config)
.pipe(
map(res => {
console.log(res.data); // always empty
if (res.status >= 400) {
throw new HttpException(res.statusText, res.status);
}
if (!res.data.length) {
this.logger.debug('Response was empty');
throw new HttpException('Account not found', 404);
}
return res.data;
}),
retryWhen(errors => {
this.logger.debug(`Retrying accountId: ${accountId}`);
// It's entirely possible the first call will return an empty array
// So we retry with a backoff
return errors.pipe(
delayWhen(() => timer(1000)),
take(1),
);
}),
);
return response;
}
When logging from inside the initial map, I can see that the array is always empty. It's as if the second mocked value never happens. Perhaps I also have a solid misunderstanding of how observables work and I should somehow be trying to assert against the SECOND value that gets emitted? Regardless, when the observable retries, we should be seeing that second mocked value, right?
I'm also getting
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
On each run... so I'm guessing I'm not calling done() in the right place.
I think the problem is that retryWhen(notifier) will resubscribe to the same source when its notifier emits.
Meaning that if you have
new Observable(s => {
s.next(1);
s.next(2);
s.error(new Error('err!'));
}).pipe(
retryWhen(/* ... */)
)
The callback will be invoked every time the source is re-subscribed. In your example, it will call the logic which is responsible for sending the request, but it won't call the post method again.
The source could be thought of as the Observable's callback: s => { ... }.
What I think you'll have to do is to conditionally choose the source, based on whether the error took place or not.
Maybe you could use mockImplementation:
let hasErr = false;
jest.spyOn(httpService, 'post')
.mockImplementation(
() => hasErr ? of(successView) : (hasErr = true, of(failView))
)
Edit
I think the above does not do anything different, where's what I think mockImplementation should look like:
let err = false;
mockImplementation(
() => new Observable(s => {
if (err) {
s.next(success)
}
else {
err = true;
s.next(fail)
}
})
)

Send param with POST to Botframework (and different channel)

I'm working on bot project, the bot are going to work on different channel (web/messenger and probably other)
I'm actually at the proactive message, we want to send dynamic message to user, for example "You don't talk me from XXX time"
So I've made a new route in bot, for sending message with conversation references, it's work good on emulator/messenger for the moment, but we trying to add parameter to this request but we don't found any way to get param in bot.
server.post('/api/notify/:conversationID', async (req, res) => {
console.log(req)
if (req.params.conversationID){
console.log(req.params.conversationID)
}
for (let conversationReference of Object.values(conversationReferences)) {
if (typeof conversationReferences[req.params.conversationID] !== "undefined"){
await adapter.continueConversation(conversationReferences[req.params.conversationID], async turnContext => {
await turnContext.sendActivity(req.params.message);
});
}else {
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity(req.params.message);
});
}
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Test send.</h1></body></html>');
res.end();
});
I also tried with GET, and sending parameter in URL like /api/notify/CONVID/MESSAGEtoUSER
But if the message length are more than 70 character, the bot return automatically method don't exist, it's like when the length are 'big' so but understand it like route and not like parameter...
Anyone have idea how can we get the param?
Thank!
EDIT :
Finally I found a way to pass param as POST call.
You need to enable bodyParser of restify, add this line :
server.use(restify.plugins.bodyParser())
in index.js
You can now get the body of POST route call !
:-)
You can achieve this by passing any params in an empty activity via the channelData property. Because the activity includes an empty string in the text property, the activity will not display when passed to the bot.
In this example, the proactive message is initiated from the browser.
server.get('/api/notify/:userId', async (req, res) => {
const { userId } = req.params;
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async turnContext => {
var reply = { type: ActivityTypes.Message };
reply.channelData = { userId };
reply.text = '';
await turnContext.sendActivity(reply);
});
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
res.end();
});
Proactive message sending the userId via channelData
userId is received by the bot via activity.channelData
Testing Web Chat also shows userId in the activity.channelData
Hope of help!

How to get session object in Microsoft azure bot sdk 4.0 in node.js?

Attaching the code snippet below. UniversalBot and ChatConnector has been deprecated in botbuilder 4.1.5.
var bot;
try {
bot = new BasicBot(conversationState, userState, botConfig);
} catch (err) {
console.error(`[botInitializationError]: ${ err }`);
process.exit();
}
// Create HTTP server
// let server = restify.createServer();
let server = express();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`);
console.log(`\nTo talk to your bot, open basic-bot.bot file in the Emulator`);
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
// Route received a request to adapter for processing
adapter.processActivity(req, res, async (turnContext) => {
// route to bot activity handler.
await bot.onTurn(turnContext);
});
});
Your question is fairly general.
The session object from 3.x has been removed. Instead acccessors are used. You will want to do following in the bot class:
public onTurn = async (turnContext: TurnContext) => {
const userProfile = await this.userProfile.get(turnContext, new UserProfile());
const conversationData = await this.dialogStateAccessor.get(turnContext, { dialogStack: undefined });
// set vars in cache
userProfile.yourUserVarProp = "userValue";
conversationData.yourConversationVarProp = "conversationValue";
// persist userVars through dialog turn
await this.userProfile.set(turnContext, userProfile);
// persist conversationVars through dialog turn
await this.dialogStateAccessor.set(turnContext, conversationData);
//
// -> your dialogs here (await dc.beginDialog("dialogname");)
//
// save uservars to db at end of a turn
await this.userState.saveChanges(turnContext);
// save conversationVars to db at end of a turn
await this.conversationState.saveChanges(turnContext);
}
But there is some additional constructor stuff
#param {ConversationState} conversationState A ConversationState object used to store the dialog state.
#param {UserState} userState A UserState object used to store values specific to the user.
... and creating the userProfile and dialogStateAccessor itself.
For the whole picture have better a look at https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs .
Or try the generator: https://learn.microsoft.com/en-us/azure/bot-service/javascript/bot-builder-javascript-quickstart?view=azure-bot-service-4.0.

Why do we await next when using koa routers?

Why do we do this
router.get('/data', async (ctx, next) => {
ctx.body = dummyjson.parse(data);
await next();
});
router.get('/data/:x', async (ctx, next) => {
const newData = dataRepeat.replace('%(x)', ctx.params.x);
ctx.body = dummyjson.parse(newData);
await next();
});
What is the use of await next()
It would work just fine without that. Similar thing was expected with koa 1. yield next was added at the end of the router.
I'll try to explain it using a very simple example:
const Koa = require('koa');
const app = new Koa();
// middleware
app.use(async function (ctx, next) {
console.log(1)
await next();
console.log(3)
});
// response
app.use(ctx => {
console.log(2)
});
app.listen(3000);
If you call localhost:3000 in your browser, the following will happen in your app:
The first app.use that you fired here was the middleware. So the request flow goes into that one first, logs 1to the console.
Then, when you see this await next(), it downstreams to the next use.
Here we just log 2 to the console. When this is finished (and no further await next is seen in the second use) the flow goes back to the first one which actually waited till the second one was finished.
Here we then continue with logging 3 to the console.
Hope this makes it a little more clear.
No, It is not necessary. It is depend on your requirement.
you use next() function when you call next middleware.
Check your router module and its version. I have use koa-router module and its version is 7.2.0 for routing. It self handle await next.
'use strict';
const Koa = require('koa'),
router = require('koa-router'),
app = new Koa();
let pubRouter = new router();
let securedRouter = new router();
let mapper = require('./mapper'),
// assign router to corresponding function
mapper(pubRouter, securedRouter);
app .use(logger(config.app.log))
.use(bodyParser())
.use(pubRouter.routes()).use(pubRouter.allowedMethods())
.use(jwt({
secret: publicKey,
algorithms: ['RS256']
}))
.use(async(ctx, next) => {
console.log('\n\n\n\n\n', ctx.state.user);
await next();
})
.use(securedRouter.routes()).use(securedRouter.allowedMethods())
.use(async(ctx, next) => {
ctx.body = 'Invalid URL!!!';
});
app.listen(port, () => console.log(`Server listening on port: ${port}`));

Resources