Unable to send authenticated request in tests usin

2019-08-17 14:03发布

问题:

Despite my best attempts to correctly write test code to authenticate a request agent in Setup blocks or previous describe/it blocks, any request I make from the agent in subsequent describe/it blocks never completes as 200.

Example code:

const request = require('supertest');
const server = require('../server');

let agent = request.agent(server);
let fakePerson = null;

beforeEach(async (done) => {
  fakePerson = await Person.createMock();

  agent.post(‘/login’)
       .send({
           email: ‘test@user.com’,
           password: ‘password’
        })
        .end(function(err, res) {
            if (err) throw err;
            done();
        });
});

describe('GET /users/:id', () => {
    it ('renders user profile', () => {
      return agent
        .get(`/users/${fakePerson.id}`)
        .expect(200)
    });
});

I thought it might have something to do with how I was forming the async calls syntactically. But after trying different ways of returning the login call in the beforeEach block using return, .end() syntax, even async/await, I've determined (ie given up) that the code must be composed properly. Could it be something else?

Referenced articles/resources:

  • How to authenticate Supertest requests with Passport?
  • https://medium.com/@juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb
  • https://gist.github.com/joaoneto/5152248
  • https://medium.com/@bill_broughton/testing-with-authenticated-routes-in-express-6fa9c4c335ca
  • https://github.com/visionmedia/supertest/issues/46

Package versions:

  • "koa": "^2.4.1"
  • "koa-passport": "^4.0.1"
  • "passport-json": "^1.2.0"
  • "passport-local": "^1.0.0"
  • "supertest": "^3.0.0"
  • "jest": "^22.1.3"

回答1:

I took some time stepping through my auth code during test runs and couldn't see any obvious problem. Then I had a thought: what if the request itself was ill formed. Turns out I was right! Inspecting the set-cookie header in the Supertest response headers I saw:

[ 'koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjoxfSwiX2V4cGlyZSI6MTUyMTIyODg0NTU5OSwiX21heEFnZSI6ODY0MDAwMDB9; path=/; httponly,koa:sess.sig=EEZzgcg3bx8bm_FXRMobrr8_Yts; path=/; httponly' ]

This looked a little suspicious as a single string, which led me to some more googling where I discovered that there were differences in the way that cookie headers were being set for Mocha and Jest users as global state on the Supertest agent instance. See: https://github.com/facebook/jest/issues/3547#issuecomment-302541653. Folks using Mocha had no trouble authenticating, while Jest users did. Turns out there is a bug with Jest globals that is causing the cookie to come in as a single string rather than an array of separate strings for each cookie--which is what Supertest needs to format the request properly.

Here is a workaround based on the code in the question where we correctly parse the buggy Jest string to a scoped variable for the cookie/session data inside the Setup block:

const request = require('supertest');
const server = require('../server');

let agent = request.agent(server);
let fakePerson = null;
let session = null;

beforeEach(async () => {
  fakePerson = await Person.createMock();

  agent.post(‘/login’)
       .send({
           email: fakePerson.email,
           password: fakePerson.password’
        })
        .then(res => {
            session = res
               .headers['set-cookie'][0]
               .split(',')
               .map(item => item.split(';')[0])
               .join(';')
            expect(res.status).toEqual(200)
      });
});

describe('GET /users/:id', () => {
    it ('renders user profile', () => {
      return agent
        .get(`/users/${fakePerson.id}`)
        .set('Cookie', session)
        .expect(200)
    });
});


回答2:

I couldn't get internetross's answer to work. Finally after lots of sleuthing, I found this: https://github.com/facebook/jest/issues/3547#issuecomment-397183207.

I had to replace

session = response.headers['set-cookie'][0]
               .split(',')
               .map(item => item.split(';')[0])
               .join('; ')

with

  response.headers['set-cookie'][0]
    .split(',')
    .map(item => item.split(';')[0])
    .forEach(c => agent.jar.setCookie(c));

Big sigh.