access req.cookie from supertest - supertest

I set a cookie and I usually access it like this when I need to test a "protected" request.
beforeAll(async () => {
await db.connect();
//sign my user and get the token
const response = await request(app).post("/gettoken").send(credentials);
TOKEN = response.headers["set-cookie"].pop().split(";")[0];
})
//test exemple
it("exemple", async () => {
const result = await request(app).post("/path").set(`Cookie`, TOKEN).send(data);
});
So far I had no problem with it but in one of my function that I want to test I have this line:
user = await getUser(req.cookies.token);
the function getUser is pretty simple:
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
user = await User.findOne({ _id: userToken.payload._id });
return user;
Seems like supertest does not work with req.cookies. Is there any way to set "req.cookies.token" during my test ?

I'm going to answer my own question but please, be aware that I have no idea why it works. Actually I was a bit desperate to not find any solution so I did try something without any hope but, surprisely, it worked.
so basically after restarting docker my console.log were all similars (npm run test vs test with postman)
module.exports.getUser = async (token) => {
console.log(token);
console.log(process.env.JWTPRIVATEKEY);
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
console.log(userToken.payload._id);
const user = await User.find({ _id: userToken.payload._id });
return user;
};
it("with success", async () => {
const question = {
title: "title",
content: "content",
};
const result = await request(app).post("/api/forum/question").set("Cookie", TOKEN).send(question);
console.log(result.body);
expect(result.status).toBe(200);
});
And I had this error:
{ error: { message: "Cannot read property '_id' of null" } }
I did change
const user = await User.findOne({ _id: userToken.payload._id });
by
const user = await User.find({ _id: userToken.payload._id });
And now it works as expected.
If someone can explain me why findOne was not working with test (but was ok with postman) I would be happy to understand... because to me, as beginner, it does not make any sense.

Related

Strapi Generic filtering from REST request to EntityServiceAPI

I've been reading the docs on the EntityService API and I understand you can builder filters, populates etc, however I'm not sure how to pass the filters down from the request without parsing the URL manually and constructing an object?
If I have a GET request that looks like http://localhost:1337/my-content-types?filters[id][$eq]=1 which is how it looks in the filtering example here: https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/filtering-locale-publication.html#deep-filtering
how do I pass the filters down to the EntityServiceAPI?
Request: http://localhost:1337/my-content-types?filters[id][$eq]=1
I have a core service that looks like this:
module.exports = createCoreService('plugin::my-plugin.my-content-type', ({strapi}) => ({
find(ctx) {
// console.log("Params:");
return super.find(ctx)
}
}))
which is called from the controller:
module.exports = createCoreController('plugin::my-plugin.my-content-type', ({strapi}) => ({
async find(ctx) {
return strapi
.plugin(_pluginName)
.service(_serviceName)
.find(ctx);
}
}));
and my routing:
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/',
handler: 'my-content-type.find',
config: {
policies: [],
auth: false
}
}
]
};
EDIT:
I've got something working by writing my own very crude pagination, but I'm not happy with it, I'd really like a cleaner solution:
find(ctx) {
const urlSearchParams = new URLSearchParams(ctx.req._parsedUrl.search);
const params = {
start: urlSearchParams.get('start') || 0,
limit: urlSearchParams.get('limit') || 25,
populate: '*'
}
const results = strapi.entityService.findMany('plugin::my-plugin.my-content-type', params);
return results;
}
you can get params from ctx.query
example:
async find(ctx){
const limit = ctx.query?.limit ?? 20;
const offset = ctx.query?.offset ?? 0;
return await strapi.db.query('api::example.example').find({…ctx.query, limit, offset, populate: ['someRelation']});
}
I think the normally it done by wrapping id under where, and extracting the known parms. Gonna do a test when near pc but if the above variant dose not work, you can do the same with:
const { offset, limit, populate, …where} = ctx.query;
await strapi.db.query(‘…’).find({offset, limit, populate, where})
You can check pagination example in this thread: Strapi custom service overwrite find method

How to test dispatched react function using Jest

