How to test a axios services which is using interceptors? - javascript

export const http = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL,
headers: {
"CONTENT-TYPE": "application/json",
ACCEPT: "application/json",
},
});
/**
* Add access token in the authorization header of the given config.
*
* #param {Object} axiosConfig Axios config object.
* #param {String} accessToken
* #returns {Object} Axios config object.
*/
const configWithAccessToken = (
axiosConfig: AxiosRequestConfig,
accessToken: string | null
): AxiosRequestConfig => ({
...axiosConfig,
headers: {
...axiosConfig.headers,
Authorization: `Bearer ${accessToken}`,
},
});
/**
* Request interceptors for axios instance.
*
* #returns {Object}
*/
http.interceptors.request.use(
(requestConfig: AxiosRequestConfig): AxiosRequestConfig | any => {
const accessToken = getAccessToken();
return configWithAccessToken(requestConfig, accessToken);
},
(requestError) => {
throw requestError;
}
);
/**
* Response interceptor for axios instance.
*
* #returns {Object}
*/
http.interceptors.response.use(
(response) => {
return response?.data;
},
(error) => {
if (isEmpty(error)) {
throw new Error(error);
}
const { data } = error?.response;
throw data;
}
);
Above is my interceptors which is used by all the services, how am i suppose to bypass this interceptors or mock test this service
export const fetchAll = () => {
const url = 'somethingurl/students';
return http.get(url);
};
This is my service and I want to test this in jest. I have tried to test this function however, it keeps on triggering interceptors
I tried to test this function using jest mock however it keeps on calling axios interceptors.

Related

Authenticatin for HERE RouteMatching API

I'm trying to use HERE's RouteMatching API of JavaScript
Current full code is here: https://github.com/code4history/ShibeContour/blob/d7e56a7/here_mapmatcher.js
For authentication, I coded like this:
import properties from "properties"
import hmacSHA256 from 'crypto-js/hmac-sha256.js'
import Base64 from 'crypto-js/enc-base64.js'
import fetch from 'node-fetch'
import {promises as fs} from "node:fs"
const getProps = async () => {
return new Promise((res) => {
properties.parse("./credentials.properties", {path: true}, function (error, data) {
res(data)
})
})
}
const getToken = async (props) => {
const nonce = `${performance.now()}`
const timestamp = Math.floor((new Date()).getTime() / 1000)
const parameters = [
"grant_type=client_credentials",
`oauth_consumer_key=${props["here.access.key.id"]}`,
`oauth_nonce=${nonce}`,
"oauth_signature_method=HMAC-SHA256",
`oauth_timestamp=${timestamp}`,
"oauth_version=1.0"
].join("&")
const encoding_params = encodeURIComponent(parameters)
const base_string = `POST&${encodeURIComponent(props["here.token.endpoint.url"])}&${encoding_params}`
console.log(base_string)
const signing_key = `${props["here.access.key.secret"]}&`
const hmac_digest = encodeURIComponent(Base64.stringify(hmacSHA256(base_string, signing_key)))
const headers = {
"Authorization": `OAuth oauth_consumer_key="${props["here.access.key.id"]}",oauth_nonce="${nonce}",oauth_signature="${hmac_digest}",oauth_signature_method="HMAC-SHA256",oauth_timestamp="${timestamp}",oauth_version="1.0"`,
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded"
}
const body = `grant_type=client_credentials`
const response = await fetch(props["here.token.endpoint.url"], {
method: 'post',
body,
headers
})
return response.json()
}
This works well, I got authentication token successfully.
Like this:
{
access_token: 'eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiJIZ0NSaFV4...',
token_type: 'bearer',
expires_in: 86399,
scope: 'hrn:here:authorization::org...'
}
But even I used this access_token, route matching call causes authentication error.
Code is:
const main = async () => {
const props = await getProps()
const token_data = await getToken(props)
const body = await fs.readFile("gps/8DD83AC3-8B5A-4108-9CC0-2B78CF9936EC.kml", {encoding: "UTF-8"})
const headers = {
"Authorization": `Bearer ${token_data.access_token}`,
"Cache-Control": "no-cache",
"Content-Type": "application/octet-stream"
}
const response = await fetch(`https://routematching.hereapi.com/v8/calculateroute.json?routeMatch=1&mode=fastest;car;traffic:disabled&apiKey=${props["here.access.key.id"]}`, {
method: 'post',
body,
headers
})
const respond = await response.json()
console.log(respond)
}
main()
Error response was like this:
{
error: 'Forbidden',
error_description: 'These credentials do not authorize access'
}
What is wrong?
I can't imagine what is wrong.
Finally I found the reason
API URL is not match.
We can find many candidate urls,
https://fleet.api.here.com/2/calculateroute.json
https://routematching.hereapi.com/v8/calculateroute.json
etc...
but true working url is only
https://routematching.hereapi.com/v8/match/routelinks
which we can find in this document.
https://platform.here.com/services/details/hrn:here:service::olp-here:route-matching-8/api-ref
Once I changed API endpoint to this correct one, it works well.

