Unit Testing Passport Strategy / NestJS Guard - unit-testing

I have a JWTGuard() decorator which follows the implementation as described in https://docs.nestjs.com/security/authentication#jwt-functionality
It looks like so:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard } from '#nestjs/passport';
import { IS_PUBLIC_ROUTE } from '../decorators/public-route.decorator';
#Injectable()
export class JWTAuthenticationGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(
IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()],
);
if (isPublic) return true;
return super.canActivate(context)
}
}
It works as expected, but having difficulty unit testing the guard. In short, I want to test three things:
If IS_PUBLIC_ROUTE is set, then canActivate() returns true ✅ this unit test works
If an expired/invalid/incorrectly signed token is passed, canActivate() returns false ❌
If a valid token is found, canActivate() returns true ❌
I tried to follow https://github.com/jmcdo29/testing-nestjs/blob/master/apps/complex-sample/src/cat/cat.guard.spec.ts which just tests a bare guard, not extending a Passport Guard.
Tests are setup as follows:
let guard: JWTAuthenticationGuard;
const reflector = new Reflector();
beforeEach(() => {
guard = new JWTAuthenticationGuard(reflector);
passport.use('jwt', new JWTStrategy());
});
The first test (should return false)
it('should return false if no authorization token is found', () => {
const context = createMock<ExecutionContext>();
const result = guard.canActivate(context);
expect(result).toBe(false);
});
My test never even makes it to the expect() clause, as it has thrown an UnauthorisedException and the test suite exits entirely.
I tried debugging inside the canActivate() method, and checking the type of super.canActivate() which tells me that it's a pending promise. I tried awaiting it, but still says its Pending, I tried wrapping in a try/catch, but the catch block never runs. Reading through the code of passport's AuthGuard, it never throws either.
I tried to add a valid token:
it('should return false if no authorization token is found', async () => {
const context = createMock<ExecutionContext>();
expect(context.switchToHttp()).toBeDefined();
context.switchToHttp().getRequest.mockReturnValue({
headers: {
Authorization:
'Bearer ey...[truncated]...dE',
},
});
const result = await guard.canActivate(context);
expect(result).toBe(true);
however I still just get an UnauthorisedException thrown from god knows where at the result = guard.can... line, and still never makes it to the expect.
Any guidance would be appriciated.

Related

How to set/mock an env variable in vitest (`process.env.NODE_ENV = 'anything'` takes effect only in test file)?

I have a method, in a class, that only executes its action when NODE_ENV === 'test'.
Here is the test that I set the env to anything to test the failing scenario:
it('returns Left on clearDatabase when not in test environment', async () => {
const { sut } = await makeSut()
process.env.NODE_ENV = 'any_environment'
const result = await sut.clearDatabase()
process.env.NODE_ENV = 'test'
expect(result.isLeft()).toBe(true)
})
Here is the method:
async clearDatabase (): Promise<Either<Error, void>> {
if (process.env.NODE_ENV !== 'test') {
return left(new Error('Clear database is allowed only in test environment'))
}
try {
const { database } = this.props.dataSource
await this.mongoClient.db(database).dropDatabase()
return right()
} catch (error) {
return left(error)
}
}
The problem is that when the method do it's verification, the value in NODE_ENV wasn't changed at all, it has its initial value (test). If I log the value, after setting it, in test file it's there, only the object can't see this change. In jest it works just fine. How can I set/mock it properly in vitest?
Here you find a StackBlitz with an example scenario: https://stackblitz.com/edit/node-lr72gz?file=test/example.unit.test.ts&view=editor

jest.spyOn mock return value not returning value

The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)

How to make stub for mongoose document object

I am writing unit test for my auth.service module validateReader unit,
async validateReader(username: string, password: string): Promise<any> {
const reader = await this.readerService.findOne(username);
const match = await bcrypt.compare(password, reader.password);
if (match) {
const { password, ...result } = reader.toJSON();
this.logger.info(
`Reader ${reader.username} username & password validation passed`,
);
return result;
}
this.logger.warn(`Incorrect password in reader ${reader.username} login`);
return null;
}
I tried to mock readerService.findOne function as following:
jest
.spyOn(readerService, 'findOne')
.mockImplementationOnce(() => Promise.resolve(readerStub()));
but did not work, always got error - Cannot spy the findOne property because it is not a function; I think the reason is the returned value must be mongoose document object (need toJSON() method), but my readerStub() just return a reader object, missing lots of document properties. Is there anyway I can set up stub for document & reader? And maybe my analysis is wrong, there is other reason to got this error.
Following is my mock readerService:
export const ReaderService = jest.fn().mockReturnValue({
register: jest.fn().mockResolvedValue(readerStub()),
findOne: jest.fn().mockResolvedValue(readerStub()),
getProfile: jest.fn().mockResolvedValue(readerStub()),
updateProfile: jest.fn().mockResolvedValue(readerStub()._id),
changePwd: jest.fn().mockResolvedValue(readerStub().username),
login: jest.fn().mockResolvedValue(accessTokenStub()),
tokenRefresh: jest.fn().mockReturnValue(accessTokenStub()),
logout: jest.fn().mockResolvedValue(readerStub()._id),
});

AdonisJS 'bouncer' package not working as intended

As described here, I have implemented the authorization:
start/bouncer.ts:
import Bouncer from '#ioc:Adonis/Addons/Bouncer'
export const { actions } = Bouncer
export const { policies } = Bouncer.registerPolicies({
UserPolicy: () => import('App/Policies/UserPolicy'),
})
app/Policies/UserPolicy.ts:
import { BasePolicy } from '#ioc:Adonis/Addons/Bouncer'
import User from 'App/Models/User'
export default class UserPolicy extends BasePolicy {
public async before(user?: User) {
return user?.isSuperUser
}
public async list(user: User) {
await user.load('policies')
return user.policies.some((policy) => policy.identifier === 'user:list')
}
// ...
}
resources/vires/layouts/main.edge
#can('UserPolicy.list')
<p>Can see users list</p>
#endcan
And I cannot see the paragraph. In fact, I placed console.log inside the action, but it didn't get executed. I don't know if I'm missing anything. Can anyone shed some lights onto it?
Gotcha! This says:
The actual action callback is never executed when a before hook returns a true or a false value.
Make sure to return undefined if you want the bouncer to execute the next hook or the action callback.
These 2 statements were missed out. :)

