Middy API, return object with jsonBodyParser middleware - javascript

Right now I have to return my data like this way, with this code:
import middy from '#middy/core';
import httpErrorHandler from '#middy/http-error-handler';
import jsonBodyParser from '#middy/http-json-body-parser';
async function sampleFunction (event) {
const result = await someCodeWith(event.body);
return result;
}
export const handler = middy(sampleFunction)
.use(jsonBodyParser())
.use(httpErrorHandler());
But I'm getting an error as a response:
{
"message": "Internal server error"
}
so I need to return the data inside this object:
return {
statusCode: 200,
body: JSON.stringify(error)
};
But I thought that by using middy I didn't need to create that object, and it would be already stringified and also with a statusCode inside. I don't want to set manually each statusCode for 200, 400, 500, etc. Is there a way to only return data with data as a javascript object?

This works with Middy 2:
const strigifyResponse = () => ({
after: async (request) => {
request.response = {
statusCode: 200,
body: JSON.stringify(request.response)
}
}
})
Of course, you need to use it the same way:
export const handler = middy(sampleFunction)
.use(strigifyResponse())
.use(jsonBodyParser())
.use(httpErrorHandler());

Middy can be used by multiple event contexts. In this case API Gateway requires the response to follow the following pattern as you described. You can add in your own middleware to transform the response before it's returned.
Something like this:
// middy v2
const strigifyResponseV2 = () => {
return {
after: (request) => {
request.response = {
statusCode: 200,
body: JSON.stringify(request.response)
}
}
}
}
// middy v1
const strigifyResponseV1 = () => {
return {
after: (handler, next) => {
handler.response = {
statusCode: 200,
body: JSON.stringify(handler.response)
}
next()
}
}
}
export const handler = middy(sampleFunction)
.use(strigifyResponseV2())
.use(jsonBodyParser())
.use(httpErrorHandler());

Related

Firebase functions onRequest to onCall