Stuck at 1004 - sign invalid from Tuya API

I tried controlling my GoSund smart socket using Tuya IoT Development Platform, but I'm stuck on this error response when trying to switch its state:
{"code":1004,"msg":"sign invalid","success":false,"t":1658384161392,"tid":"97e938e608bc11eda4f0322e56e3d437"}
The following code is basically slightly modified copy of develop code sample from official Tuya API site with my keys and deviceId pasted(https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc)
When I tried to do the exact same thing using Tuya's site debug device option it just works. When I try to do it using their code sample in a web app, it fails with 1004. Except for the token that is new every time I call this, basically all the request headers are the same as when calling them from Tuya's site. Payload is the same too, but the response is very different.
same request on Tuya website device debugging & in a web app
Adding sign_version: '2.0' to request headers or using different url (const url = /v1.0/iot-03/devices/${deviceId}/commands;) doesn't seem to help.
const config = {
/* openapi host */
//host: 'https://openapi.tuyacn.com',
host: 'https://openapi.tuyaeu.com',
/* fetch from openapi platform */
accessKey: 'I pasted here my Access ID/Client ID from iot.tuya.com',
/* fetch from openapi platform */
secretKey: 'I pasted here my Access Secret/Client Secret from iot.tuya.com',
/* Interface example device_ID */
deviceId: 'I pasted here Device ID of my GoSund smart plug',
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async main(switchValue: boolean) {
try{
await this.getToken();
const data = await this.getDeviceInfo(config.deviceId, switchValue);
console.log('fetch success: ', JSON.stringify(data));
}catch(error){
console.log(error);
}
}
/**
* fetch highway login token
*/
async getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
this.setState({ token: login.result.access_token })
}
/**
* fetch highway business data
*/
async getDeviceInfo(deviceId: string, switchValue: boolean) {
const query = {};
const method = 'POST';
const url = `/v1.0/devices/${deviceId}/commands`;
const reqHeaders: { [k: string]: string } = await this.getRequestSign(url, method, {}, query);
const { data } = await httpClient.request({
method,
data: {commands: [{code: "countdown_1", value: 0}, {code: "switch", value: switchValue}]},
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
}
/**
* HMAC-SHA256 crypto function
*/
async encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* #param path
* #param method
* #param headers
* #param query
* #param body
*/
async getRequestSign(
path: string,
method: string,
headers: { [k: string]: string } = {},
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const t = Date.now().toString();
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const client_id = config.accessKey
const access_token = this.state.token
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = client_id + access_token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
sign_version: '2.0',
access_token: access_token
};
}
Looks like you're not passing the body to the signature method.
The whole request needs to be signed including any body. You can't change the request details after signing it, except to add the sign header.
It's probably worth structuring your call into three steps - one to build up the request object. One to add the signing header based on the whole request object (so it's responsible for signing the right fields). Then finally send it to httpClient.request to make the call.
I presume there's a bit of left over "trying things out to get it working" in your code, e.g. setting the url to the requestHeaders.path. And I think you need a timestamp header in there too. All should be in the docu, or look at Tuya's postman collection's pre-request script.
Their example script has a few errors:
In getDeviceInfo():
set method to GET instead of POST
set url to "/v1.0/iot-03/devices/${deviceId}/functions" or "/v1.0/iot-03/devices/${deviceId}/specification"
return data; at the end, so it gets output
This made it work for me.
Ah, and this answer relates to today's version of their example:
import * as qs from 'qs';
import * as crypto from 'crypto';
import { default as axios } from 'axios';
let token = '';
const config = {
/* openapi host */
host: 'https://openapi.tuyacn.com',
/* fetch from openapi platform */
accessKey: '',
/* fetch from openapi platform */
secretKey: '',
/* Interface example device_ID */
deviceId: '',
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async function main() {
await getToken();
const data = await getDeviceInfo(config.deviceId);
console.log('fetch success: ', JSON.stringify(data));
}
/**
* fetch highway login token
*/
async function getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
token = login.result.access_token;
}
/**
* fetch highway business data
*/
async function getDeviceInfo(deviceId: string) {
const query = {};
const method = 'POST';
const url = `/v1.0/devices/${deviceId}/commands`;
const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, query);
const { data } = await httpClient.request({
method,
data: {},
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
}
/**
* HMAC-SHA256 crypto function
*/
async function encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* #param path
* #param method
* #param headers
* #param query
* #param body
*/
async function getRequestSign(
path: string,
method: string,
headers: { [k: string]: string } = {},
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const t = Date.now().toString();
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = config.accessKey + token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
access_token: token,
};
}
main().catch(err => {
throw Error(`error: ${err}`);
});

