Я создаю Restfull API с использованием Node.js и express и решил протестировать мое приложение с использованием Jest и Supertest , в моем классе приложений мне требуется некоторая конфигурация i18n (i18n - это библиотека, используемая для переводов).
При работе на обычном сервере express и тестировании моих маршрутов вручную с помощью Бессонница или Почтальон , все отлично работает. Но когда я пытаюсь создать тест для маршрута / login , тест выполняется нормально, и я получаю ожидаемые результаты, но Jest никогда не завершает работу и выдает Jest has detected the following 1 open handle potentially keeping Jest from exiting
. Из того, что он указывает, кажется, что вызов be i18n.configure(...
оставляет открытый дескриптор, как ни странно, я проверил метод configure
, и он не асинхронный.
Вот вывод консоли:
Вот мое приложение. js Файл:
require("dotenv").config({
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
});
const express = require("express");
const cookieParser = require("cookie-parser");
class AppController {
constructor() {
this.express = express();
this.configure();
this.middlewares();
this.routes();
}
async configure() {
this.i18n = require("./config/i18n");
}
middlewares() {
this.express.use(express.json());
this.express.use(cookieParser());
this.express.use(this.i18n.init);
}
routes() {
this.express.use(require("./routes"));
}
}
module.exports = new AppController().express;
Файл конфигурации i18n:
const i18n = require("i18n");
i18n.configure({
locales: ["pt-br", "en-us"],
defaultLocale: "pt-br",
cookie: "locale",
directory: "./locales",
autoReload: true
});
i18n.setLocale("pt-br");
module.exports = i18n;
Тест сам по себе:
const request = require("supertest");
const app = require("../../src/app.js");
describe("Authentication", () => {
it("should not authenticate when user does not exist", async () => {
const response = await request(app)
.post("/login")
.send({
username: "john",
password: "123123"
});
expect(response.status).toBe(401);
});
});
Маршрут / login ( Он находится внутри папки входа, поэтому я могу объявить только "/" для маршрута ):
const routes = require("express").Router();
const LoginController = require("../app/controllers/LoginController");
routes.post("/", LoginController.index);
module.exports = routes;
LoginController
с индексной функцией:
const passport = require("../../app/middlewares/passport");
const jwt = require("jsonwebtoken");
class LoginController {
async index(req, res) {
passport.authenticate("local", { session: false }, (error, user) => {
if (error || !user) {
res.status(401).send(res.__(error));
} else {
const payload = {
username: user.username,
expires: Date.now() + parseInt(process.env.JWT_EXPIRATION_MS)
};
req.login(payload, { session: false }, error => {
if (error) {
res.status(400).send({ error });
}
const token = jwt.sign(
JSON.stringify(payload),
process.env.APP_SECRET
);
res.cookie("jwt", token, { httpOnly: true, secure: true });
res.status(200).send({ username: user.username });
});
}
})(req, res);
}
}
module.exports = new LoginController();
На всякий случай вот паспорт стратегии:
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const passportJWT = require("passport-jwt");
const JWTStrategy = passportJWT.Strategy;
const { User } = require("../models");
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
const user = await User.findOne({ where: { username } });
let passwordsMatch = false;
if (user) {
passwordsMatch = await user.checkPassword(password);
}
if (passwordsMatch) {
return done(null, user);
} else {
return done("Incorrect username and/or password");
}
} catch (error) {
done(error);
}
})
);
passport.use(
new JWTStrategy(
{
jwtFromRequest: req => req.headers.authorization.split(" ")[1],
secretOrKey: process.env.APP_SECRET
},
(jwtPayload, done) => {
if (Date.now() > jwtPayload.expires) {
return done("jwt expired");
}
return done(null, jwtPayload);
}
)
);
module.exports = passport;