Mocking node-fetch with jest creating a Response Object for mocking - node.js

I am trying to create Response Objects for mocking with jest, I can't seem to get the right syntax.
Initialization,
jest.mock('node-fetch')
const fetch = require('node-fetch')
const { Response, Headers } = jest.requireActual('node-fetch')
// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
const meta = {
'Content-Type': 'application/json',
'Accept': '*/*',
'Breaking-Bad': '<3'
}
// You can in fact use any iterable objects, like a Map or even another Headers
const headers = new Headers(meta)
const copyOfHeaders = new Headers(headers)
const ResponseInit = {
status: 200,
statusText: 'fail',
headers: headers
}
With a basic test
test('Basic Test', async () => {
const token = ''
const getDocList = new Response(JSON.stringify(downloadDocumentData), ResponseInit)
fetch.mockResolvedValueOnce(Promise.resolve(getDocList))
await module.doSomething('mock', token)
.then( async(res) => {
await expect(res.data).toEqual(Object)
})
}, 5000)
I'm getting an error which is
FetchError {
message:
'invalid json response body at reason: Unexpected token H in JSON at position 2',
type: 'invalid-json' }
How can I initial a response for valid json, I have tried a lot of different things.
Following the article at https://jestjs.io/docs/en/bypassing-module-mocks but I want to return and test json instead.