How can I pass auth headers to other components

So I have recently stared to work with react, I am authenticating a user in my App component like this:
App
signIn(userData) {
console.log(userData)
//do a fetch call to get/users
axios.get('http://localhost:5000/api/users', {
auth: { //set auth headers so that userData will hold the email address and password for the authenticated user
username: userData. emailAddress,
password: userData.password
}
}).then(results => { console.log(results.data)
this.setState({
//set the authenticated user info into state
emailAddress: results.data,
password: results.data.user
});
})
}
and I also have another component called CreateCourse that allows a post request only if I provided the auth header from App,
CreateCourse
handleSubmit = event => {
event.preventDefault();
console.log(this.props)
const newCourse = {
title: this.state.title,
description: this.state.description,
estimatedTime: this.state.estimatedTime,
materialsNeeded: this.state.materialsNeeded
};
axios({
method: 'post',
url: 'http://localhost:5000/api/courses',
auth: {
username: this.props.emailAddress,
password: this.props.password
},
data: newCourse
}).then(
alert('The course has been successfully created!')
).then( () => {
const { history } = this.props;
history.push(`/`)
})
};
I was wondering if I could pass the auth header from App to the children components without using props or context api so that I don't have to manually put the auth headers on every axios request, for reference this is my repo : https://github.com/SpaceXar20/full_stack_app_with_react_and_a_rest_api_p10
I always create a singleton axios instance and set header for it after user signin successful.
let instance = null
class API {
constructor() {
if (!instance) {
instance = this
}
this.request = Axios.create({
baseURL: 'http://localhost:5000',
})
return instance
}
setToken = (accessToken) => {
this.request.defaults.headers.common.authorization = `Bearer ${accessToken}`
}
createCourses = () => this.request.post(...your post request...)
}
export default new API()
After your login successfull, you need call API.setToken(token). Then, when you call Api.createCourse(), the request will have token in headers.
singleton axios instance is the right approach . In the same pattern, use the below method .Import the file wherever required and use axiosapi.get .
const axiosConfig = {auth: {username: XXXX, password: YYYY}};
const axiosservice = axios.create(axiosConfig);
export const axiosapi = {
/**
* Describes the required parameters for the axiosapi.get request
* #param {string} url
* #param {Object} config - The configfor the get request (https://github.com/axios/axios#request-config)
*
* #returns {Promise}
*/
get: (url, config = {}, params) => {
return axiosservice.get(url, {
params,
responseType: 'json',
transformResponse: [
data => {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data;
return get(parsedData);
},
],
...config,
})
.then()
.catch(error => {
return Promise.reject(error);
});
},
}