I am trying to unit test a function which makes an async call using an Axios helper instance. I have attempted multiple ways of trying to unit test this but I can not seem to find any material online which has helped. I've been stuck on this problem for a few days which is frustrating so any help would be appreciated! Below are the Axios Helper file (api.js)
api.js
import axios from 'axios'
const API = (token = null) => {
let headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-key': process.env.NEXT_PUBLIC_API_HEADER_SUBSCRIPTION_KEY
}
if (token) {
const tokenHeader = { Authorization: 'Bearer ' + token }
headers = { ...headers, ...tokenHeader }
}
const url = process.env.NEXT_PUBLIC_API_BASE_URL
const API = axios.create({
baseURL: url,
headers
})
return API
}
export default API
mocked API
export default {
post: jest.fn(() =>
Promise.resolve({
data: {}
})
),
get: jest.fn(() =>
Promise.resolve({
data: {}
})
)
}
action file
export const initiate2FA = (destinationValue) => async () => {
const twoFactorAuth = destinationValue
const res = await API().post('/foo', {
Destination: twoFactorAuth
})
return res
}
Action.test.js
import API from 'api/api'
import { initiate2FA } from 'actions/userActions'
jest.mock('api/api')
const mockedAxios = API
const dispatch = jest.fn()
describe('Initiate2FA function', () => {
it('bar', async () => {
mockedAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200 }))
const t = await dispatch(initiate2FA('test#test.com'))
console.log(t)
})
})
My issue with the above test file is that it returns an anonymous function and I do not know how to handle this to pass the unit test. The goal of the test is to make sure the function is called. I am not sure if I am approaching this the correct way or should change my approach.
Again, any suggestions would be great!
Mocking an API call is something you can mock on your own React component, instead of a function, and the best option would be to not mock anything on your component. Here you can read all about why you should not mock your API functions. At the end of the article, you're going to find a library called Mock Service Worker which you can use for your purpose.
The way you declare you have an actual HTTP called that needs to be mocked would be something like this:
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
If you just need to unit test a function, you can still use Mock Service Worker to resolve the HTTP request, and then test what happens after that. This would still be your first choice. And the test would look like:
// this could be in another file or on top of your tests.
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
// and this would be your test
describe('Initiate2FA function', () => {
it('bar', async () => {
const res = await initiate2FA('test#test.com');
expect(res).toBe({bar: '');
})
})

Test GraphQl passport Context

Okey, this is the repo
What I want to do: Test my protected routes.
Currently, the security of the app is handle by passport, with this strategy: graphql-passport.
I am running my rests with supertest (for the request) and jest
When I build the Apollo Server, i use it to create the context:
import { buildContext } from 'graphql-passport';
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
return buildContext({ req, res, User })
},
playground: {
settings: {
'request.credentials': 'same-origin',
},
},
});
This allows me to get the user from the request. Like any authentication with passport works.
passport.use(
new GraphQLLocalStrategy((email, password, next) => {
console.log(`🎫 GraphQLLocalStrategy ${email} 🚔 👮‍♂`)
User.findOne({ email })
.then(user => !user
? next(null, false, 'Invalid email or password')
: user.checkPassword(password) //bcrypt
.then(match => !match
? next(null, false, 'Invalid email or password')
: next(null, user)
)
)
.catch(error => next(error))
}),
);
So far, it works good enough. For every test that i run, I can see my 🎫 GraphQLLocalStrategy ${email} 🚔 👮‍♂ being called. Good!.
For some mutations, like login and update user profile, i am able to do this:
user.mutations.test.js
// * Login for add the user in the context
agent
.post("/graphql")
.send({ query: ` mutation {
${loginQuery(email)}
${updateFirstName}
}`})
.set("Accept", "application/json")
.end((err, {body:{data, errors}}) => {
if (err) return done(err);
const {updateUser} = data;
expect(updateUser).toBeInstanceOf(Object);
expect(updateUser.email).toBe(email);
expect(updateUser.firstName).toBe(newName);
expect(updateUser.rol).toBe("patron");
UserFields.map(checkFields(updateUser));
done();
})
So, in one query, I can send the login mutation and then run the update the first name mutation. Both, works good enough, and according to passport I am logged and I can update the user profile.
What is the issue?? I want to run a loging mutation and after that run a query to get all users.
But, ofcourse, I can not run both at the same time in the request(app).post("/graphql").send() It has to be a one or multiple mutations or a queries... but not both.
The other idea, who doesnt work, is run one, and in the response, run the second one, like this:
const agent = request(app);
agent
.post("/graphql")
.send({ query: `mutation { ${loginQuery(email)} }`})
.end((err, {body:{data}}) => {
if (err) return done(err);
agent
.post("/graphql")
.send({ query: `query { getGuestsQuery() }`})
...
If I try to ask in a second request for a protected route, there is not a way to know that i was authenticated, at least not automatically... Can I make an authenticated request here with supertest
**How can I tell to my tested application that I am authenticated???? **
test("fetch all Guests", async (done) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return done('no admin users for testing');
const agent = request.agent(app);
agent
.post('/graphql')
.send({ query: ` mutation { ${loginQuery(userAdmin.email)} }`})
.expect(200)
.end((err, res) => {
if (err) return done(err);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
});
});

VueResource Vue.http.get Response Status Code 0

im having this issue where i send a request to the API to retrieve all users, the login function is called(index.vue) when called it tries to go to api/users/all which in this case should return all the users in that collection.
using Postman the API returns the correct results and if i console.log the output in the routeUsers before i send the response back, it outputs all the correct data to the console
when it returns to index.vue, the response status code is 0.
ive had a look online and some things are mentioning about CORS Headers but i dont think thats applicable to me and other things about the response has been cancelled,
can anyone shed some light on this for me and help me try to fix it?!
API main.js
var app = express();
var users = require('./routes/routeUsers');
app.use('/users', users);
module.exports = app;
api/models/users.js
var db = require('../Utilities/db')
module.exports.all = function(cb) {
var collection = db.get().collection('users')
collection.find().toArray(function(err, docs) {
cb(err, docs)
})
}
api/routes/routeUsers.js
var express = require('express')
, router = express.Router()
var user = require('../models/users');
router.get('/all', function(req, res) {
user.all(function(err, users) {
res.send(users);
})
})
Index.vue
export default {
data: function () {
return {
username: '',
password: '',
users: []
}
},
methods: {
login: function() {
Vue.http.get('/api/users/all').then((response) => {
console.log("SUCCESS",response);
this.users = response.body;
console.log(users);
}, function (error) {
console.log("Error", error.status); // handle error
});
}
}
};
The issue was that the inputs were in a form tag. removed Form tag and worked fine.

Cannot connect Ember Simple Auth and DRF Token Auth

I have a trouble with Ember Simple Auth.
I'm trying to connect my server-side application, which working on Django 1.9 with DRF, and client-side which working on Ember 2.2.
On server side I'm obtaining token on 'http://localhost:8000/api-token-auth/'. Function requires two args from request: "username" and "password". But Ember Simple Auth send POST request with args: "username[identification]" and "password[password]", and server returns "400". I think that problem with arguments keys.
POST request
Responce
I tried to change .authenticate method in oauth2-password-grant.js(i can't write custom authenticator because i'm newbee in javascript), but nothing changed.
Manually POST request returns expected answer.
Please tell me the way to solve this problem.
And please forgive me for my english.
authenticate(identification, password, scope = []) {
return new RSVP.Promise((resolve, reject) => {
const data = { 'grant_type': 'password', username: identification, password };
const serverTokenEndpoint = this.get('serverTokenEndpoint');
const scopesString = Ember.makeArray(scope).join(' ');
if (!Ember.isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
run(() => {
const expiresAt = this._absolutizeExpirationTime(response['expires_in']);
this._scheduleAccessTokenRefresh(response['expires_in'], expiresAt, response['refresh_token']);
if (!isEmpty(expiresAt)) {
response = Ember.merge(response, { 'expires_at': expiresAt });
}
resolve(response);
});
}, (xhr) => {
run(null, reject, xhr.responseJSON || xhr.responseText);
});
});
},
My variant:
const data = { 'grant_type': 'password', 'username': identification, 'password': password };
authenticate: function () {
// var username = this.getProperties('username');
// var password = this.getProperties('password');
const {username, password} = this.getProperties('username', 'password');
this.get('session').authenticate('authenticator:oauth2', username, password).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
It was my mistake.

Resources