私はPassport.jsを認証(ローカル戦略)とMochaとSupertestでのテストに使用しています。
Supertestを使用してセッションを作成し、認証されたリクエストを行うにはどうすればよいですか?
私はPassport.jsを認証(ローカル戦略)とMochaとSupertestでのテストに使用しています。
Supertestを使用してセッションを作成し、認証されたリクエストを行うにはどうすればよいですか?
zeMircoが指摘しているように、基盤となるsuperagent
モジュールはセッションをサポートし、Cookieを自動的に維持します。ただし、文書化されていない機能を介して、superagent.agent()
からの機能を使用することは可能です。supertest
require('supertest').agent('url')
代わりに使用するrequire('supertest')('url')
:
var request = require('supertest');
var server = request.agent('http://localhost:3000');
describe('GET /api/getDir', function(){
it('login', loginUser());
it('uri that requires user to be logged in', function(done){
server
.get('/api/getDir')
.expect(200)
.end(function(err, res){
if (err) return done(err);
console.log(res.body);
done()
});
});
});
function loginUser() {
return function(done) {
server
.post('/login')
.send({ username: 'admin', password: 'admin' })
.expect(302)
.expect('Location', '/')
.end(onResponse);
function onResponse(err, res) {
if (err) return done(err);
return done();
}
};
};
そのためにはスーパーエージェントを使用する必要があります。これは下位レベルのモジュールであり、によって使用されsupertest
ます。「エージェントの永続化」セクションをご覧ください。
var request = require('superagent');
var user1 = request.agent();
user1
.post('http://localhost:4000/signin')
.send({ user: 'hunter@hunterloftis.com', password: 'password' })
.end(function(err, res) {
// user1 will manage its own cookies
// res.redirects contains an Array of redirects
});
これで、を使用user1
して認証済みリクエストを作成できます。
これを試して、
var request=require('supertest');
var cookie;
request(app)
.post('/login')
.send({ email: "user@gluck.com", password:'password' })
.end(function(err,res){
res.should.have.status(200);
cookie = res.headers['set-cookie'];
done();
});
//
// and use the cookie on the next request
request(app)
.get('/v1/your/path')
.set('cookie', cookie)
.end(function(err,res){
res.should.have.status(200);
done();
});
Andyの回答の補足として、Supertestにサーバーを起動させるには、次のようにします。
var request = require('supertest');
/**
* `../server` should point to your main server bootstrap file,
* which has your express app exported. For example:
*
* var app = express();
* module.exports = app;
*/
var server = require('../server');
// Using request.agent() is the key
var agent = request.agent(server);
describe('Sessions', function() {
it('Should create a session', function(done) {
agent.post('/api/session')
.send({ username: 'user', password: 'pass' })
.end(function(err, res) {
expect(req.status).to.equal(201);
done();
});
});
it('Should return the current session', function(done) {
agent.get('/api/session').end(function(err, res) {
expect(req.status).to.equal(200);
done();
});
});
});
申し訳ありませんが、提案された解決策のどちらも私には機能しません。
インスタンスを使用supertest.agent()
できないため、app
事前にサーバーを実行して指定する必要がhttp://127.0.0.1:port
あり、さらにスーパーテストの期待値(アサーション)を使用できない、ライブラリを使用できないsupertest-as-promised
など...
ケースはcookies
私にはまったく機能しません。
だから、私の解決策は:
Passport.jsを使用している場合は、「ベアラートークン」メカニズムを利用しており、仕様で次の例を使用できます。
var request = require('supertest');
var should = require('should');
var app = require('../server/app.js'); // your server.js file
describe('Some auth-required API', function () {
var token;
before(function (done) {
request(app)
.post('/auth/local')
.send({
email: 'test@example.com',
password: 'the secret'
})
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.to.have.property('token');
token = res.body.token;
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.set('authorization', 'Bearer ' + token) // 1) using the authorization header
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.query({access_token: token}) // 2) using the query string
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
});
ユーザーを認証するためのヘルパー関数が必要になる場合があります。
test/auth-helper.js
'use strict';
var request = require('supertest');
var app = require('app.js');
/**
* Authenticate a test user.
*
* @param {User} user
* @param {function(err:Error, token:String)} callback
*/
exports.authenticate = function (user, callback) {
request(app)
.post('/auth/local')
.send({
email: user.email,
password: user.password
})
.end(function (err, res) {
if (err) {
return callback(err);
}
callback(null, res.body.token);
});
};
生産的な一日を!
CookieSessionミドルウェアを使用していると仮定します。
grubが述べたように、あなたの目標はあなたのリクエストに渡すためのクッキー値を取得することです。ただし、何らかの理由で(少なくとも私のテストでは)、supertestは同じテストで2つのリクエストを実行しません。したがって、適切なCookie値を取得する方法をリバースエンジニアリングする必要があります。まず、Cookieを作成するためのモジュールを要求する必要があります。
var Cookie = require("express/node_modules/connect/lib/middleware/session/cookie")
, cookieSignature = require("express/node_modules/cookie-signature")
はい、それは醜いです。それらをテストファイルの先頭に置きます。
次に、Cookieの値を作成する必要があります。beforeEach
認証されたユーザーを必要とするテストのために、これをに入れます。
var cookie = new Cookie()
, session = {
passport: {
user: Test.user.id
}
}
var val = "j:" + JSON.stringify(session)
val = 's:' + cookieSignature.sign(val, App.config.cookieSecret)
Test.cookie = cookie.serialize("session",val)
Test.user.id
beforeEach
以前は、「ログイン」しようとしているユーザーを定義するチェーンの部分で定義されていました。の構造session
は、Passportが(少なくとも現在)現在のユーザー情報をセッションに挿入する方法です。
Cookieベースのセッションを使用している場合にPassportがフォールバックするConnectCookieSessionミドルウェアを使用した行var val
と"j:"
削除された行。"s:"
最後に、Cookieをシリアル化します。それが"session"
私のCookieセッションミドルウェアを構成した方法だからです。また、App.config.cookieSecret
他の場所で定義されており、Express /ConnectCookieSessionミドルウェアに渡す秘密である必要があります。Test.cookie
後でアクセスできるように、それを隠しておきます。
さて、実際のテストでは、そのCookieを使用する必要があります。たとえば、次のテストがあります。
it("should logout a user", function(done) {
r = request(App.app)
.del(App.Test.versionedPath("/logout"))
.set("cookie", Test.cookie)
// ... other sets and expectations and your .end
}
set
with"cookie"
と。の呼び出しに注意してくださいTest.cookie
。これにより、リクエストで作成したCookieを使用するようになります。
そして今、あなたはユーザーがログインしているとアプリを偽って考えさせました、そしてあなたは実際のサーバーを動かし続ける必要はありません。
これは、再利用可能であるという追加の利点があるきちんとしたアプローチです。
const chai = require("chai")
const chaiHttp = require("chai-http")
const request = require("supertest")
const app = require("../api/app.js")
const should = chai.should()
chai.use(chaiHttp)
describe("a mocha test for an expressjs mongoose setup", () => {
// A reusable function to wrap your tests requiring auth.
const signUpThenLogIn = (credentials, testCallBack) => {
// Signs up...
chai
.request(app)
.post("/auth/wizard/signup")
.send({
name: "Wizard",
...credentials,
})
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
// ...then Logs in...
chai
.request(app)
.post("/auth/wizard/login")
.send(credentials)
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
should.not.exist(err)
res.should.have.status(200)
res.body.token.should.include("Bearer ")
// ...then passes the token back into the test
// callBack function.
testCallBack(res.body.token)
})
})
}
it.only("flipping works", done => {
// "Wrap" our test in the signUpThenLogIn function.
signUpLogIn(
// The credential parameter.
{
username: "wizard",
password: "youSHALLpass",
},
// The test wrapped in a callback function which expects
/// the token passed back from when signUpLogIn is done.
token => {
// Now we can use this token to run a test...
/// e.g. create an apprentice.
chai
.request(app)
.post("/apprentice")
.send({ name: "Apprentice 20, innit" })
// Using the token to auth!
.set("Authorization", token)
.end((err, res) => {
should.not.exist(err)
res.should.have.status(201)
// Yep. apprentice created using the token.
res.body.name.should.be.equal("Apprentice 20, innit")
done()
})
}
)
})
})
ボーナス素材
さらに再利用可能にするには、関数を「myMochaSuite.js」というファイルに入れます。このファイルは、APIサーバーをテストするときに「describe」を置き換えることができます。ウィザードになって、この「スイート」にすべての前/後のものを入れてください。例えば:
// tests/myMochaSuite.js
module.exports = (testDescription, testsCallBack) => {
describe(testDescription, () => {
const signUpThenLogIn = (credentials, testCallBack) => {
// The signUpThenLogIn function from above
}
before(async () => {
//before stuff like setting up the app and mongoose server.
})
beforeEach(async () => {
//beforeEach stuff clearing out the db
})
after(async () => {
//after stuff like shutting down the app and mongoose server.
})
// IMPORTANT: We pass signUpLogIn back through "testsCallBack" function.
testsCallBack(signUpThenLogIn)
})
}
// tests/my.api.test.js
// chai, supertest, etc, imports +
const myMochaSuite = require("./myMochaSuite")
// NB: signUpThenLogIn coming back into the tests.
myMochaSuite("my test description", signUpThenLogIn => {
it("just works baby", done => {
signUpThenLogIn(
{username: "wizard", password: "youSHALLpass"},
token => {
chai
.request(app)
.get("/apprentices/20")
// Using the incoming token passed when signUpThenLogIn callsback.
.set("Authorization", token)
.end((err, res) => {
res.body.name.equals("Apprentice 20, innit")
done()
})
}
)
})
})
これで、すべてのテストにさらに再利用可能なスイート「ラッパー」ができ、整理されたままになります。
GraphQlの完全な例:
const adminLogin = async (agent) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return new Promise.reject('Admin not found')
return agent.post('/graphql').send({
query: ` mutation { ${loginQuery(userAdmin.email)} }`
})//.end((err, {body:{data}}) => {})
}
test("Login Admin", async (done) => {
const agent = request.agent(app);
await adminLogin(agent);
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();
});
})