Skip to content

Commit 9c2c963

Browse files
committed
add authorization tests and restructure tests
1 parent 2663838 commit 9c2c963

File tree

7 files changed

+172
-74
lines changed

7 files changed

+172
-74
lines changed

src/auth/authorization.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import express from 'express';
22
import { ProtectedRequest } from 'app-request';
33
import { AuthFailureError } from '../core/ApiError';
4-
import RoleRepository from '../database/repository/RoleRepo';
4+
import RoleRepo from '../database/repository/RoleRepo';
55
import asyncHandler from '../helpers/asyncHandler';
66

77
const router = express.Router();
@@ -11,7 +11,7 @@ export default router.use(
1111
if (!req.user || !req.user.roles || !req.currentRoleCode)
1212
throw new AuthFailureError('Permission denied');
1313

14-
const role = await RoleRepository.findByCode(req.currentRoleCode);
14+
const role = await RoleRepo.findByCode(req.currentRoleCode);
1515
if (!role) throw new AuthFailureError('Permission denied');
1616

1717
const validRoles = req.user.roles

tests/auth/apikey.test.ts renamed to tests/auth/apikey/index.test.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
1-
import app from '../../src/app';
1+
import { API_KEY, mockFindApiKey } from './mock'; // mock should be imported on the top
2+
import app from '../../../src/app';
23
import supertest from 'supertest';
3-
import { IApiKey } from '../../src/database/model/ApiKey';
4-
5-
export const API_KEY = 'abc';
6-
7-
export const mockFindApiKey = jest.fn(async (key: string) => {
8-
if (key == API_KEY) return <IApiKey>{ key: API_KEY };
9-
else return null;
10-
});
11-
12-
jest.mock('../../src/database/repository/ApiKeyRepo', () => ({
13-
get findByKey() { return mockFindApiKey; }
14-
}));
154

165
describe('apikey validation', () => {
176

tests/auth/apikey/mock.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { IApiKey } from '../../../src/database/model/ApiKey';
2+
3+
export const API_KEY = 'abc';
4+
5+
export const mockFindApiKey = jest.fn(async (key: string) => {
6+
if (key == API_KEY) return <IApiKey>{ key: API_KEY };
7+
else return null;
8+
});
9+
10+
jest.mock('../../../src/database/repository/ApiKeyRepo', () => ({
11+
get findByKey() { return mockFindApiKey; }
12+
}));
Lines changed: 7 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,19 @@
1-
import app from '../../src/app';
2-
import supertest, { SuperTest } from 'supertest';
3-
import { IUser } from '../../src/database/model/User';
4-
import { Types } from 'mongoose';
5-
import { mockFindApiKey, API_KEY } from './apikey.test';
6-
import JWT, { ValidationParams, JwtPayload } from '../../src/core/JWT';
7-
import { BadTokenError } from '../../src/core/ApiError';
8-
import { IKeystore } from '../../src/database/model/Keystore';
1+
import {
2+
USER_ID, ACCESS_TOKEN, addHeaders, addAuthHeaders,
3+
mockValidateTokenData, mockUserFindById, mockJwtValidate, mockKeystoreFindForKey
4+
} from './mock';
95

10-
export const ACCESS_TOKEN = 'xyz';
11-
12-
export const USER_ID = new Types.ObjectId('5e7b95923085872d3c378f35'); // random id with object id format
13-
14-
const mockUserFindById = jest.fn(async (id: Types.ObjectId) => {
15-
if (USER_ID.equals(id)) return <IUser>{ _id: new Types.ObjectId(id) };
16-
else return null;
17-
});
18-
19-
const mockJwtValidate = jest.fn(
20-
async (token: string, validations: ValidationParams): Promise<JwtPayload> => {
21-
if (token == ACCESS_TOKEN) return <JwtPayload>{ prm: 'abcdef' };
22-
throw new BadTokenError();
23-
});
24-
25-
const mockKeystoreFindForKey = jest.fn(
26-
async (client: IUser, key: string): Promise<IKeystore> => (<IKeystore>{ client: client, primaryKey: key }));
27-
28-
const mockValidateTokenData =
29-
jest.fn(async (payload: JwtPayload, userId: Types.ObjectId): Promise<JwtPayload> => payload);
30-
31-
jest.mock('../../src/auth/authUtils', () => ({
32-
get validateTokenData() { return mockValidateTokenData; }
33-
}));
34-
35-
jest.mock('../../src/database/repository/UserRepo', () => ({
36-
get findById() { return mockUserFindById; }
37-
}));
38-
39-
jest.mock('../../src/database/repository/ApiKeyRepo', () => ({
40-
get findByKey() { return mockFindApiKey; }
41-
}));
42-
43-
jest.mock('../../src/database/repository/KeystoreRepo', () => ({
44-
get findforKey() { return mockKeystoreFindForKey; }
45-
}));
46-
47-
JWT.validate = mockJwtValidate;
6+
import app from '../../../src/app';
7+
import supertest from 'supertest';
488

499
describe('authentication validation', () => {
5010

5111
const endpoint = '/v1/profile/my/test';
5212
const request = supertest(app);
5313

54-
5514
beforeEach(() => {
5615
mockValidateTokenData.mockClear();
5716
mockUserFindById.mockClear();
58-
mockFindApiKey.mockClear();
5917
mockJwtValidate.mockClear();
6018
mockKeystoreFindForKey.mockClear();
6119
});
@@ -115,14 +73,4 @@ describe('authentication validation', () => {
11573
expect(mockValidateTokenData).toBeCalledTimes(1);
11674
expect(mockJwtValidate).toBeCalledTimes(1);
11775
});
118-
});
119-
120-
export const addHeaders = (request: any) => request
121-
.set('Content-Type', 'application/json')
122-
.set('x-api-key', API_KEY);
123-
124-
export const addAuthHeaders = (request: any) => request
125-
.set('Content-Type', 'application/json')
126-
.set('x-api-key', API_KEY)
127-
.set('x-access-token', ACCESS_TOKEN)
128-
.set('x-user-id', USER_ID);
76+
});

tests/auth/authentication/mock.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// all dependent mock should be on the top
2+
import { API_KEY } from '../apikey/mock';
3+
4+
import { IUser } from '../../../src/database/model/User';
5+
import { Types } from 'mongoose';
6+
import JWT, { ValidationParams, JwtPayload } from '../../../src/core/JWT';
7+
import { BadTokenError } from '../../../src/core/ApiError';
8+
import { IKeystore } from '../../../src/database/model/Keystore';
9+
10+
export const ACCESS_TOKEN = 'xyz';
11+
12+
export const USER_ID = new Types.ObjectId('5e7b95923085872d3c378f35'); // random id with object id format
13+
14+
export const mockUserFindById = jest.fn(async (id: Types.ObjectId) => {
15+
if (USER_ID.equals(id)) return <IUser>{ _id: new Types.ObjectId(id) };
16+
else return null;
17+
});
18+
19+
export const mockJwtValidate = jest.fn(
20+
async (token: string, validations: ValidationParams): Promise<JwtPayload> => {
21+
if (token == ACCESS_TOKEN) return <JwtPayload>{ prm: 'abcdef' };
22+
throw new BadTokenError();
23+
});
24+
25+
export const mockKeystoreFindForKey = jest.fn(
26+
async (client: IUser, key: string): Promise<IKeystore> => (<IKeystore>{ client: client, primaryKey: key }));
27+
28+
export const mockValidateTokenData =
29+
jest.fn(async (payload: JwtPayload, userId: Types.ObjectId): Promise<JwtPayload> => payload);
30+
31+
jest.mock('../../../src/auth/authUtils', () => ({
32+
get validateTokenData() { return mockValidateTokenData; }
33+
}));
34+
35+
jest.mock('../../../src/database/repository/UserRepo', () => ({
36+
get findById() { return mockUserFindById; }
37+
}));
38+
39+
jest.mock('../../../src/database/repository/KeystoreRepo', () => ({
40+
get findforKey() { return mockKeystoreFindForKey; }
41+
}));
42+
43+
JWT.validate = mockJwtValidate;
44+
45+
export const addHeaders = (request: any) => request
46+
.set('Content-Type', 'application/json')
47+
.set('x-api-key', API_KEY);
48+
49+
export const addAuthHeaders = (request: any) => request
50+
.set('Content-Type', 'application/json')
51+
.set('x-api-key', API_KEY)
52+
.set('x-access-token', ACCESS_TOKEN)
53+
.set('x-user-id', USER_ID);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { addAuthHeaders } from '../authentication/mock';
2+
import { mockRoleRepoyFindByCode, mockUserFindByIdForWriter } from './mock';
3+
4+
import app from '../../../src/app';
5+
import supertest from 'supertest';
6+
import { RoleCode } from '../../../src/database/model/Role';
7+
8+
describe('authentication validation for editor', () => {
9+
10+
const endpoint = '/v1/editor/blog/test';
11+
const request = supertest(app);
12+
13+
beforeEach(() => {
14+
mockRoleRepoyFindByCode.mockClear();
15+
mockUserFindByIdForWriter.mockClear();
16+
});
17+
18+
it('Should response with 401 if user do not have editor role', async () => {
19+
const response = await addAuthHeaders(request.get(endpoint));
20+
expect(response.status).toBe(401);
21+
expect(response.body.message).toMatch(/denied/);
22+
expect(mockRoleRepoyFindByCode).toBeCalledTimes(1);
23+
expect(mockUserFindByIdForWriter).toBeCalledTimes(1);
24+
expect(mockRoleRepoyFindByCode).toBeCalledWith(RoleCode.EDITOR);
25+
});
26+
});
27+
28+
describe('authentication validation for writer', () => {
29+
30+
const endpoint = '/v1/writer/blog/test';
31+
const request = supertest(app);
32+
33+
beforeEach(() => {
34+
mockRoleRepoyFindByCode.mockClear();
35+
mockUserFindByIdForWriter.mockClear();
36+
});
37+
38+
it('Should response with 404 if user have writer role', async () => {
39+
const response = await addAuthHeaders(request.get(endpoint));
40+
expect(response.status).toBe(404);
41+
expect(mockRoleRepoyFindByCode).toBeCalledTimes(1);
42+
expect(mockUserFindByIdForWriter).toBeCalledTimes(1);
43+
expect(mockRoleRepoyFindByCode).toBeCalledWith(RoleCode.WRITER);
44+
});
45+
});

tests/auth/authorization/mock.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// all dependent mock should be on the top
2+
import { USER_ID } from '../authentication/mock';
3+
4+
import { Types } from 'mongoose';
5+
import { IUser } from '../../../src/database/model/User';
6+
import { RoleCode, IRole } from '../../../src/database/model/Role';
7+
8+
const LEARNER_ROLE_ID = new Types.ObjectId('5e7b95923085872d3c378f35'); // RONDOM ID
9+
const WRITER_ROLE_ID = new Types.ObjectId('56cb91bdc3464f14678934ca'); // RONDOM ID
10+
const EDITOR_ROLE_ID = new Types.ObjectId('58cd7cfcf9f1150515ee9fb0'); // RONDOM ID
11+
12+
export const mockUserFindByIdForWriter = jest.fn(async (id: Types.ObjectId) => {
13+
if (USER_ID.equals(id)) return <IUser>{
14+
_id: USER_ID,
15+
roles: [
16+
<IRole>{ _id: LEARNER_ROLE_ID, code: RoleCode.LEARNER },
17+
<IRole>{ _id: WRITER_ROLE_ID, code: RoleCode.WRITER },
18+
]
19+
};
20+
else return null;
21+
});
22+
23+
export const mockRoleRepoyFindByCode = jest.fn(
24+
async (code: string): Promise<IRole> => {
25+
switch (code) {
26+
case RoleCode.WRITER: return <IRole>{
27+
_id: WRITER_ROLE_ID,
28+
code: RoleCode.WRITER,
29+
status: true
30+
};
31+
case RoleCode.EDITOR: return <IRole>{
32+
_id: EDITOR_ROLE_ID,
33+
code: RoleCode.EDITOR,
34+
status: true
35+
};
36+
case RoleCode.LEARNER: return <IRole>{
37+
_id: LEARNER_ROLE_ID,
38+
code: RoleCode.LEARNER,
39+
status: true
40+
};
41+
}
42+
return null;
43+
});
44+
45+
jest.mock('../../../src/database/repository/UserRepo', () => ({
46+
get findById() { return mockUserFindByIdForWriter; }
47+
}));
48+
49+
jest.mock('../../../src/database/repository/RoleRepo', () => ({
50+
get findByCode() { return mockRoleRepoyFindByCode; }
51+
}));

0 commit comments

Comments
 (0)