How to catch 3rd party api error response using NestJS' httpService? - javascript

Suppose i make a request using httpService like this
const response = await this.httpService
.request({
url: 'https://example.com/data',
method: 'POST',
})
.toPromise()
.catch(error => console.log(error))
Assume that example.com/data api sends a message like "Cannot send data without specifying user id." on this request (if i make this request via curl)
Now, if i use httpService like in the codeblock above i get this ambiguous message:
Error: Request failed with status code 400 at createError (/workspace/node_modules/axios/lib/core/createError.js:16:15) at settle (/workspace/node_modules/axios/lib/core/settle.js:17:12) at IncomingMessage.handleStreamEnd (/workspace/node_modules/axios/lib/adapters/http.js:236:11) at IncomingMessage.emit (events.js:203:15) at endReadableNT (_stream_readable.js:1145:12) at process._tickCallback (internal/process/next_tick.js:63:19)"
but if i write the catch statement like this
.catch(error => {
const errorLog = _.pick(error.response, [
'status',
'statusText',
'data',
]);
this.logger.error(
util.inspect(
{
...errorLog,
user: { id: user.id },
},
{ showHidden: false, depth: null },
),
);
throw new InternalServerErrorException();
});
I'll get the 3rd party api response in my logs "Cannot send data without specifying user id."
So, is there a way to make the httpService behave this way by default? like an interceptor or something?

You can try like this:
const response = await this.httpService
.request({
url: 'https://example.com/data',
method: 'POST',
}).pipe(
catchError((e) => {
throw new HttpException(e.response.data, e.response.status);
})
)

Related

How to stub an incoming http request (response) with cypress

