Skip to content

Commit

Permalink
Merge pull request #19 from bcgov/feat/guard-get-record-and-validate
Browse files Browse the repository at this point in the history
WIP: Get Record and Validate Case/Incident (guard refactor)
  • Loading branch information
hannah-macdonald1 authored Nov 5, 2024
2 parents 14ff47e + e0a0b70 commit b9b023e
Show file tree
Hide file tree
Showing 19 changed files with 678 additions and 9 deletions.
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
NODE_ENV=production

RECORD_CACHE_MS = 60000

ACCESS_TOKEN_URL='url here'
CLIENT_ID='id here'
CLIENT_SECRET='secret here'
UPSTREAM_BASE_URL=http://www.google.com
SUPPORT_NETWORK_ENDPOINT=/endpoint/path/here
SUPPORT_NETWORK_ENDPOINT=/endpoint/path/here
CASE_ENDPOINT=/endpoint/path/here
INCIDENT_ENDPOINT=/endpoint/path/here
SR_ENDPOINT=/endpoint/path/here
MEMO_ENDPOINT=/endpoint/path/here
SKIP_AUTH_GUARD=false
5 changes: 5 additions & 0 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@ spec:
secretKeyRef:
name: visitz-api
key: CLIENT_SECRET
- name: SKIP_AUTH_GUARD
valueFrom:
secretKeyRef:
name: visitz-api
key: SKIP_AUTH_GUARD
restartPolicy: Always
37 changes: 37 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"save": "^2.9.0"
},
"devDependencies": {
"@jest-mock/express": "^2.1.0",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
Expand Down
12 changes: 10 additions & 2 deletions src/common/common.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
import { UtilitiesModule } from '../helpers/utilities/utilities.module';
import { UtilitiesService } from '../helpers/utilities/utilities.service';
import { AuthService } from './guards/auth/auth.service';
import { AuthModule } from './guards/auth/auth.module';
import { TokenRefresherModule } from '../helpers/token-refresher/token-refresher.module';

@Module({
providers: [],
imports: [],
providers: [UtilitiesService, AuthService, UtilitiesService, ConfigService],
imports: [UtilitiesModule, AuthModule, HttpModule, TokenRefresherModule],
exports: [AuthService],
})
export class CommonModule {}
133 changes: 133 additions & 0 deletions src/common/guards/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { HttpService } from '@nestjs/axios';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Controller, ExecutionContext, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TestingModule, Test } from '@nestjs/testing';
import { UtilitiesService } from '../../../helpers/utilities/utilities.service';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';
import { getMockReq } from '@jest-mock/express';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';

describe('AuthGuard', () => {
let service: AuthService;
let configService: ConfigService;
let guard;

@Controller()
class TestController {
@UseGuards(AuthGuard)
async example() {
return true;
}
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AuthService,
useValue: { getRecordAndValidate: () => jest.fn() },
},
TokenRefresherService,
{
provide: CACHE_MANAGER,
useValue: {
set: () => jest.fn(),
get: () => jest.fn(),
},
},
UtilitiesService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const lookup = {
skipAuthGuard: true,
};
return lookup[key];
}),
},
},
{ provide: HttpService, useValue: { get: jest.fn() } },
],
controllers: [TestController],
}).compile();

service = module.get<AuthService>(AuthService);
configService = module.get<ConfigService>(ConfigService);
const guards = Reflect.getMetadata(
'__guards__',
TestController.prototype.example,
);
guard = new guards[0](service, configService);
jest.clearAllMocks();
});
it('should be defined', () => {
expect(guard).toBeDefined();
expect(guard).toBeInstanceOf(AuthGuard);
});

describe('canActivate tests', () => {
it('should always return true when skipping', async () => {
const authSpy = jest
.spyOn(service, 'getRecordAndValidate')
.mockResolvedValueOnce(false);
const guardSpy = jest.spyOn(AuthGuard.prototype, 'canActivate');
const execContext = {
switchToHttp: () => ({
getRequest: () => getMockReq(),
}),
};
const isAuthed = await guard.canActivate(execContext as ExecutionContext);
expect(authSpy).toHaveBeenCalledTimes(0);
expect(guardSpy).toHaveBeenCalledTimes(1);
expect(isAuthed).toBe(true);
});

it('should return the result of getRecordAndValidate when not skipping', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
TokenRefresherService,
{ provide: CACHE_MANAGER, useValue: {} },
UtilitiesService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const lookup = {
skipAuthGuard: false,
};
return lookup[key];
}),
},
},

{ provide: HttpService, useValue: { get: jest.fn() } },
],
}).compile();
service = module.get<AuthService>(AuthService);
configService = module.get<ConfigService>(ConfigService);
const guards = Reflect.getMetadata(
'__guards__',
TestController.prototype.example,
);
guard = new guards[0](service, configService);

const authSpy = jest
.spyOn(service, 'getRecordAndValidate')
.mockResolvedValueOnce(false);
const guardSpy = jest.spyOn(AuthGuard.prototype, 'canActivate');
const execContext = {
switchToHttp: () => ({
getRequest: () => getMockReq(),
}),
};
const isAuthed = await guard.canActivate(execContext);
expect(authSpy).toHaveBeenCalledTimes(1);
expect(guardSpy).toHaveBeenCalledTimes(1);
expect(isAuthed).toBe(false);
});
});
});
26 changes: 26 additions & 0 deletions src/common/guards/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate {
skip;
constructor(
private readonly authService: AuthService,
private readonly configService: ConfigService,
) {
this.skip = this.configService.get('skipAuthGuard');
}

canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
if (this.skip) {
// skip for local development
return true;
}
const request = context.switchToHttp().getRequest();
return this.authService.getRecordAndValidate(request);
}
}
20 changes: 20 additions & 0 deletions src/common/guards/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
import { AuthService } from './auth.service';
import { UtilitiesModule } from '../../../helpers/utilities/utilities.module';
import { UtilitiesService } from '../../../helpers/utilities/utilities.service';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherModule } from '../../../helpers/token-refresher/token-refresher.module';

@Module({
providers: [
TokenRefresherService,
AuthService,
UtilitiesService,
ConfigService,
],
imports: [UtilitiesModule, HttpModule, TokenRefresherModule],
exports: [AuthService],
})
export class AuthModule {}
Loading

0 comments on commit b9b023e

Please sign in to comment.