I have a nestjs application and some nestjs tests written in jest and they make use of guards. So in the tests i have created fake guards and trying to override them.
Due to multiple overrideGuard configurations I am seeing test failures and my test failures are like
● Organization › POST /organizations
expected 201 "Created", got 500 "Internal Server Error"
at Test.Object.<anonymous>.Test._assertStatus (node_modules/supertest/lib/test.js:268:12)
at Test.Object.<anonymous>.Test._assertFunction (node_modules/supertest/lib/test.js:283:11)
at Test.Object.<anonymous>.Test.assert (node_modules/supertest/lib/test.js:173:18)
at Server.localAssert (node_modules/supertest/lib/test.js:131:12)
Other error is and i think the main reason for above error would be as well
[Nest] 94156 - 09/21/2022, 11:06:43 PM ERROR [ExceptionsHandler] Invalid role(s): []
AccessControlError: Invalid role(s): []
Guards are bound to endpoint in this way, attaching some part of the code
import * as common from "@nestjs/common";
import * as swagger from "@nestjs/swagger";
import * as nestAccessControl from "nest-access-control";
import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
export class OrganizationControllerBase {
constructor(
protected readonly service: OrganizationService,
protected readonly rolesBuilder: nestAccessControl.RolesBuilder
) {}
@common.UseInterceptors(AclValidateRequestInterceptor)
@nestAccessControl.UseRoles({
resource: "Organization",
action: "create",
possession: "any",
})
@common.Post()
@swagger.ApiCreatedResponse({ type: Organization })
@swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
async create(
@common.Body() data: OrganizationCreateInput
): Promise<Organization> {
return await this.service.create({
data: data,
select: {
id: true,
createdAt: true,
updatedAt: true,
name: true,
},
});
}
}
Some parts of spec code(tests) looks like below
const basicAuthGuard = {
canActivate: (context: ExecutionContext) => {
const argumentHost = context.switchToHttp();
const request = argumentHost.getRequest();
request.user = {
roles: ["user"],
};
return true;
},
};
const acGuard = {
canActivate: () => {
return true;
},
};
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: OrganizationService,
useValue: service,
},
],
controllers: [OrganizationController],
imports: [MorganModule.forRoot(), ACLModule],
})
.overrideGuard(DefaultAuthGuard)
.useValue(basicAuthGuard)
.overrideGuard(ACGuard)
.useValue(acGuard)
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
When I removed the .overrideGuard(ACGuard).useValue(acGuard)
then test passed fine but I am not sure why is that happening ?
Why the later overrideGuard setting cancelling the previous overrideGuard configuration ?
Edit 1 :
Project on github : https://github.com/shubhwip/sample-app
Steps to reproduce :
npm install
npm run test
Your guard mocks are actually working fine, and you can verify this by adding in a console.log()
to your mocks. The issue is coming from your AclFilterResponseInterceptor
and AclValidateRequestInterceptor
interceptors, where you pass role: permissionRoles.role
. That value is undefined
, so when going into the accesscontrol
library, it is invalid and the error is thrown subsequently.
I was able to fix this in your local code by using role: context.switchToHttp().getRequest().user.roles
.
I believe this fails when mocking the AC guard because the AcGuard
actually mutates the roles
metadata. By modifying a reference to the roles
, the metadata pulled later in the interceptor is the mutated metadata, not the directly set metadata. One of JavaScript's pass-by-reference quirks with objects.