We should use jest.mock(moduleName, factory, options) to mock node-fetch module and fetch function.
In order to construct the response object of the fetch function, you need to use the Response class provided by the node-fetch module, so use jest.requireActual(moduleName) to get the original, unmocked node-fetch Module and Response class.
Of course, we can construct the response object arbitrarily, but the instance of the Response class is really close to the real response.
The same goes for headers object.
Here is a working demo:
index.js:
const fetch = require('node-fetch');
module.exports = {
async doSomething(url, token) {
return fetch(url).then(res => res.json());
}
};
index.spec.js:
jest.mock('node-fetch');
const fetch = require('node-fetch');
const { Response, Headers } = jest.requireActual('node-fetch');
const mod = require('./');
const meta = {
'Content-Type': 'application/json',
Accept: '*/*',
'Breaking-Bad': '<3'
};
const headers = new Headers(meta);
const copyOfHeaders = new Headers(headers);
const ResponseInit = {
status: 200,
statusText: 'fail',
headers: headers
};
test('Basic Test', async () => {
const token = '';
const downloadDocumentData = { data: {} };
const getDocList = new Response(JSON.stringify(downloadDocumentData), ResponseInit);
fetch.mockResolvedValueOnce(Promise.resolve(getDocList));
const res = await mod.doSomething('mock', token);
expect(res).toEqual({ data: {} });
expect(fetch).toBeCalledWith('mock');
});
Unit test result:
PASS src/stackoverflow/58648691/index.spec.js
✓ Basic Test (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.557s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58648691

Related

PUT request works in Postman but not a deno typescript file as a fetch request

I can do a put request just fine in Postman.
But when I try to do the same put request in a deno fresh app through a fetch command like this:
async function sendSignature() {
const signer = provider.getSigner();
const nonce = "asdf123492fd";
const signatureSigned = await signer.signMessage(nonce);
const headers = new Headers({
'Content-Type': 'application/json'
});
const opts = {
method: 'PUT',
headers: headers,
body: JSON.stringify({
key: props.singleUser,
wallet_address: walletAddrs,
signature: signatureSigned
})
}
console.log(opts.body);
const rawPosts = await fetch('http://localhost:4000/users/kythis1', opts);
console.log(rawPosts);
}
Btw all of the values are being populated in body. I can confirm that key, wallet_address, and signature are not null and are strings. It fails though... Here's what the browser's console looks like.
This is the entry point for the backend oak (deno's version of express) server.
import { Application } from "https://deno.land/x/oak/mod.ts";
import { APP_HOST, APP_PORT } from "./config.js";
import router from "./routes.js";
import _404 from "./controllers/404.js";
import errorHandler from "./controllers/errorHandler.js";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
const app = new Application();
app.use(oakCors()); // Enable CORS for All Routes
app.use(errorHandler);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(_404);
console.log(`Listening on port:${APP_PORT}...`);
await app.listen(`${APP_HOST}:${APP_PORT}`);
This is the function that is getting called by the put request:
import User from "../db/database.js";
export default async ({ request, response }) => {
if (!request.hasBody) {
response.status = 400;
response.body = { msg: "Invalid user data" };
return;
}
const body = request.body();
const {
key, wallet_address, signature
} = JSON.parse(await body.value);
console.log(signature);
if (!key) {
response.status = 422;
response.body = { msg: "Incorrect user data. Email and key are required" };
return;
}
const foundUser = await User.where('key', '=', key).first();
if (!foundUser) {
response.status = 404;
response.body = { msg: `User with key ${key} not found` };
return;
}
foundUser.wallet_address = wallet_address;
foundUser.updated_at = new Date();
const updatedResp = await foundUser.update();
response.body = { msg: "User updated", updatedResp };
};
Finally this is the backend routes:
import { Router } from "https://deno.land/x/oak/mod.ts";
import getUsers from "./controllers/getUsers.js";
import getUserDetails from "./controllers/getUserDetails.js";
import createUser from "./controllers/createUser.js";
import updateUser from "./controllers/updateUser.js";
//import deleteUser from "./controllers/deleteUser.js";
const router = new Router();
router
.get("/users", getUsers)
.get("/users/:key", getUserDetails)
.post("/users", createUser)
.put("/users/:key", updateUser);
//.delete("/users/:id", deleteUser);
export default router;
So why can I successfully call this function with a Postman put request, but I can't do a successful put request with fetch through a typescript file?
Your postman and fetch call aren't exactly the same.
Looking at postman it has 8 headers, and content-type seems to be set on Text.
While the fetch() is set on application/json.
When content type is application/json your request.body() is likely already parsed. Thus another JSON.parse will throw an error.
One easy fix with your current code would be to set your javascript headers to this:
const headers = new Headers({
'Content-Type': 'text/plain'
});
This will avoid 2 times parsing of the json file.
But the better fix would be to actually use application/json and see/log what request.body() returns.
Postman simply doesn’t care about CORS headers. So CORS is just a browser concept and not a strong security mechanism. It allows you to restrict which other web apps may use your backend resources.
You have to just specifies CORS (Access-Control-Allow-Origin) headers.

Creating Wallet Address with coinbase API Using Axios Request

Hello I'm trying to use the coinase api using axios to make request. I have set up the neccessary api authentication using SHA256 HMAC. I have been able to make some GET Request and got response. I have been trying to make a POST Request but i have been getting 401 status code.
const name = "Test BTC Address";
const body = {
name: name
}
var encHash = {
baseUrl: 'https://api.coinbase.com',
method: 'POST',
path: '/v2/accounts/bbc2e3f7-a851-50ab-b4b3-a0f2a700846f/addresses',
body: body,
scopes: "wallet:addresses:create"
};
const sign = generateHashKey(encHash, key.APISECRET);
console.log(sign);
const config = {
headers: {
'CB-ACCESS-SIGN': sign.signature,
'CB-ACCESS-TIMESTAMP': sign.timestamp,
'CB-ACCESS-KEY': key.APIKEY,
'CB-VERSION': '2021-10-15'
}
}
const url = `${encHash.baseUrl}${encHash.path}`
console.log(url);
var options = await axios.post(url, body, config);
return res.send({data: options.data})
} catch (error) {
// console.error(error);
return res.send({error})
} ```

Getting LinkedIn access token through http request on node.js server

I am following the Authorization Code Flow (3-legged OAuth) documentation and I am now at step 3 where I need to use the authorization code in order to recieve an access token from LinkedIn. In the project I am using node.js, typescript and the node-fetch library. The following function creates a body with content type x-www--form-urlencoded since this is content type which LinkedIn require.
async function GetAccessToken(data: any) {
let body: string | Array<string> = new Array<string>();
for (let property in data) {
let encodedKey = encodeURIComponent(property);
let encodedValue = encodeURIComponent(data[property]);
body.push(encodedKey + "=" + encodedValue);
}
body = body.join("&");
const response = await fetch("https://www.linkedin.com/oauth/v2/accessToken", {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
body: body
}).then((res: any) => {
console.log("Result", res);
});
return response;
}
I do not recieve any errors and the response status is 200 but the response values I recieve are:
size: 0,
timeout: 0,
and what LinkedIn promise is:
access_token
expires_in
When I post the url with my parameters using postman the request goes through and I recieve the correct data which indicates the problem lies within my request function and not my values.
Any help is appreciated!
You need add all headers from postman
const urlencoded = new URLSearchParams();
urlencoded.append("client_id", env.LINKEDIN_CLIENT_ID);
urlencoded.append("client_secret",env.LINKEDIN_CLIENT_SECRET);
urlencoded.append("grant_type", "authorization_code");
urlencoded.append("code", code);
urlencoded.append(
"redirect_uri",
"http://localhost:3000/api/auth/linkedin-custom"
);
const accessTokenPromise = await fetch(
"https://www.linkedin.com/oauth/v2/accessToken",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: urlencoded,
}
);

Sending DataForm containing a pdf file from a node.js file

I am trying to send a FormData containing a pdf file from a node.js script to a server. The request fail with a status code 400 in response. In the server side, it seems that he do not consider the data as a pdf file, or maybe that that the whole data is undefined.
This is what my node.js script does : (I've tried other ways to send the request but without success)
import axios from "axios"
import fs from "fs-extra"
import FormData from 'form-data'
const formData = new FormData()
const diplomaBuffer = await fs.readFile(`../data/fake/foo`+i+`.pdf`)
formData.append("diploma", diplomaBuffer)
formData.append("diplomaId", 1)
axios({
method: 'post',
processData: false,
contentType: 'multipart/form-data',
cache: false,
url: 'http://localhost:8080/certificates',
data: formData,
config: { headers: formData.getHeaders() }
})
// const response = await axios
// .post(`http://localhost:8080/certificates`, {
// body: formData
// })
// const response = await axios
// .post(`http://localhost:8080/certificates`
// , formData
// , {headers: formData.getHeaders()})
This is the function called in the server side :
app.post('/certificates', async (req, res) => {
const files = req.files
if(!files || !files.diploma || !files.diplomaId) {
res.status(400).send(new Error("No file provided"))
}
if(files.diploma.mimetype !== "application/pdf") {
res.status(400).send(new Error("diploma is not a pdf"))
}
// operations made with the received pdf (not relevant for the question)
const certificateId = uniqid()
const outputDiploma = `data/diplomas/${certificateId}.pdf`
await Promise.all([
fs.writeFile(outputDiploma, files.diploma.data),
])
await pdf.sign(outputDiploma, `${sha("sha256").update(certificateId + diplomaSecret).digest("hex")} : ${date()}`)
const diplomaBuffer = await fs.readFile(outputDiploma)
const certificate_hash = verify_pdf.hashBuffer(diplomaBuffer)
const certificateRegistry = await app.bizNetworkConnection.getAssetRegistry("consortium.supchain.assets.Certificate")
const certificate = app.factory.newResource("consortium.supchain.assets", "Certificate", certificateId)
certificate.diploma = app.factory.newRelationship("consortium.supchain.assets", "Diploma", req.body.diplomaId)
certificate.hashInfo = certificate_hash
await app.registry.certificate.add(certificate)
res.status(200).send("ok")
})

How can I turn this POST method into valid Node.js code?

Using https://mws.amazonservices.com/scratchpad/index.html, I am able to make a valid request to the MWS endpoint, the details of which look like this:
POST /Products/2011-10-01?AWSAccessKeyId=myAWSAccessKeyId
&Action=GetMatchingProductForId
&SellerId=mySellerId
&SignatureVersion=2
&Timestamp=2018-08-14T01%3A00%3A39Z
&Version=2011-10-01
&Signature=6xwEi3Mk9Ko9v9DyF9g6zA4%2Buggi7sZWTlUmNDxHTbQ%3D
&SignatureMethod=HmacSHA256
&MarketplaceId=ATVPDKIKX0DER
&IdType=UPC
&IdList.Id.1=043171884536 HTTP/1.1
Host: mws.amazonservices.com
x-amazon-user-agent: AmazonJavascriptScratchpad/1.0 (Language=Javascript)
Content-Type: text/xml
How can I take this, and turn it into a valid URL that I can use to make a request from my app, i.e., using fetch or some other javascript implementation. I tried to take the info and make a URL like this:
https://mws.amazonservices.com/Products/2011-10-01?
AWSAccessKeyId=myAWSAccessKeyId
&Action=GetMatchingProductForId
&SellerId=mySellerId
&SignatureVersion=2
&Timestamp=2018-08-14T01%3A00%3A39Z
&Version=2011-10-01
&Signature=6xwEi3Mk9Ko9v9DyF9g6zA4%2Buggi7sZWTlUmNDxHTbQ%3D
&SignatureMethod=HmacSHA256
&MarketplaceId=ATVPDKIKX0DER
&IdType=UPC
&IdList.Id.1=043171884536
, to which I tried to send a POST request via postman, and I got this error:
<?xml version="1.0"?>
<ErrorResponse xmlns="https://mws.amazonservices.com/">
<Error>
<Type>Sender</Type>
<Code>InvalidParameterValue</Code>
<Message>Value 2
for parameter SignatureVersion is invalid.</Message>
</Error>
<RequestID>6ded1eed-eb92-4db6-9837-3453db0f8a77</RequestID>
</ErrorResponse>
How can I make a valid request to an MWS endpoint using javascript?
You could use npm module like superagent, axios or request.
const agent = require('superagent)
agent
.post('https://mws.amazonservices.com/Products/2011-10-01')
.query({
AWSAccessKeyId: myAWSAccessKeyId,
Action: GetMatchingProductForId,
SellerId: mySellerId,
SignatureVersion: 2,
Timestamp: 2018-08-14T01%3A00%3A39Z,
Version: 2011-10-01,
Signature: 6xwEi3Mk9Ko9v9DyF9g6zA4%2Buggi7sZWTlUmNDxHTbQ%3D,
SignatureMethod: HmacSHA256,
MarketplaceId: ATVPDKIKX0DER,
IdType: UPC,
IdList.Id.1: 043171884536
})
.then(res => {
console.log('here is the response');
console.log(res)
})
.catch(error => {
console.log('here is the error');
console.log(error);
})
I haven't written against AWS but are you sure that these parameters should be sent with the querystring. Usually with post, parameters are sent with the body?
The error you are recieving from Postman is telling you that you are reaching the server but something is wrong the with values that you are sending. For example: SignatureVersion should be 1 (or something).
I used node-fetch to make a valid request the MWS endpoint. You can look code as given below.
var param = {};
param['AWSAccessKeyId'] = 'xxxxxxxxxxxxx';
param['Action'] = 'GetMatchingProductForId';
param['MarketplaceId'] = 'xxxxxxxxxxxxx';
param['SellerId'] = 'xxxxxxxxxxxxx';
param['IdType'] = 'ISBN';
param['IdList.Id.1'] = 'xxxxxxxxxx';
param['ItemCondition'] = 'New';
param['SignatureMethod'] = 'HmacSHA256';
param['SignatureVersion'] = '2';
param['Timestamp'] = encodeURIComponent(new Date().toISOString());
param['Version'] = '2011-10-01';
secret = 'xxxxxxxxxxxxx';
var url = [];
for(var i in param){
url.push(i+"="+param[i])
}
url.sort();
var arr = url.join("&");
var sign = 'POST\n'+'mws.amazonservices.com\n'+'/Products/2011-10-01\n'+arr;
const crypto = require('crypto');
let s64 = crypto.createHmac("sha256", secret).update(sign).digest('base64');
let signature = encodeURIComponent(s64);
var bodyData = arr+"&Signature="+signature;
await fetch('https://mws.amazonservices.com/Products/2011-10-01', {
method: 'POST',
body: bodyData,
headers: {
'content-type': 'application/x-www-form-urlencoded',
'Accept': '',
},
})
.then(res => {
console.log(res)
})
.catch(error => {
console.log('Request failed', error);
});
}
amazon-mws package is also available for Node.
https://www.npmjs.com/package/amazon-mws

Resources