I'm am trying to get an integration test using jest (test environment : node) to pass, for a login form which uses csurf for csrf protection (using the cookie option).
I've extracted the csrfToken from the login form and the set-cookie headers yet the test still fails with a 403 - invalid csrf token.
I can't see what the issue is and would appreciate a steer in the right direction.
test file :
const request = require('supertest');
const {User} = require('../../server/models/user');
const cheerio = require('cheerio');
const app = require('../../app');
let user, csrfToken, password, cookies;
beforeEach( async () => {
user = await new User({
firstName: "Name",
lastName: "Surname",
email: "email#example.com",
password: "password",
isAdmin : true
}).save();
});
afterEach( async () => {
await User.deleteMany();
});
describe('/login', () => {
describe('GET /', () => {
const exec = async () => {
const res = await request(app).get(`/login`);
let $ = cheerio.load(res.text);
csrfToken = $('[name=_csrf]').val();
return res;
};
it('should return the login form', async () => {
const res = await exec();
expect(res.status).toBe(200);
expect(res.text).toMatch(/Sign In/);
});
});
describe('POST /', () => {
const getLoginCsrfs = async () => {
const res = await request(app).get(`/login`);
let $ = cheerio.load(res.text);
csrfToken = $('[name=_csrf]').val();
cookies = res.headers['set-cookie'];
return res;
};
const postLogin = async () => {
return request(app).post(`/login`)
.set('Cookie', cookies)
.send({ email: user.email,
password: password,
_csrf: csrfToken
});
};
it('should return 401 without incorrect user info', async () => {
await getLoginCsrfs();
password = 'wrongpassword';
const res = await postLogin();
expect(res.status).toBe(401)
});
it('should return 403 without csrf token/header credentials', async () => {
await getLoginCsrfs();
csrfToken = '';
cookies = '';
password = 'password';
const res = await postLogin();
expect(res.status).toBe(403)
});
it('should return 200 with correct credentials', async () => {
await getLoginCsrfs();
password = 'password';
const res = await postLogin();
expect(res.status).toBe(200)
});
});
});
FAIL tests/integration/login.test.js
/login
GET /
✓ should return the login form (300ms)
POST /
✕ should return 401 without incorrect user info (150ms)
✓ should return 403 without csrf token/header credentials (130ms)
✕ should return 200 with correct credentials (131ms)
● /login › POST / › should return 401 without incorrect user info
expect(received).toBe(expected) // Object.is equality
Expected: 401
Received: 403
61 | password = 'wrongpassword';
62 | const res = await postLogin();
> 63 | expect(res.status).toBe(401)
| ^
64 | });
65 |
66 | it('should return 403 without csrf token/header credentials', async () => {
at Object.toBe (tests/integration/login.test.js:63:26)
● /login › POST / › should return 200 with correct credentials
expect(received).toBe(expected) // Object.is equality
Expected: 200
Received: 403
77 | password = 'password';
78 | const res = await postLogin();
> 79 | expect(res.status).toBe(200)
| ^
80 | });
81 | });
82 | });
at Object.toBe (tests/integration/login.test.js:79:26)
initially I thought the problem related to _csrf in set-cookie but after rechecking through the solution was straightforward,
const postLogin = async () => {
return request(app).post(`/login`)
.type('form')
.set('Cookie', cookies)
.send({ email: user.email,
password: password,
_csrf: csrfToken
});
};
i had omitted the .type('form'), the 403 related to the csrfToken from the form, (all form data), not being seen.
PASS tests/integration/login.test.js (5.661s)
/login
GET /
✓ should return the login form (489ms)
POST /
✓ should return 401 without incorrect user info (443ms)
✓ should return 403 without csrf token/header credentials (131ms)
✓ should return 200 with correct credentials (255ms)
I had been googling around and considered the issues to relate to jest / supertest and multiple cookies but as ever the solution was much more straightforward and closer to home.
Related
[Junior dev!!]
->Node v18.08
->jest 29.0.1
->MySQL
Hi i m running several jest test in diferent folders of each endpoint on my API. I have several files where i m creating an account => test several endpoints => clean DB.. I have at least 14 files like these. So.. When an endpoint in those 14 file is not working the test doesn't go until the end and clean the DB so i need to go back and change the user name.. A clearly waste of time..
I'm learning about the Hook beforeAll and afterAll but they are not working.
i will show here my code without the hooks (test working)
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//Creating User
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
//Endpoints to test
test("POST/test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
//Deleting all info from DB
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
And when i want to add the hooks doesn't work
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//creating before all an user
beforeAll(() => {
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
});
//testing endpoints
test("POST/action test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
// if the endpoint doesn't work afterAll clean all DB
afterAll(() => {
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
});
what do you think. Could you help me ? Because the info on jest is only showing how to do it with a function.. Can we test something inside beforeAll & afterAll ??
I have something similar in a project that I'm working on.
I had to make sure that the beforeAll was an async function and put the await.
beforeAll(async () => {
connection = await createConnection();
await connection.runMigrations();
const id = uuidv4();
const password = await hash('admin', 8);
await connection.query(
`insert into users (id, name, email, password, is_admin, created_at, driver_license)
values ('${id}', 'Admin', 'admin#test.com.br', '${password}', true, 'now()', 'XXXXXXX')`
);
});
afterAll(async () => {
await connection.dropDatabase();
await connection.close();
});
Another thing is that I have done a test using a test function inside the beforeAll and it has returned an error, so maybe just executing the post without the test may work.
Edit: Reading the docs about the test inside the beforeAll and beforeEach, it says that you can not use a test inside it.
I am using axios to make different GET and POST requests to a third-party API that requires a username and password login to access the data. I have tried several approaches to make this work but I keep getting this error in the console.
data: '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n' +
'<html><head>\n' +
'<title>401 Unauthorized</title>\n' +
'</head><body>\n' +
'<h1>Unauthorized</h1>\n' +
'<p>This server could not verify that you\n' +
'are authorized to access the document\n' +
'requested. Either you supplied the wrong\n' +
'credentials (e.g., bad password), or your\n' +
"browser doesn't understand how to supply\n" +
'the credentials required.</p>\n' +
'</body></html>\n'
}
I have been using this approach to send the username and password with node.js
export const getCameraStream = (req, res) => {
let conn = createConnection(config);
let query = `SELECT * FROM cameras WHERE id = ${req.params.id}`
conn.connect();
conn.query(query, async (error, rows, _) => {
const camera = rows[0];
const username = camera.user;
const password = camera.pass;
if (error) {
return res.json({ "status": "failure", "error": error });
}
const result = await axios({
method: 'get',
url: <placeholder for api url>,
auth: {
username, password
}
})
console.log(result);
});
conn.end();
}
and then use this code on my React front-end
const getCameraStream = async () => {
const username = camera.user
const password = camera.pass
try {
const token = Buffer.from(`${username}:${password}`).toString('base64')
const res = await publicRequest.get(`camera/getCameraStream/${camera.id}`, {
headers: {
'content-type': 'multipart/x-mixed-replace; boundary=--myboundary',
'Authorization': `Basic ${token}`
},
auth: {
username, password
}
})
console.log(res)
} catch (error) {
console.error(error)
}
}
Using this approach, I am able to pass the username and password to the server and the responseUrl property is correct. However, my app crashes and I keep getting the same unauthorized error in the console. Could this be an issue with the fact that the browser is sending a preflight OPTIONS request or am I overlooking something with my authorization?
can anyone help me out creating a fake response (500) for testing my API using "sinon" , am new to nodeJs , i have tried to test where the return status is 201 and it worked however am still not able to make the fake 500 response
here is my code , thank you in advance
//knex
app.post("/api/categories", function (req, rep) {
knex('categories').insert(req.body)
.then(() => rep.sendStatus(201).json({ message: "Category inserted" }))
.catch((err) => {
console.log(err)
rep.status(500);
})
});
// in my test js
var request=require('supertest');
var KnexApp=require('../KnexFolder/app');
var sinon = require("sinon");
describe("POST/users", ()=>{
describe('when everything is fine and no errors', () => {
it('should respond with status 201',async () => {
const res = await request(KnexApp)
.post('/api/categories')
.send({
name:"from test",
img_id: 5
})
expect(res.statusCode).toEqual(201)
})
})
describe('when There is internal server error', () => {
it('should respond with status 500',async () => {
sinon.stub('/api/categories', "post").throws(
new Error({
response: { status: 500},
})
);
expect(res.statusCode).toEqual(500)
})
})
})
There are two testing strategies:
Stub knex, query interface, and the resolved/rejected value. This way is easier than the second way, you don't need to set up a real testing database and populate testing data.
As mentioned above, you need to set up a real testing database(run your migration script to create database and tables, create the seed testing data, etc...)
I will use the first way to test your code. Since sinon doesn't support stub a function export defaults by a module. We need to use proxyquire package.
app.js:
const express = require('express');
const knex = require('knex')({
client: 'mysql',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
});
const app = express();
app.use(express.json());
app.post('/api/categories', function (req, rep) {
console.log(req.body);
knex('categories')
.insert(req.body)
.then(() => rep.sendStatus(201).json({ message: 'Category inserted' }))
.catch((err) => {
console.log(err);
rep.sendStatus(500);
});
});
module.exports = app;
app.test.js:
const request = require('supertest');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('POST/users', () => {
describe('when everything is fine and no errors', () => {
it('should respond with status 201', async () => {
const queryInterfaceStub = {
insert: sinon.stub().resolves(),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({
name: 'from test',
img_id: 5,
});
sinon.assert.match(res.statusCode, 201);
});
});
describe('when There is internal server error', () => {
it('should respond with status 500', async () => {
const queryInterfaceStub = {
insert: sinon.stub().rejects(new Error('fake error')),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({});
sinon.assert.match(res.statusCode, 500);
});
});
});
Test result:
POST/users
when everything is fine and no errors
{ name: 'from test', img_id: 5 }
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:558:11)
at ServerResponse.header (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:267:15)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.js:20:37
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
✓ should respond with status 201 (436ms)
when There is internal server error
{}
Error: fake error
at Context.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.test.js:27:38)
at callFn (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:364:21)
at Test.Runnable.run (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:352:5)
at Runner.runTest (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:677:10)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:801:12
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:594:14)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:604:7
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:486:14)
at Immediate.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:572:5)
at processImmediate (internal/timers.js:461:21)
✓ should respond with status 500
2 passing (448ms)
I have a button that lauches a fetch to my API that uses KOA and JWT. The javascript for the fetch initiated on click is:
<script>
function loginButton(user, pass) {
fetch('http://localhost:5454/api/login', {
method: "post",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
username: user,
password: pass
})
})
.then( (response) => {
console.log("Success")
})
.catch(e => console.log(e));
}
</script>
The code for my Authentication is:
router.post(`${BASE_URL}/login`, async (ctx) => {
const reqUsername = ctx.request.body.username
const reqPassword = ctx.request.body.password
const unauthorized = (ctx) => {
ctx.status = 401
ctx.body = {
error: 'Invalid username or password'
}
}
let attemptingUser
try {
attemptingUser = await Employee.findOne({ where: { username: reqUsername }})
if (attemptingUser != null && attemptingUser.password === reqPassword) {
ctx.status = 200
ctx.body = {
username: attemptingUser.username,
given_name: attemptingUser.given_name,
role: attemptingUser.role,
created_at: attemptingUser.createdAt,
updated_at: attemptingUser.updatedAt,
}
const token = jwt.sign({ username: attemptingUser.username, role: attemptingUser.role }, SECRET)
ctx.set("X-Auth", token)
} else {
unauthorized(ctx)
}
} catch(err) {
console.error(err)
console.error(`Failed to find username: ${reqUsername}`)
unauthorized(ctx)
}
})
The code for my KOA initiation is:
require('dotenv').config()
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const baseRoutes = require('./routes')
const cors = require('#koa/cors');
const PORT = process.env.PORT || 8080
const app = new Koa()
app.use(bodyParser())
app.use(baseRoutes.routes())
app.use(cors());
app.listen(PORT, () => {
console.log(`Server listening on ${PORT}`)
})
Im using Port 8080 for my http-server and port 5454 for my npm server. I am getting a Failed to Fetch in the catch of the Fetch, as well as a CORS error related to not having a Access-Control-Allow-Origin header in the response header. I've tried a couple things and am ready to have a new set of eyes look at it, any tips?
Edit: I am successfully receiving the token in the X-Auth header, but for some reason it’s still throwing errors and I’d like to get them resolved before it spirals out of control.
I'm setting up an authentication server when the user logged in; express should add a property ('x-auth-token') to the response header the route actually working but I don't know how to write a test for that in jest.
here is my route handler;
const bcrypt = require('bcrypt');
const { User } = require('../models/user');
const Joi = require('joi');
const express = require('express');
const router = express.Router();
router.post('/', async (req, res) => {
const { error } = validation(req.body);
if (error) return res.status(400).send(error.details[0].message);
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password)))
return res.status(400).send('Invalid email or password.');
// here express adding the token to header
res
.header('x-auth-token', user.generateAuthToken())
.send(JSON.stringify(`Welcome dear ${user.name}`));
});
function validation(value) {
const schema = {
email: Joi.string()
.min(10)
.max(255)
.email()
.required(),
password: Joi.string()
.min(8)
.max(1024)
.required()
};
return Joi.validate(value, schema);
}
module.exports = router;
here is my test:
user is available in MongoDB and email and password are valid.
const request = require('supertest');
const execude = () => {
return request(server)
.post('/api/auth')
.send({ email, password });
};
it('should place token in x-auth-token if request is valid', async () => {
const res = await execude();
expect(res.header).to.haveKey('x-auth-token');
});
jest console
● /auth › POST/ › should place token in x-auth-token if request is valid
expect(object).toHaveProperty(path)
Expected the object:
{"connection": "close", "content-length": "26", "content-type": "text/html; charset=utf-8", "date": "Mon, 29 Apr 2019 20:54:45 GMT", "etag": "W/\"1a-ARJvVK+smzAF3QQve2mDSG+3Eus\"", "strict-transport-security": "max-age=15552000; includeSubDomains", "x-content-type-options": "nosniff", "x-dns-prefetch-control": "off", "x-download-options": "noopen", "x-frame-options": "SAMEORIGIN", "x-xss-protection": "1; mode=block"}
To have a nested property:
"x-auth-token"
94 | const res = await execude();
95 |
> 96 | expect(res.header).toHaveProperty('x-auth-token');
| ^
97 | });
98 | // should send specific string if request is valid
99 | });
at Object.toHaveProperty (tests/integration/authentication.test.js:96:
Here is the function you can utilize to give post request with/without token. you have to declare your deployed database ip/address in server variable
/**
* Post request function.
* #param {String} endpoint - Endpoint to give post request.
* #param {String} token - Token to validate-optional.
* #param {Object} schema - Data to post.
* #return {} Returns body response and status code.
*/
async postRequest(endpoint, schema, token = 0) {
if (token === 0) { return await request(server).post(endpoint).send(schema) }
else { return await request(server).post(endpoint).set('x-auth-token', token).send(schema) };
}