Make cache persist even if Node process dies

My backend, Nodejs, has to make some API GET request calls to external services to retrieve data and serve as a webpage. These API calls are being cached.
Since I'm using Heroku, this data is being removed every time the apps enter to hibernate state. Is there any cache library that persists? Currently I'm using lru-cache and axios-extensions.
const axios = require('axios');
const { cacheAdapterEnhancer } = require('axios-extensions');
const LRUCache = require("lru-cache")
const options = {
defaultCache: new LRUCache({ maxAge: 60 * 60 * 1000, max: 100 })
}
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000,
adapter: cacheAdapterEnhancer(axios.defaults.adapter)
}
)
getData: async () => {
try {
const response = await http.get(url, config)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}
As suggested by #giankotarola in comments, I needed to use cache DB to solve the problem. Finally I managed to make it work using Redis! My final code:
const axios = require('axios')
const redis = require('redis')
// create and connect redis client to local instance.
const client = redis.createClient()
// Print redis errors to the console
client.on('error', (err) => {
console.log("Error " + err)
});
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000
}
)
let config = {
headers: {
Authorization: `Bearer ${API_KEY}`,
}
}
let url = 'https://example.com'
module.exports={
getData: async () => {
try {
// Try to get response from Redis store
const response = await client.get(url)
return responseJSON = JSON.parse(response)
} catch (err) {
try {
const response = await http.get(url, config)
const data = response.data
// Save response in Redis store
client.setex(url, 60 * 60 * 1000, JSON.stringify(data));
return data
} catch (error) {
console.log(error)
}
}
}
}

Async Functions returning undefined

Im working on fetching data from a GraphQL Server and I'm attempting to implement ES7 Async functions through babel. I'm currently receiving undefined in the console and I'm not sure what I'm doing wrong.
import fetch from 'isomorphic-fetch';
/**
* [transport creates call to server with isomorphic-fetch]
* #param {[String]} path [url to hit with request]
* #param {[Object]} query [The GraphQL query/mutation]
* #param {[Object]} queryParams = {} [Params to pass into query]
* #return {[Promise]} [Promise containing payload]
*/
//function that returns a promise
export function transport (path, query, queryParams = {}) {
return new Promise ((resolve, reject) => {
return fetch(path, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify({
query,
queryParams
})
})
.then(res => res.json())
.then(response => {
if(response.errors) {
return error(response.errors);
}
return resolve(response.data);
})
.catch(error);
});
}
import { transport } from './utils/transport.js';
/**
* [reachGraphQL Makes queres or mutations against GraphQL]
* #param {[String]} path [path to the GraphQL server]
* #param {[Object]} query [The query that GraphQL will use to fetch your data]
* #param {[object]} queryParams = {} [Should contain object with different query params]
* #return {[Object]} [Data that was queried or mutated]
*/
//Heres Where I'm awaiting a promise from the transport function
export function reachGraphQL (path, query, queryParams = {}) {
async () => {
try{
let response = await transport(path, query, queryParams);
return response;
} catch (error) {
console.log(error)
}
}
}
Your reachGraphQL just defines an async arrow function but doesn't do anything with it. And it doesn't return anything. Rather, it should be async itself:
export async function reachGraphQL (path, query, queryParams = {}) {
try {
return await transport(path, query, queryParams);
} catch (error) {
console.log(error)
}
}

Categories

Resources