-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve test coverage & fix exposed issues (#80)
* #79 Inject config into the AuthenticationService * #79 Add test coverage to AuthorisationService.requireUserRole * #79 Add tests to memory cache and fix exposed issues * #79 Add test coverage to UserService.loadUserDetailsAndUpdateIfNecessary * #79 Add docs to loadUserDetailsAndUpdateIfNecessary * #79 Use function overloading to properly type UserService.getUsers and fix issue where role was not included in filter * #79 Add unit test for getQuizDetails * #79 Add new user service function to load a single user * #79 Refactor and unit test createQuiz * #79 Ignore rest siblings eslint violation * #79 Throw explicit UnauthorisedError when unauthorised
- Loading branch information
1 parent
9b44d32
commit a6291c9
Showing
18 changed files
with
579 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export class UnauthorisedError extends Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { QuizlordContext } from '..'; | ||
import { UnauthorisedError } from './authorisation.errors'; | ||
import { AuthorisationService } from './authorisation.service'; | ||
|
||
describe('authorisation', () => { | ||
describe('authorisation.service', () => { | ||
const sut = new AuthorisationService(); | ||
describe('requireUserRole', () => { | ||
it('must do nothing if the user has the required role', () => { | ||
const context = { | ||
roles: ['USER', 'ADMIN'], | ||
} as QuizlordContext; | ||
sut.requireUserRole(context, 'ADMIN'); | ||
}); | ||
it('must throw an error if the user does not have the required role', () => { | ||
const context = { | ||
roles: ['USER'], | ||
} as QuizlordContext; | ||
expect(() => sut.requireUserRole(context, 'ADMIN')).toThrow(UnauthorisedError); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
import { Role } from '@prisma/client'; | ||
import { QuizlordContext } from '..'; | ||
import { UnauthorisedError } from './authorisation.errors'; | ||
|
||
export class AuthorisationService { | ||
requireUserRole(context: QuizlordContext, role: Role) { | ||
if (!context.roles.includes(role)) { | ||
throw new Error('You are not authorised to perform this action'); | ||
throw new UnauthorisedError('You are not authorised to perform this action'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export class MustProvideAtLeastOneFileError extends Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,44 @@ | ||
import { v4 as uuidv4 } from 'uuid'; | ||
|
||
import { QuizService } from './quiz.service'; | ||
import { QuizPersistence } from './quiz.persistence'; | ||
import { S3FileService } from '../file/s3.service'; | ||
import { Decimal } from '@prisma/client/runtime/library'; | ||
import { UserService } from '../user/user.service'; | ||
import { MustProvideAtLeastOneFileError } from './quiz.errors'; | ||
|
||
jest.mock('uuid'); | ||
|
||
const mockedUUIDv4 = jest.mocked(uuidv4); | ||
const mockPersistence = { | ||
getQuizzesWithUserResults: jest.fn(), | ||
createQuizWithImages: jest.fn(), | ||
getCompletionScoreWithQuizTypesForUser: jest.fn(), | ||
getQuizzesWithUserResults: jest.fn(), | ||
getQuizByIdWithResults: jest.fn(), | ||
}; | ||
const mockFileService = { | ||
createKey: jest.fn(), | ||
generateSignedUploadUrl: jest.fn(), | ||
}; | ||
const mockUserService = { | ||
getUser: jest.fn(), | ||
}; | ||
const mockFileService = {}; | ||
|
||
const sut = new QuizService(mockPersistence as unknown as QuizPersistence, mockFileService as S3FileService); | ||
const sut = new QuizService( | ||
mockPersistence as unknown as QuizPersistence, | ||
mockFileService as unknown as S3FileService, | ||
mockUserService as unknown as UserService, | ||
); | ||
|
||
describe('quiz', () => { | ||
describe('quiz.service', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
jest.restoreAllMocks(); | ||
}); | ||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
describe('getQuizzesWithUserResults', () => { | ||
it('must call getQuizzesWithUserResults on persistence with correct arguments and transform the result', async () => { | ||
const persistenceResult = [ | ||
|
@@ -180,5 +203,117 @@ describe('quiz', () => { | |
}); | ||
}); | ||
}); | ||
describe('getQuizDetails', () => { | ||
it('must call getQuizByIdWithResults on persistence with correct arguments and transform the result', async () => { | ||
mockPersistence.getQuizByIdWithResults.mockResolvedValueOnce({ | ||
id: 'fake-quiz-id', | ||
type: 'SHARK', | ||
date: new Date('2023-01-01'), | ||
uploadedAt: new Date('2023-01-02'), | ||
completions: [], | ||
images: [], | ||
uploadedByUser: { | ||
id: 'fake-user-id', | ||
email: '[email protected]', | ||
name: 'Master', | ||
}, | ||
}); | ||
|
||
const actual = await sut.getQuizDetails('fake-quiz-id'); | ||
|
||
expect(actual).toEqual({ | ||
id: 'fake-quiz-id', | ||
type: 'SHARK', | ||
date: new Date('2023-01-01'), | ||
uploadedAt: new Date('2023-01-02'), | ||
completions: [], | ||
images: [], | ||
uploadedBy: { | ||
id: 'fake-user-id', | ||
email: '[email protected]', | ||
name: 'Master', | ||
}, | ||
}); | ||
}); | ||
}); | ||
describe('createQuiz', () => { | ||
it('must throw if no files are provided', async () => { | ||
await expect(() => | ||
sut.createQuiz('fake-user', { type: 'SHARK', date: new Date('2022-01-01'), files: [] }), | ||
).rejects.toThrow(MustProvideAtLeastOneFileError); | ||
}); | ||
it('must create quiz with upload link for each image', async () => { | ||
const fakeNow = new Date('2023-02-12'); | ||
jest.useFakeTimers(); | ||
jest.setSystemTime(fakeNow); | ||
|
||
mockedUUIDv4.mockReturnValueOnce('fake-quiz-id'); | ||
mockUserService.getUser.mockResolvedValueOnce({ | ||
email: '[email protected]', | ||
}); | ||
mockFileService.createKey.mockReturnValueOnce('key-one').mockReturnValueOnce('key-two'); | ||
mockFileService.generateSignedUploadUrl | ||
.mockResolvedValueOnce('https://upload-one.net') | ||
.mockResolvedValueOnce('https://upload-two.net'); | ||
|
||
const actual = await sut.createQuiz('fake-user-id', { | ||
date: new Date('2022-01-01'), | ||
type: 'SHARK', | ||
files: [ | ||
{ | ||
fileName: 'questions.jpg', | ||
type: 'QUESTION', | ||
}, | ||
{ | ||
fileName: 'answers.jpg', | ||
type: 'ANSWER', | ||
}, | ||
], | ||
}); | ||
|
||
expect(mockedUUIDv4).toHaveBeenCalledTimes(1); | ||
expect(mockFileService.createKey).toHaveBeenCalledTimes(2); | ||
expect(mockFileService.createKey).toHaveBeenNthCalledWith(1, 'fake-quiz-id', 'questions.jpg'); | ||
expect(mockFileService.createKey).toHaveBeenNthCalledWith(2, 'fake-quiz-id', 'answers.jpg'); | ||
expect(mockFileService.generateSignedUploadUrl).toHaveBeenCalledTimes(2); | ||
expect(mockFileService.generateSignedUploadUrl).toHaveBeenNthCalledWith(1, 'key-one'); | ||
expect(mockFileService.generateSignedUploadUrl).toHaveBeenNthCalledWith(2, 'key-two'); | ||
expect(mockPersistence.createQuizWithImages).toHaveBeenCalledTimes(1); | ||
expect(mockPersistence.createQuizWithImages).toHaveBeenCalledWith( | ||
{ | ||
date: new Date('2022-01-01'), | ||
id: 'fake-quiz-id', | ||
type: 'SHARK', | ||
uploadedAt: new Date(fakeNow), | ||
uploadedByUserId: 'fake-user-id', | ||
}, | ||
[ | ||
{ imageKey: 'key-one', state: 'PENDING_UPLOAD', type: 'QUESTION' }, | ||
{ imageKey: 'key-two', state: 'PENDING_UPLOAD', type: 'ANSWER' }, | ||
], | ||
); | ||
|
||
expect(actual).toEqual({ | ||
quiz: { | ||
myCompletions: [], | ||
uploadedBy: { | ||
email: '[email protected]', | ||
id: 'fake-user-id', | ||
name: undefined, | ||
}, | ||
}, | ||
uploadLinks: [ | ||
{ | ||
fileName: 'questions.jpg', | ||
link: 'https://upload-one.net', | ||
}, | ||
{ | ||
fileName: 'answers.jpg', | ||
link: 'https://upload-two.net', | ||
}, | ||
], | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.