Skip to content

Commit

Permalink
feat(testing): Implement more supertest methods
Browse files Browse the repository at this point in the history
  • Loading branch information
spuxx-dev committed Oct 26, 2024
1 parent 052ce5e commit b4bdaaf
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 73 deletions.
3 changes: 1 addition & 2 deletions apps/nest/src/app.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ describe('AppController', () => {
const response = await supertest.get('/', {
session: {
sub: '123',
name: 'John Doe',
email: '[email protected]',
preferred_username: 'John Doe',
},
});
expect(response.statusCode).toBe(200);
Expand Down
1 change: 1 addition & 0 deletions packages/nest-utils/src/testing/supertest/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './supertest';
export * from './types';
81 changes: 73 additions & 8 deletions packages/nest-utils/src/testing/supertest/supertest.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Controller, Get } from '@nestjs/common';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { TestContainer } from '../container';
import { Supertest } from './supertest';
import { SessionResource } from '../../auth';
import { AuthGuard, Roles, SessionResource } from '../../auth';

describe('Supertest', () => {
@Controller()
Expand All @@ -10,22 +10,87 @@ describe('Supertest', () => {
get() {
return 'Hello World!';
}

@Get('/requires-authentication')
@UseGuards(AuthGuard)
requiresAuthentication() {
return "You're authenticated!";
}

@Get('/requires-authorization')
@UseGuards(AuthGuard)
@Roles('admin')
requiresAuthorization() {
return "You're authorized!";
}
}

let supertest: Supertest;

beforeEach(async () => {
const container = await TestContainer.create({
controllers: [MyController],
authOptions: {
roles: {
user: 'user',
admin: 'admin',
},
},
enableEndToEnd: true,
});
supertest = container.supertest;
});

describe('get', () => {
it('should trigger the endpoint', async () => {
const container = await TestContainer.create({ controllers: [MyController], enableEndToEnd: true });
const supertest = new Supertest(container.app);
it('should return 200', async () => {
const response = await supertest.get('/hello');
expect(response.status).toBe(200);
expect(response.text).toBe('Hello World!');
});

it('should return 401 on the endpoint that requires authentication', async () => {
const response = await supertest.get('/requires-authentication');
expect(response.status).toBe(401);
});

it('should return 200 on the endpoint that requires authentication', async () => {
const response = await supertest.get('/requires-authentication', {
session: {
sub: '123',
groups: ['user'],
},
});
expect(response.status).toBe(200);
});

it('should return 401 on the endpoint that requires authorization', async () => {
const response = await supertest.get('/requires-authorization');
expect(response.status).toBe(401);
});

it('should return 403 on the endpoint that requires authentication', async () => {
const response = await supertest.get('/requires-authorization', {
session: {
sub: '123',
groups: ['user'],
},
});
expect(response.status).toBe(403);
});

it('should return 200 on the endpoint that requires authentication', async () => {
const response = await supertest.get('/requires-authorization', {
session: {
sub: '123',
groups: ['user', 'admin'],
},
});
expect(response.status).toBe(200);
});
});

describe('setSession', () => {
describe('setupRequest', () => {
it("should set the 'X-Mock-Session' header", async () => {
const container = await TestContainer.create({ enableEndToEnd: true });
const supertest = new Supertest(container.app);
const session: Partial<SessionResource> = {
sub: '123',
};
Expand Down
134 changes: 71 additions & 63 deletions packages/nest-utils/src/testing/supertest/supertest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { INestApplication } from '@nestjs/common';
import supertest, { Request } from 'supertest';
import { SessionResource } from '../../auth';
import { SupertestOptions } from './types';

/**
* `Supertest` is an abstraction of the `supertest` package that allows easily faking
Expand All @@ -28,77 +29,84 @@ export class Supertest {
) {}

/**
* Creates a fake HTTP `GET` request.
* Emits a fake `GET` request.
* @param url The request URL.
* @param options.session (optional) The session to use for the request. Session must contain
* at least `sub` to be considered an authenticated session.
* @param options (optional) Additional options.
* @returns The request.
* @example
* const response = await supertest.get("/hello");
* expect(response.statusCode).toBe(200);
*/
get(url: string, options?: { session?: Partial<SessionResource> }): Request {
const httpServer = this.app.getHttpServer();
const request: Request = supertest(httpServer).get(url);
this.setSession(request, options?.session);
get(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).get(url);
this.setupRequest(request, options);
return request;
}

/**
* Sets the session for the request, simulating an authenticated or possibly authorized
* request.
* @param request The request.
* @param session The session.
* Emits a fake `POST` request.
* @param url The request URL.
* @param options (optional) Additional options.
* @returns The request.
*/
private setSession(request: Request, session?: Partial<SessionResource>) {
session = session ?? this.session;
if (session) request.set('X-Mock-Session', JSON.stringify(session));
post(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).post(url);
this.setupRequest(request, options);
return request;
}
}

// /**
// * Creates a fake get request.
// * @param app The Nest application instance.
// * @param url The request url.
// * @returns The request.
// * @example
// * import { fakeGetRequest } from 'test/helpers/app-e2e-spec.ts';
// * import { AuthRole } from 'src/auth/config/roles.config';
// *
// * const request = fakeGetRequest(app, '/test/simBundles', { roles: [AuthRole.frankfurtDispatcher] })
// * const response = request.send();
// */
// export const fakeGetRequest = (app: INestApplication, url: string) => {
// const httpServer = app.getHttpServer();
// const request = supertest(httpServer).get(url);
// // if (options?.roles) {
// // const roleIds = options.roles.map((roleKey) => {
// // return AUTH_ROLES[roleKey].test.id;
// // });
// // request.set('groups', roleIds.join(','));
// // }
// };
/**
* Emits a fake `PUT` request.
* @param url The request URL.
* @param options (optional) Additional options.
* @returns The request.
*/
put(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).put(url);
this.setupRequest(request, options);
return request;
}

// /**
// * Creates a fake post request.
// * @param app The Nest application instance.
// * @param url The request url.
// * @returns The request.
// * @example
// * import { fakeGetRequest } from 'test/helpers/app-e2e-spec.ts';
// * import { AuthRole } from 'src/auth/config/roles.config';
// *
// * const request = fakeGetRequest(app, '/test/simBundles', { roles: [AuthRole.frankfurtDispatcher] })
// * const response = request.send({ foo:"bar" });
// */
// export const fakePostRequest = (app: INestApplication, url: string) => {
// const httpServer = app.getHttpServer();
// const request = supertest(httpServer).post(url);
// // if (options?.roles) {
// // const roleIds = options.roles.map((roleKey) => {
// // return AUTH_ROLES[roleKey].test.id;
// // });
// // request.set('groups', roleIds.join(','));
// // }
// return request;
// };
/**
* Emits a fake `PATCH` request.
* @param url The request URL.
* @param options (optional) Additional options.
* @returns The request.
*/
patch(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).patch(url);
this.setupRequest(request, options);
return request;
}

/**
* Emits a fake `DELETE` request.
* @param url The request URL.
* @param options (optional) Additional options.
* @returns The request.
*/
delete(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).delete(url);
this.setupRequest(request, options);
return request;
}

/**
* Emits a fake `OPTIONS` request.
* @param url The request URL.
* @param options (optional) Additional options.
* @returns The request.
*/
options(url: string, options?: SupertestOptions): Request {
const request: Request = supertest(this.server).options(url);
this.setupRequest(request, options);
return request;
}

private get server() {
return this.app.getHttpServer();
}

private setupRequest(request: Request, options?: SupertestOptions) {
const { session, body } = { session: this.session, ...options };
if (session) request.set('X-Mock-Session', JSON.stringify(session));
if (body) request.send(body);
}
}
13 changes: 13 additions & 0 deletions packages/nest-utils/src/testing/supertest/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SessionResource } from '../../auth';

export interface SupertestOptions {
/**
* The session resource that should be attached to the request. May be used to simulate
* authenticated or authorized requests.
*/
session?: Partial<SessionResource>;
/**
* The request body.
*/
body?: object;
}

0 comments on commit b4bdaaf

Please sign in to comment.