How do I pass this onRequest function to onCall? I am working from my localhost with emulators. Could someone give me some guidance, I have tried to follow the documentation of functions.https.onCall but I can't understand if I have to do any previous step.
export const getFileInformation = functions.https.onRequest( (req, res) => {
return cors( req, res, () => {
const urls = [
`url1`,
`url2`,
`url3`
];
const urlsCalls: any[] = [];
const resultados: any[] = [];
urls.forEach( url => {
urlsCalls.push(axios.get(url));
});
Promise.allSettled(urlsCalls)
.then( response => {
response.map( (element: any) => {
const item = element.value.data;
resultados.push(item);
});
console.log(resultados);
res.json(resultados);
})
.catch( error => {
console.log(error);
});
} );
});
I'm trying something as simple as this:
export const getFileInformation2 = functions.https.onCall( (data, context) => {
return { msg: 'Hello from Firebase!' };
});
But I get the following error:
{"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}}
How should I address an onCall function?
HTTPS Callable functions must be called using the POST method, the Content-Type must be application/json, and the body must contain a field called data for the data to be passed to the method.
See sample body:
{
"data": {
"someKey": "someValue"
}
}
When pasting the URL directly to a browser it is called using the GET method which returns an error Request has invalid method. GET and the reason you're getting {"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}} is you're not passing the required field which is data on your request body.
You can call a https.onCall using curl or Postman.
Sample call using curl:
curl -d '{"data": {"someKey": "someValue"}}' -H 'Content-Type: application/json' http://localhost:5001/[myProjectName]/us-central1/getFileInformation2
For more information, you may check :
Protocol specification for https.onCall
Lo resolví asi:
index.ts
export const getFileInformation2 = functions.https.onCall( (data, context) => {
const urls = [
`url1`,
`url2`,
`url3`
];
const urlsCalls: any[] = [];
const resultados: any[] = [];
urls.forEach( url => {
return urlsCalls.push(axios.get(url));
});
return Promise.allSettled(urlsCalls)
.then( response => {
response.map( (element: any) => {
const item = element.value.data;
resultados.push(item);
});
console.log(resultados);
return resultados
})
.catch( error => {
console.log(error);
});
});
Luego ejecuto emulators:
firebase emulators:start --only functions
Luego lo llamo desde mi frontEnd:
component.ts
getFilesInformation() {
const functions = getFunctions(getApp());
connectFunctionsEmulator(functions, 'localhost', 5001);
const getInfo = httpsCallable(functions, 'getFileInformation2')
getInfo().then( (result: any) => {
console.log(result.data);
})
}

How to mock a curried function in jest?

I keep running into an issue where one of my curried functions is not a function when mocked out according to jest. I made a set of util httpRequest functions in a file called httpRequest.js that looks like this:
const httpRequest = (method) => {
return (headers) => {
return (data) => {
return async (url) => {
try {
const result = await axios({ method, url, data, headers });
const { data: axiosResult } = result;
return axiosResult;
} catch (err) {
console.log(`${method}Data: `, err);
throw err;
}
};
};
};
};
const getData = httpRequest('get')()();
const postData = httpRequest('post')();
const putData = httpRequest('put')();
const patchData = httpRequest('patch')();
const deleteData = httpRequest('delete')()();
const preBuiltGetRequest = httpRequest('get');
const preBuiltPostRequest = httpRequest('post');
const preBuiltPutRequest = httpRequest('put');
const preBuiltPatchRequest = httpRequest('patch');
const preBuiltDeleteRequest = httpRequest('delete');
module.exports = {
httpRequest,
getData,
postData,
putData,
patchData,
deleteData,
preBuiltGetRequest,
preBuiltPostRequest,
preBuiltPutRequest,
preBuiltPatchRequest,
preBuiltDeleteRequest,
};
When I mock out this file in a test and then use a function such as preBuiltGetRequest I get an error on jest saying TypeError: preBuiltGetRequest(...) is not a function. Here is an example of implementation of this.
Here is the function in my codebase I am testing:
queryUser: async (accessToken, email) => {
const query = `
{
getUsersByCriteria(criteria: Email, values: "${email}") {
id
groups {
id
name
entitlements {
id
code
}
members {
total
}
}
}
}
`;
const newUrl = new URL(`${BaseUrl}/v3/graphql`);
newUrl.searchParams.append('query', papiQuery);
console.log('From the Api ', preBuiltGetRequest);
const getAuthenticatedData = preBuiltGetRequest({
Authorization: `Bearer ${accessToken}`,
})();
const response = await getAuthenticatedData(newUrl.toString());
const graphQlResult = response.data?.getUsersByCriteria;
if (!graphQlResult || graphQlResult.length === 0) {
throw new Error(`Could not find user with email=${email}`);
}
return graphQlResult[0];
},
When I then run the test code mocking out preBuiltGetRequest using this code:
jest.mock('/opt/httpRequest');
const { preBuiltGetRequest } = require('/opt/httpRequest');
I receive this error:
The preBuiltGetRequest function has a signature that can be typed as
declare const prebuiltGetRequest: (header: object) => (data: object) => (url: String) => Promise<never>;
You need to mock it accordingly,
jest.mock('/opt/httpRequest');
const { preBuiltGetRequest } = require('/opt/httpRequest');
const mockSig = jest.fn().mockReturnValue(
jest.fn().mockResolvedValueOnce(error)
)
preBuiltGetRequest.mockReturnValue(mockSig)

How do I send a variable from Express.js backend to React.js frontend?

I am trying to send my variable 'backEndResponse' with its value from my Express.js backend to my React.js Frontend. I am not quite sure how to send a variable from the backend to the frontend. I have searched around and can't find any good resources. I would appreciate any help.
Express.js Backend
function getcookie(req) {
var authCookie = req.headers.cookie;
if (authCookie = req.headers.cookie) {
try {
return authCookie
.split('; ')
.find(row => row.startsWith('Auth='))
.split('=')[1];
} finally {
if (authCookie = result) {
backEndResponse = true
console.log(backEndResponse);
console.log(result);
} else {
backEndResponse = false
console.log(backEndResponse);
console.log(result);
}
}
} else {
}
}
app.get('/auth', (req, res) => {
getcookie(req)
if (backEndResponse) {
res.json(backEndResponse); // OR json({ message: "Authorised" })
} else {
res.json(backEndResponse); // OR json({ message: "Unauthorised" })
}
});
Frontend React.js
const useAuth = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchAuthData = () => {
const result = axios('http://localhost:5000/auth');
console.log(result)
setData(result.data);
};
fetchAuthData()
}, []);
// Logic to check if backEndResponse is true or false
if (data) {
const authorized = {loggedIn: true}
return authorized && authorized.loggedIn;
} else {
const authorized = {loggedIn: false}
return authorized && authorized.loggedIn;
}
}
const ProtectedRoutes = () => {
const isAuth = useAuth();
return isAuth ? <Outlet/> : <Navigate to="/login" />;
}
You won't be able to send a variable directly, rather you will send a payload in a certain shape that best represents the data suited to the applications needs. To send a response payload in an express route use something like the following:
app.get('/auth', (req, res) => {
// do some logic for `backEndResponse`...
res.json(backEndResponse);
});
If you were intending to provide more information in the response such as HTTP headers differing based on the of backEndResponse then you might consider:
app.get('/auth', (req, res) => {
// do some logic for `backEndResponse`...
// send HTTP Ok if true, otherwise Bad Request
// consider handling 400 and/or 500 errors too
if (backEndResponse) {
res.status(200).json(true); // OR json({ message: "Authorised" })
} else {
res.status(401).json(false); // OR json({ message: "Unauthorised" })
}
});
A component fetching the above endpoint would be similar to:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchAuthData = async () => {
const result = await axios('http://localhost:5000/auth');
setData(result.data); // true/false OR { message: "Authorised" }
};
fetchAuthData();
}, []);
// display payload
return (<div>{JSON.stringify(data)}</div>)
}
There is an opportunity to refactor the above into a custom hook should you find the need to reuse the functionality across multiple components.
axios request is async function, so you should do like that,
const useAuth = async () => {
try {
const res = await axios.get('http://localhost:5000/auth', {
withCredentials: true
})
return true
} catch (e) {
return false
}
};

