I'm trying to unit test a DialogflowApp locally by using the firebase shell environment. (in a cli do firebase experimental:functions:shell and then call my methods)
I have followed this guide by google https://firebase.google.com/docs/functions/local-emulator but they don't use the DialogflowApp where the invoked function tries to bind a request object containing intents and parameters like this ->
exports.myFunction = functions.https.onRequest((request, response) => {
const app = new App({ request, response });
function myMethod(app) {
let myArgument = app.getArgument(MY_ARGUMENT);
app.tell('Here we are responding');
}
let actionMap = new Map();
actionMap.set(MYMETHOD_ACTION, myMethod);
app.handleRequest(actionMap);
});
Regardless of what request object I send in the CLI, like this myFunction(require("../test/testdata.json")), the request body object is empty, like this body: {} which means I can't do app.handleRequest() or app.getArgument(). The error message I get is
RESPONSE RECEIVED FROM FUNCTION: 400, Action Error: no matching intent
handler for: null
I thought that if I populated testdata.json with the json request data shown in Actions on Google -> console.actions.google.com -> Simulator it would be valid data but no.
My question is, how can i mock my request data so that I can start unit testing my fullfillment methods locally?
EDIT 1:
firebase > myMethod.post("/").form(require("../test/testdata.json"))
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property `parameters` of 'undefined' or 'null'.
if we look in dialogflow_app.js we can see this code for fetching an argument value
getArgument (argName) {
debug('getArgument: argName=%s', argName);
if (!argName) {
error('Invalid argument name');
return null;
}
const { parameters } = this.body_.result;
if (parameters && parameters[argName]) {
return parameters[argName];
}
return this.getArgumentCommon(argName);
}
this.body_ is always just empty {}, regardless of how and what I send into the method when running locally.
EDIT 3
firebase > myMethod({method: "post",json: true, body: require("../test/testdata.json")})
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property parameters of 'undefined' or 'null'.
Invoking a Firebase HTTPS function using the shell requires a different form. It takes the parameters that the request module does, so in order to emulate a webhook, it will be something like this:
myfunction({
method: 'POST',
json: true,
body: require("../test/testdata.json")
});
These three parameters are important:
You need to specify that this is a POST operation
You need to indicate that the body will be JSON. This will send the correct header and won't try to send the body as x-www-form-urlencoded
You need to include the body. As an object is ok because you've set the json parameter to true.
Related
I am not sure how I am supposed to get the errors that come from the backend when a POST request is sent to the backend. If I use plain axios calls, I can simply get the errors from the response object in the catch block with:
error.response.data.errors
But when using Redux and using createAsyncThunk method, on a 400 status code from the server, a rejected action is dispatched and the error object I get is a generic one like so:
{
message: "Request failed with status code 400"
name: "Error"
stack: "Error: Request failed with status code 400\n...."
}
How can I get the server errors, just like using axios?
You can make use of the rejectWithValue function from redux-toolkit to include the server error as the payload property of your rejected action.
It would be something like this (untested code because I’m on my phone)
const myAction = createAsyncThunk(
‘actionName’,
async ( arg, {rejectWithValue} ) => {
try {
const res = await axios.post(…);
return res.data;
} catch (error) {
return rejectWithValue( error.response.data.errors );
}
});
I think what you can do is add an additional check for the errors and also wrap the axios post request with a try catch block.
Note : In your case the request is failing so I guess there must be some error with the way you are making a request.
This question already has answers here:
Firebase Callable Function + CORS
(21 answers)
Closed 1 year ago.
I have a problem with Firebase and CORs, apparently it cannot reach the endpoint with errors like:
Access to fetch at
'https://europe-west2-XXX.cloudfunctions.net/fetchChatToken'
from origin 'https://trato.app' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
service.ts:203
POST
https://europe-west2-XXX.cloudfunctions.net/fetchChatToken
net::ERR_FAILED (anonymous) # service.ts:203 ... ...
error.ts:66 Uncaught (in promise) Error: internal
at new t (error.ts:66)
at error.ts:175
at e. (service.ts:276)
at tslib.es6.js:100
at Object.next (tslib.es6.js:81)
at a (tslib.es6.js:71)
I also checked the network tab on dev inspector (chrome) to check if the CORS header is there, i dont see it.
Also, I have been checking firebase functions logs and apparently is not being even invoked, the last line showing is the deployment.
the way that Im using it is this:
Front End side:
const functions = firebaseApp.functions('europe-west2');
export const fetchChatToken = async () => (await functions.httpsCallable('fetchChatToken')()).data;
Functions (Backend) side:
const ensureAuthentication = auth => { if (!auth) throw new HttpsError("unauthenticated", "authentication required"); };
exports.fetchChatToken = functions.region("europe-west2").https.onCall((data, context) => {
ensureAuthentication(context.auth);
try {
const { AccessToken } = twilio.jwt;
const { ChatGrant } = AccessToken;
const grant = new ChatGrant({
serviceSid: conversationsid
});
const token = new AccessToken(accountsid, apikey, apisecret);
token.addGrant(grant);
token.identity = context.auth.uid;
return token.toJwt();
} catch (error) {
console.error(error);
throw new HttpsError("internal", "internal error");
} });
Unfortunately there many reasons possible for this CORS error. If the cloud function returns an "internal" error message it might be due to inconsistent Regions or errors in your cloud function code. My checklist for this error when creating a new cloud function:
Not matched Regions of Firestore-Project, Functions and Client side init cause a CORS Error
internal code errors inside the cloud functions cause this error
new function must be included in cloud function index file (if used)
cloud function name must match the string on client side invocation
delete cloud function in firebase dashboard before deploying new one after error
Make sure the function name referenced in the client is correct, see https://stackoverflow.com/a/62042554/1030246
I got it solved changing it to us, basically removing the region, taking out the 'europe-wes2' region from the function declaration and from the function call it works fine again.
I assume there is some error on the firebase side.
I am following this Twilio tutorial on how to reply to SMS messages with my app:
https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-node-js
The tutorial assumes you're using Express, but I am doing this with a Cloud Function, so my code looks a bit different:
exports.sms = functions.https.onCall((req: any, res: any) => {
const twiml = new MessagingResponse();
if (req.body.Body === 'hello') {
twiml.message('Hi!');
} else if (req.body.Body === 'bye') {
twiml.message('Goodbye');
} else {
twiml.message(
'No Body param match, Twilio sends this in the request to your server.',
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
});
When I text my Twilio #, it hits that endpoint, but I get the following error:
Request has incorrect Content-Type. application/x-www-form-urlencoded
How do I get around this?
It looks like you're mixing up callable type functions and normal HTTP type functions. Please read the documentation to understand the difference. Callable functions are intended to be invoked directly from your mobile app using the provided client SDK. They provide two arguments: an input data object, and a context. Callables do NOT provide "req" and "res". If you want control over the the response, you should be using a normal HTTP function with "onRequest" instead of "onCall".
When I am hitting this function directly by URL it worked and this insert data in Firebase object
addCountry: function (req, res) {
var ref = db.ref();
var usersRef = ref.child("country");
usersRef.push({
name: 'United States',
is_deleted: 0,
});
return res.view('city-listing');
}
But when I called this function by the form submit post method then it will throw the error:
"error: Sending 500 ("Server Error") response:
Error: Can't set headers after they are sent."
Error: Can't set headers after they are sent.
Error occurs if you are sending the view more than once in the same route handler . It had happened to me once too. Check that you don't have more than one view related rendering which might execute twice.
Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?