How can I stub a response of a HTTP request?
Let me explain it with my code I have now:
Cypress.Commands.add("FakeLoginWithMsal", (userId) => {
cy.intercept('**/oauth2/v2.0/token', (req) => {
req.reply({
token_type: "Bearer",
expires_in: 3795,
access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJS"
})
req.continue((res) => {
})
})
With this code I am trying to stub the response for the following request:
But it still gives the following error, where I can understand the stub did not work:
We attempted to make an http request to this URL but the request
failed without a response.
I've tried already different intercept methods of cypress but I couldn't get worked.
I even can't intercept the /token endpoint with the following:
cy.intercept({
method: 'POST',
url: 'https://login.microsoftonline.com/xx-xx-xx-xx-/oauth2/v2.0/token',
}).as('apiCheck')
Update:
#Fody Thankyou vey much (again) for you respond. Actually I am trying to stub all the MSAL endpoints. It is not a testscript, but a login command.
Here it is:
Cypress.Commands.add("FakeLoginWithMsal", (userId) => {
cy.intercept('GET', '**/authorize', { fixture: 'authorizeStub.json' })
cy.intercept('GET', '**/v2.0/.well-known/openid-configuration', { fixture: 'openidConfigurationStub.json' })
cy.request({
method: 'POST',
url: 'https://login.microsoftonline.com/xxxxx/oauth2/v2.0/token',
body: {
grant_type: "password",
client_id: "xxxxxxxxxxxxx",
client_secret: "xxxxxxxxxxx",
scope: "api://xxxxxxxxxxxxxx/Cp.Use",
username: "xxx#xxxx.com",
password: "xxxxx",
},
form: true,
}).then(response => {
cy.log(JSON.stringify(response))
cy.intercept('response', { fixture: 'tokenStub.json' })
})
These are 3 endpoints, namely:
GET: /authorize (stubbed with a fixture)
GET: /openid-configuration (stubbed with a fixture)
Post: /token --> This POST has a response and there inside is the accesstoken. This response I need to stub.
And I guess that this response is a "incoming HTTP request" (see attachments). This incoming http response is exactly what I want to stub in a fixture.
I'm not sure without seeing the whole test, but are you are issuing the POST to microsoftonline from within the test using cy.request()?
If so, you can't use cy.intercept() to catch it, only requests from the app will be caught.
But you can append a .then() to the cy.request() to wait for the response.
cy.request({
method: 'POST',
url: 'https://login.microsoftonline.com/.../oauth2/v2.0/token',
})
.then(response => {
// handle response
})
Also in this code req.reply() and req.continue() you are both stubbing (with reply) and continuing to the server (with continue), which are opposite actions. You would only want to do one or the other.
cy.intercept('**/oauth2/v2.0/token', (req) => {
req.reply({
token_type: "Bearer",
expires_in: 3795,
access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJS"
})
req.continue((res) => {
})
})

How to print the request logs (like request url,request body, request queryParam) on supertest failure?

I want to print or console log below details on failure for Supertest expect failure
for the below request on success no need to print any thing on success
const result = await request(dummy_url).get("repos/Microsoft/TypeScript/pulls")
.set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0")
.expect(200)
.then(response => {
console.log("success");
console.log(response);
return response;
}).catch(error => {
console.log("error");
// console.log(error);
return 2;
})
console.log(result)
done();
on failure lets say I modify the url as dummy instead of pull,
I need to know the request url, path parma and request body if any,
currenly on trying to add it in the above way it only gives the below error
Error: expected 200 "OK", got 404 "Not Found"
at Object.<anonymous> (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/__tests__/github-routes/jest.test.ts:40:8)
at /Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at processTicksAndRejections (node:internal/process/task_queues:96:5)
----
at Test.Object.<anonymous>.Test._assertStatus (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:296:12)
at /Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:80:15
at Test.Object.<anonymous>.Test._assertFunction (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:311:11)
at Test.Object.<anonymous>.Test.assert (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:201:21)
at localAssert (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:159:12)
at fn (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/supertest/lib/test.js:156:5)
at Test.callback (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/superagent/src/node/index.js:902:3)
at fn (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/superagent/src/node/index.js:1130:18)
at IncomingMessage.<anonymous> (/Users/thoughtworks/projects/api-test-supertest-jest-typescript/node_modules/superagent/src/node/parsers/json.js:19:7)
at Stream.emit (node:events:365:28)
things to note,
I am using this in Typescript, inside the async function, thought this is not a blocker for now.
After multiple try and attempts, I was able to come up with a function that logs the request details, I pass on the response from supertest along with the expected status code.
On failure, the function will log the details of request and response like path parm, query parm, req body
import SuperTest from "supertest";
export const checkStatusCode = (res: any, expectedStatus: any = 200): SuperTest.Response => {
if (res.status === expectedStatus) {
return res
};
const error = res.error;
const reqData = JSON.parse(JSON.stringify(res)).req;
throw new Error(`
request-method : ${JSON.stringify(reqData.method)}
request-url : ${JSON.stringify(reqData.url)}
request-data : ${JSON.stringify(reqData.data)}
request-headers : ${JSON.stringify(reqData.headers)}
reponse-status : ${JSON.stringify(res.status)}
reponse-body : ${JSON.stringify(res.body)}
`
);
};
usage in jest test file
describe("Jest - Api - user", () => {
it("Verify POST ", async () => {
const res = await request(url.someurl)
.post("/dummy")
.set("Authorization", authToken)
.send(updateThirdParty)
checkStatusCode(res, 200)
})
})
The solution is inspired from one of the suggestion in supertest github issue.
Thanks to sakovias.
Note: this is function that logs the data, we can still have this as wrapper to the expect itself, which I will post as separate thread.

How to correctly manage fetch errors response with observables and RxJS?

I just recovered a simple auth app built with ReactJS, redux-observable and RxJS.
I want to implement an error handler when the API backend send a response > 400.
Actually, there is an epic that handle login process on LOGIN_REQUEST event:
const apiLoginEpic = (action$, state$) =>
action$.pipe(
ofType(authConstant.LOGIN_REQUEST),
mergeMap(action => authService.loginApi(action.username, action.password)),
map(({ body }) => extractJwtPayload(body.token)),
map(authAction.login.apiSuccess),
logObservableErrorAndTriggerAction(authAction.login.apiFailure)
This epic just call a service, which is a simple fetch that refer to:
const fetchBackend = (path, options = {}, withCredentials = false) => {
fetch(`${process.env.REACT_APP_BACKEND_URL}${path}`, options)
.then(response =>
response.status < 400
? Promise.all([response, response.json()])
: Promise.reject(response)
)
.then(([response, body]) => ({
status: response.status,
headers: response.headers,
body,
}))
}
As you can see, when the response status is superior to 400, it reject the promise. Then, in the epic (first code extract), the error is catch in logObservableErrorAndTriggerAction function which is implemented as:
const logObservableErrorAndTriggerAction = action =>
catchError((response, source) =>
pipe(
tap(logger.error),
() => of(action(response)).pipe(merge(source))
)(response)
)
My problem is that I want to catch the response body in logObservableErrorAndTriggerAction, which can be reusable.
Unfortunately, I don't have much knowledge about observalbes and RxJS.
When I try to use body of the response, it appears that it is a ReadableStream. I tried a lot of things but I can't turn it into JSON :
{
body: ReadableStream,
bodyUsed: false,
headers: Headers {},
ok: false,
redirected: false,
status: 400,
statusText: "",
type: "cors",
url: "http://localhost/api/login"
}
Does anybody know how to manage my use case ?
Thank you very much,
Sylvain

Bot Framework V4 - TypeError: Cannot perform 'get' on a proxy that has been revoked

I am trying to make a rest query against a database that stores knowledge articles for users and returns an array of results based on what the user has searched for. Whenever I try to search I get:
"TypeError: Cannot perform 'get' on a proxy that has been revoked"
I have tried adding it to async as shown but I still keep getting the same error. Any idea what I am doing wrong?
const Response = async (turnContext) => {
if (turnContext.activity.value.choice === 'feedbackProvider') {
try {
const feedbackBody = turnContext.activity.value.feedbackBody;
const feedbackEmail = turnContext.activity.value.feedbackEmail;
storage.write(feedbackBody, feedbackEmail);
await turnContext.sendActivity(`Feedback Sent`);
} catch (err) {
console.log('fetch failed', err);
}
} else if (turnContext.activity.value.choice === 'issueRaiser') {
try {
const bugTitle = turnContext.activity.value.issueTitle;
const bugDesc = turnContext.activity.value.issueDescription;
const bugEmail = turnContext.activity.value.issueEmail;
const request = require('request');
request({
method: 'Post',
uri: `<uri>issues?title=${ bugTitle }&description=${ bugDesc } ${ bugEmail }&labels=bug`,
json: true,
headers: {
'Private-Token': '<token>'
}
});
turnContext.sendActivity(`Issue Raised`);
} catch (err) {
console.log('fetch failed', err);
}
} else if (turnContext.activity.value.choice === 'knowledgeBaseSearch') {
try {
const knowledgeBaseTopic = turnContext.activity.value.knowledgeBaseTopic;
request({
url: process.env.SN_KB_URL + knowledgeBaseTopic,
json: true,
auth: {
'username': process.env.Ticket_User,
'password': process.env.Ticket_Key
}
}, async (error, response, body) => {
try {
var stuff = [];
for (var i = 0, len = body.result.length; i < len; i++) {
stuff.push(
CardFactory.heroCard(body.result[i].short_description, ['imageUrl1'], [`${ process.env.SN_KB_Resp_URl }${ body.result[i].number }`])
);
}
let messageWithCarouselOfCards = MessageFactory.carousel(stuff);
await turnContext.sendActivity(messageWithCarouselOfCards);
} catch (err) {
console.log(error);
}
});
} catch (err) {
console.log('fetch failed', err);
}
}
};
Full Error Message:
TypeError: Cannot perform 'get' on a proxy that has been revoked
cardMiddleware.js:35
at Request.request [as _callback] (c:\Bots\sdk4-2\skills\cardMiddleware.js:35:45)
at Request.self.callback (c:\Bots\sdk4-2\node_modules\request\request.js:186:22)
at emitTwo (events.js:126:13)
at Request.emit (events.js:214:7)
at Request.<anonymous> (c:\Bots\sdk4-2\node_modules\request\request.js:1163:10)
at emitOne (events.js:116:13)
at Request.emit (events.js:211:7)
at IncomingMessage.<anonymous> (c:\Bots\sdk4-2\node_modules\request\request.js:1085:12)
at Object.onceWrapper (events.js:313:30)
at emitNone (events.js:111:20)
From my post on the forum I was informed that I was using a request module that did not support Promises, which I believe was causing my error. I've now began to use Axios for my request which is shown below;
try {
return await axios.get(process.env.SN_KB_URL + knowledgeBaseTopic, {
headers: {
auth: {
username: process.env.Ticket_User,
password: process.env.Ticket_Key
}
}
})
}
However now when I run the request I get a 401 'Unauthorised' error and I'm not sure what is wrong with my request.
This issue happened because I was using a request module that did not support promises. Changing my request module for one that did support promises (which I found out about by using this article) resolved the issue.
The answer for me was to double check I didn't miss any await usage that might be necessary. Turns out I called this.dialog.run(context, this.dialogState); without the await and that threw the same error. I found the answer on this Github issue
I spent a lot of time struggling with this issue. As other commenters have noted, the issue lies in the fact that the Lex Runtime is not promise-based, so you cannot await requests, which causes the proxy to be revoked.
Here is my solution:
async callLex(context) {
const params = {
botAlias: 'prod',
botName: 'botName',
userId: context.activity.from.id,
contentType: 'text/plain; charset=utf-8',
accept: 'text/plain; charset=utf-8',
inputStream: context.activity.text.trim()
}
let request = lexruntime.postContent(params)
await request.promise().then(
async response => {
console.log(response)
console.log('Success!')
await context.sendActivity(response.message)
},
err => {
console.log(err)
console.log('Error!')
})
}
Rather than directly invoke the request like "lexruntime.postContent(params, callback func)", I exclude the callback function and utilize the "promise" property of AWS.Request to send the request as a promise which enables me to use "await" and keeps the proxy open. See documentation here.
I'm going to put this here only because it's the first result that pops up when searching, although it doesn't directly relate to this issue.
There's a very easy way to use setTimeout() and avoid this error:
await new Promise(resolve => setTimeout(() => resolve(
turnContext.sendActivity('I was sent 5 seconds later')
), 5000));
In my scenario, we were trying to upload files from a Task Module (modal popup of teams) to the bot and in response the bot would give a first confirmation that the attachments are uploading. This activity would close the task module (as the bot must reply within 10 seconds or teams would resend the request). Now when the attachments were uploaded, we wanted to update the previously sent adaptive card with the list of the uploaded attachments. We achieved this using the proactive messaging feature of bot framework.
const conversationReference = TurnContext.getConversationReference(activity);
Promise.all(listOfPromises).then(() => {
await botAdapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('All attachments uploaded!');
});
}
Docs: https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=javascript
Check for lines that needs await inside any aync function. I hope that azure will point usto which file or line specifically but I have not figured it out until I looked at all my functions.
Okay, so this is indeed a very cryptic error message as the github thread here suggested .
But I found that I was not await ing in this block:
this.onMessage(async (context, next) => {.
const didBotWelcomedUser = await this.welcomedUserProperty.get(
context,
"false"
);
if (didBotWelcomedUser === false) {
// first time user is in chat
await this.sendWelcomeMessage(context); `<-------- await here was missing`
} else {
await this.sendSuggestedAction(context); `<-------- await here was missing`
}
await next();
});
this.onMembersAdded(async (context, next) => {
await this.sendWelcomeMessage(context);
await next();
});
}
I thought await.next() is enough. We all gotta learn this somehow... Hope you resolve yours.

CORS POST request throws on Authorization header

I'm trying to send a CORS POST request to my API and it throws a TypeError every time I use the 'Authorization' header. The request doesn't even get sent, so the server is not involved. But this only happens in my tests. When I try it in Chrome it works just fine.
Here is the function that I'm testing:
export const postNewEmployee = formData => {
return fetch('http://localhost:3003', {
method: 'POST',
headers: {
'Authorization': 'Bearer test123',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response)
.catch(error => {
throw error;
});
};
And its test:
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});
The application is a react/redux app created with create-react-app, so I'm using Jest and JSDOM to test this. The thing is, if I comment out the Authorization header from the fetch()-call, it works fine. But if I add that header I get this:
TypeError: Cannot convert undefined or null to object
at Object.getRequestHeader (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xhr-utils.js:20:23)
at setDispatchProgressEvents (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:909:38)
at XMLHttpRequest.send (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:700:11)
at /Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:429:11
at Object.<anonymous>.self.fetch (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:373:12)
at Object.<anonymous>.exports.postNewEmployee.formData [as postNewEmployee] (/Users/johanh/Kod/react-app/src/api/api.js:20:10)
at Object.it (/Users/johanh/Kod/react-app/src/api/api.test.js:75:16)
at Object.<anonymous> (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/build/jasmine-async.js:42:32)
at attemptAsync (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1919:24)
at QueueRunner.run (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1874:9)
And as I said, this only happens in the test. In the browser it works fine.
I feel like I'm missing something obvious here, but I just can't see it. I've looked in the fetch spec and the jsdom documentation, but to no avail. Any ideas?
Normally you should not make real requests in a unit test. The best way to handle this is to use a mock instead of the real fetch implementation.
I assume you are using the JS implementation of fetch. So you can set fetch to what ever you want in your test.
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
// set fetch to a mock that always returns a solved promise
const fetch = jest.fn((url, options) => return Promise.resolve({status: 201}))
global.fetch = fetch;
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
//test that fetch was called with the correct parameters
expect(fetch.mock.calls[0][0]).toBe('http://localhost:3003')
expect(fetch.mock.calls[0][1]).toEqual(formData)
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});

Categories

Resources