Encountering a problem when trying to remove some code from a route to put it into a service - Node.js / Express.js

I'm having a problem right now when i want to remove some code out of my route to put it into a service. I'm just trying to follow the best practices of developing an application.
This is my route right now:
const express = require('express');
const cityRouter = express.Router();
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
cityRouter.get('/:cep', async (request, response) => {
try {
const { cep } = request.params;
const value = myCache.get(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
} catch (error) {
return response.status(400);
}
});
module.exports = cityRouter;
I'm using axios to retrieve data from an API, where i have a variable called "cep" as a parameter and then using node-cache to cache it.
And it works with out problems:
enter image description here
But, when i try to put the same code into a service, and then call it into my route:
My service:
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
function verificaCache(cep) {
return async function (request, response, next) {
const value = myCache.get(cep);
console.log(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
next();
};
}
module.exports = verificaCache;
My route using the service:
const express = require('express');
const cityRouter = express.Router();
const verificaCache = require('../services/VerificaCacheService');
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
verificaCache(cep);
response.status(200);
});
module.exports = cityRouter;
By some reason, it doesn't work:
enter image description here
What is the problem that i can't see? I'm a beginner so i'm kinda lost right now.
You have created a high-order function by returning a function in verificaCache(), so to properly call it you need to do it like that await verificaCache(cep)(req, res), remember, the first time you call it, you have a function being returned, since you want the tasks inside of that function to be executed, you need to call it as well.
Take a reading about high-order functions here: https://blog.alexdevero.com/higher-order-functions-javascript/
My recommendation, you could just get rid of the other function you are returning to simplify your code, and let the service only handle business logic, all the http actions should be handled on the controller level:
// Service
function verificaCache(cep) {
const value = myCache.get(cep);
if (value) {
return { city: value, message: 'Data from the cache'})
}
// No need of an else statement because the
// execution will stop at the first return if the condition passes
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
return { city: resp.data, message: 'Data not from the cache'};
}
// Controller
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
try {
const data = verificaCache(cep);
// Use json() instead of send()
response.status(200).json(data);
} catch(error) {
// Handle errors here
console.log(error);
}
});
Estamos juntos!

Mocking Secrets Manager module for JavaScript jest unit tests

I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});

Categories