How to properly test functions that return Mongoose queries as Promises

I'm trying to write a basic unit test to work on the function below, but can't get it to work. How do I test that something like a proper npm-express response is returned?
I already looked at Using Sinon to stub chained Mongoose calls, https://codeutopia.net/blog/2016/06/10/mongoose-models-and-unit-tests-the-definitive-guide/, and Unit Test with Mongoose, but still can't figure it out. My current best guess, and the resulting error, is below the function to be tested. If possible, I don't want to use anything but Mocha, Sinon, and Chai.expect (i.e. not sinon-mongoose, chai-as-expected, etc.). Any other advice, like what else I can/should test here, is welcome. Thank you!
The function to be tested:
function testGetOneProfile(user_id, res) {
Profiles
.findOne(user_id)
.exec()
.then( (profile) => {
let name = profile.user_name,
skills = profile.skills.join('\n'),
data = { 'name': name, 'skills': skills };
return res
.status(200)
.send(data);
})
.catch( (err) => console.log('Error:', err));
}
My current best-guess unit test:
const mongoose = require('mongoose'),
sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect,
Profile = require('../models/profileModel'),
foo = require('../bin/foo');
mongoose.Promise = global.Promise;
describe('testGetOneProfile', function() {
beforeEach( function() {
sinon.stub(Profile, 'findOne');
});
afterEach( function() {
Profile.findOne.restore();
});
it('should send a response', function() {
let mock_user_id = 'U5YEHNYBS';
let expectedModel = {
user_id: 'U5YEHNYBS',
user_name: 'gus',
skills: [ 'JavaScript', 'Node.js', 'Java', 'Fitness', 'Riding', 'backend']
};
let expectedResponse = {
'name': 'gus',
'skills': 'JavaScript, Node.js, Java, Fitness, Riding, backend'
};
let res = {
send: sinon.stub(),
status: sinon.stub()
};
sinon.stub(mongoose.Query.prototype, 'exec').yields(null, expectedResponse);
Profile.findOne.returns(expectedModel);
foo.testGetOneProfile(mock_user_id, res);
sinon.assert.calledWith(res.send, expectedResponse);
});
});
The test message:
1) testGetOneProfile should send a response:
TypeError: Profiles.findOne(...).exec is not a function
at Object.testGetOneProfile (bin\foo.js:187:10)
at Context.<anonymous> (test\foo.test.js:99:12)
This is a bit of a tricky scenario. The problem here is that the findOne stub in your test returns the model object - instead, it needs to return an object which contains a property exec which in turn is a promise-returning function that finally resolves into the model value... yeah, as mentioned, it's a bit tricky :)
Something like this:
const findOneResult = {
exec: sinon.stub().resolves(expectedModel)
}
Profile.findOne.returns(findOneResult);
You also need to have the status function on the response object return an object containing a send function
//if we set up the stub to return the res object
//it returns the necessary func
res.status.returns(res);
I think you shouldn't need to change anything else in the test and it might work like that. Note that you sinon 2.0 or newer for the resolves function to exist on the stub (or you can use sinon-as-promised with sinon 1.x)
This post goes into a bit more detail on how you can deal with complex objects like that:
https://codeutopia.net/blog/2016/05/23/sinon-js-quick-tip-how-to-stubmock-complex-objects-such-as-dom-objects/