Trouble testing my Deno/Oak error handling middleware - deno

So I have a simple middleware that listens for errors and removes details to make them friendlier:
import {
isHttpError,
Status,
isProduction,
Middleware
} from "../deps.ts";
import { Context } from "./../types.ts";
const errorMiddleware: Middleware = async (ctx: Context, next: () => Promise<unknown>) => {
try {
await next();
} catch (err) {
let message = err.message;
const status = err.status || err.statusCode || Status.InternalServerError;
/**
* considering all unhandled errors as internal server error,
* do not want to share internal server errors to
* end user in non "development" mode
*/
if (!isHttpError(err)) {
message = !isProduction
? message
: "Internal Server Error";
}
if (!isProduction) {
console.log(err);
}
ctx.response.status = status;
ctx.response.body = { status, message };
}
};
export { errorMiddleware };
I'm trying to write a test that throws a random error and checks to see if the middleware is adjusting the message as expected.
This is what I've tried:
Deno.test({
name: "Middleware changes error message to Internal Server Error",
async fn() {
const mw1: Middleware = async (_context, next) => {
await next();
};
let caught: any;
const mockContext = testing.createMockContext();
const mockNext = () => {
return new Promise<void>((resolve) => {
setTimeout(() => {
mockContext.throw(500, "Hi there");
resolve();
}, 50);
});
};
try {
await mw1(mockContext, mockNext); // hoping this throws the error into the next middleware
await errorMiddleware(mockContext, testing.createMockNext());
} catch (error) {
caught = error;
}
assert(caught instanceof httpErrors.InternalServerError);
assertEquals(caught.message,"Internal Server Error");
},
});
The test doesn't work, I just get this error:
Is there a different way I should be approaching the tests? I've tried to look at the tests in the oak library to see if there's similar examples but I couldn't seem to find any.

It's not possible to reproduce your issue because you have not provided all of the necessary code, so here's a similar, self-contained example to demonstrate how to test an error renaming middleware with Oak. Also, see the internal Oak middleware module tests for reference.
so-69820660.test.ts
import { delay } from "https://deno.land/std#0.113.0/async/mod.ts";
import {
assert,
assertEquals,
} from "https://deno.land/std#0.113.0/testing/asserts.ts";
import {
composeMiddleware,
httpErrors,
Middleware,
Status,
testing,
} from "https://deno.land/x/oak#v9.0.1/mod.ts";
const renameError: Middleware = async (_, next) => {
try {
await next();
} catch (exception: unknown) {
if (!(exception instanceof Error)) throw new Error(String(exception));
exception.message = "Custom error message";
throw exception;
}
};
Deno.test({
name: "Middleware renames error message",
async fn() {
let exceptionWasCaught = false;
const delayAndThrow: Middleware = async (ctx) => {
await delay(50);
ctx.throw(Status.InternalServerError, "Uh oh");
};
const catchAndAssert: Middleware = async (_, next) => {
try {
await next();
} catch (exception: unknown) {
exceptionWasCaught = true;
assert(exception instanceof httpErrors.InternalServerError);
assertEquals(exception.message, "Custom error message");
}
};
const mw = composeMiddleware([catchAndAssert, renameError, delayAndThrow]);
await mw(testing.createMockContext());
assert(exceptionWasCaught);
},
});
% deno --version
deno 1.15.3 (release, x86_64-apple-darwin)
v8 9.5.172.19
typescript 4.4.2
% deno test so-69820660.test.ts
running 1 test from file:///Users/deno/so-69820660.test.ts
test Middleware renames error message ... ok (65ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (261ms)

Related

How to handle next api route using dynamic id

I am using NextJS and created three layers that separate the logic.
The purpose is to minimise the error handling to the getServerSideProps section. I want to get all the lists from the database.
In the first layer, in the API route I created a folder called get-all-lists and a file [userId].js. The get request will be 'http://localhost:3000/api/get-all-lists/iudga937gr8'. Bellow there is the api route that get all the lists with the help of Prsima. It is working perfectly
import prisma from '../../../lib/prisma'
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await prisma.List.findMany({ where: { userId: userId }});
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
The next layer, is the abstraction one which sent the final result to getServerSideProps. I created this because I need to fetch alot of requests and it would be too messy...
export const getAllLists = async userId => {
try {
const lists = await axios.get(`/api/get-all-lists/${userId}`);
return lists;
}
catch (error) {
console.log('Abstraction layer error: ', error);
return 'Something went wrong. Please try again later';
}
}
The problem arise here. In the postman I have the right result. In postman I use http://localhost:3000/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d and the server sent me the array specified.
But this function does not work and send me this error:
Abstraction layer error: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:176:20)
at new Promise (<anonymous>)
at http (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:112:10)
at Axios.dispatchRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/dispatchRequest.js:51:10)
at Axios.request (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:142:33)
at Axios.<computed> [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:168:17)
at Function.wrap [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/helpers/bind.js:5:15) {
input: '/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d',
code: 'ERR_INVALID_URL'
}
I also tried to paste the localhost in the browser and it have no problem.
You could extract the functionality into /lib/getAllList.js:
import prisma from './prisma';
export default async function getAllLists(userId) {
return await prisma.List.findMany({ where: { userId: userId }});
}
Then use it in your API route:
import getAllLists from '../../../lib/getAllLists';
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await getAllLists(userId);
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
Then use it in getServerSideProps:
import getAllLists from 'path/to/lib/getAllLists';
export async function getServerSideProps(context) {
const { userId } = context.params;
const shoppingLists = await getAllLists(userId);
return {
props: {
shoppingLists,
},
};
}

