javascriptjestjsauthorizationnestjsnestjs-jwt

Multiple overrideGuard configurations resulting into tests failures


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 :


Solution

  • 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.