Server Error Error: Request failed with status code 500

export const getServerSideProps = async () => {
const res = await axios.get('http://localhost:3000/api/products');
return {
props: {
kebabList: res.data
}
}
};
// get all the products
if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
res.status(500).json(err);
}
}
When I first load the app it is showing this error
Server Error
Error: Request failed with status code 500
If I reload again then it works fine but after deploying on vercel the error is permanent
When you have deployed your app on vercel, your API url might be changed http://localhost:3000 to http://your-domain(live-url).com.
export const getServerSideProps = async () => {
const res = await axios.get('http://your-domain(live-url).com/api/products'); // please focus on this line. I belive you have messed up here
return {
props: {
kebabList: res.data
}
}
};
// get all the products
if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
res.status(500).json(err);
}
}

Jasmine 4: Async function did not complete within 5000ms issue

I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});

How to test asynchonous functions using sinon?

I have a class called PostController, and I trying to test the following function create:
class PostController {
constructor(Post) {
this.Post = Post;
}
async create(req, res) {
try {
this.validFieldRequireds(req);
const post = new this.Post(req.body);
post.user = req.user;
...some validations here
await post.save();
return res.status(201).send(message.success.default);
} catch (err) {
console.error(err.message);
const msg = err.name === 'AppError' ? err.message :
message.error.default;
return res.status(422).send(msg);
}
}
My test class is:
import sinon from 'sinon';
import PostController from '../../../src/controllers/posts';
import Post from '../../../src/models/post';
describe('Controller: Post', async () => {
it.only('should call send with sucess message', () => {
const request = {
user: '56cb91bdc3464f14678934ca',
body: {
type: 'Venda',
tradeFiatMinValue: '1',
... some more attributes here
},
};
const response = {
send: sinon.spy(),
status: sinon.stub(),
};
response.status.withArgs(201).returns(response);
sinon.stub(Post.prototype, 'save');
const postController = new PostController(Post);
return postController.create(request, response).then(() => {
sinon.assert.calledWith(response.send);
});
});
});
But I'm getting the following error:
Error: Timeout of 5000ms exceeded. For async tests and hooks, ensure
"done()"
is called; if returning a Promise, ensure it resolves.
(D:\projeto\mestrado\localbonnum-back-end\test\unit\controllers\post_spec.js)
Why?
Most probably it's because misuse of sinon.stub.
You've
sinon.stub(Post.prototype, 'save');
without telling what this stub will do, so in principle this stub will do nothing (meaning it returns undefined).
IDK, why you don't see other like attempt to await on stub.
Nevertheless, you should properly configuture 'save' stub - for example like this:
const saveStub = sinon.stub(Post.prototype, 'save');
saveStub.resolves({foo: "bar"});

Redux Thunk Common Actions - Standalone + Combined

Implementing the pattern Dan (#gaearon) demonstrated here, I was just wondering the best way to have the common action work standalone in the following scenario:
const commonAction = () => {
return async (dispatch, getState, api) => {
try {
const response = await api.get('/resource');
dispatch(success('SUCCESS', response.data));
} catch(error) {
dispatch(error('ERROR', error));
throw error; //this is the problem
}
}
const combinedAction = () => {
return async (dispatch, getState, api) => {
try {
await dispatch(commonAction());
const otherResponse = await api.get('/otherResource');
dispatch(success('COMBINED_SUCCESS', otherResponse.data));
} catch(error) {
dispatch(error('COMBINED_ERROR', error));
}
}
The above works in that if either fails, the COMBINED_ERROR will be dispatched, due to the commonAction re-throwing it's error. However, if I wanted to use commonAction by itself, I would get an Unhandled promise rejection error due to the re-throw.
One thought I had was an optional bool isStandAlone = true to determine whether to re-throw or not, i.e.
const commonAction = (isStandAlone = true) => {
return async (dispatch, getState, api) => {
try {
const response = await api.get('/resource');
dispatch(success('SUCCESS', response.data));
} catch(error) {
dispatch(error('ERROR', error));
if(!isStandAlone) {
throw error;
}
}
}
And then in the combinedAction I would just:
await dispatch(commonAction(false));
Is this a code smell? Is there a better and/or different way to approach this?

Resources