Skip to content

Commit

Permalink
refactor: convert analytics module to TypeScript/fp-ts, remove statis…
Browse files Browse the repository at this point in the history
…tics animation on landing page (#1361)
  • Loading branch information
liangyuanruo authored Mar 16, 2021
1 parent ef6bd4c commit 8ce6479
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 219 deletions.
156 changes: 149 additions & 7 deletions src/app/modules/analytics/__tests__/analytics.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { errAsync, okAsync } from 'neverthrow'
import * as E from 'fp-ts/lib/Either'
import * as TE from 'fp-ts/TaskEither'

import expressHandler from 'tests/unit/backend/helpers/jest-express'

Expand All @@ -17,7 +18,7 @@ describe('analytics.controller', () => {
const mockUserCount = 21
const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(okAsync(mockUserCount))
.mockReturnValueOnce(TE.of(mockUserCount))

// Act
await AnalyticsController.handleGetUserCount(MOCK_REQ, mockRes, jest.fn())
Expand All @@ -32,7 +33,7 @@ describe('analytics.controller', () => {
const mockRes = expressHandler.mockResponse()
const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(errAsync(new DatabaseError()))
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))

// Act
await AnalyticsController.handleGetUserCount(MOCK_REQ, mockRes, jest.fn())
Expand All @@ -53,7 +54,7 @@ describe('analytics.controller', () => {
const mockRes = expressHandler.mockResponse()
const getSubsSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(okAsync(mockSubmissionCount))
.mockReturnValueOnce(TE.of(mockSubmissionCount))

// Act
await AnalyticsController.handleGetSubmissionCount(
Expand All @@ -73,7 +74,7 @@ describe('analytics.controller', () => {
const mockRes = expressHandler.mockResponse()
const getSubsSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(errAsync(new DatabaseError()))
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))

// Act
await AnalyticsController.handleGetSubmissionCount(
Expand All @@ -98,7 +99,7 @@ describe('analytics.controller', () => {
const mockRes = expressHandler.mockResponse()
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(okAsync(mockFormCount))
.mockReturnValueOnce(TE.of(mockFormCount))

// Act
await AnalyticsController.handleGetFormCount(MOCK_REQ, mockRes, jest.fn())
Expand All @@ -114,7 +115,7 @@ describe('analytics.controller', () => {
const mockRes = expressHandler.mockResponse()
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(errAsync(new DatabaseError()))
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))

// Act
await AnalyticsController.handleGetFormCount(MOCK_REQ, mockRes, jest.fn())
Expand All @@ -127,4 +128,145 @@ describe('analytics.controller', () => {
)
})
})

describe('handleGetStatistics', () => {
it('should return HTTP 200 when calls to AnalyticsService do not return any errors', async () => {
// Arrange
const mockUserCount = 10
const mockFormCount = 20
const mockSubmissionCount = 100

const mockRes = expressHandler.mockResponse()

const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(TE.of(mockUserCount))
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(TE.of(mockFormCount))
const getSubmissionSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(TE.of(mockSubmissionCount))

// Act
await AnalyticsController.handleGetStatistics(
MOCK_REQ,
mockRes,
jest.fn(),
)

// Assert
expect(getUserSpy).toHaveBeenCalledTimes(1)
expect(getFormSpy).toHaveBeenCalledTimes(1)
expect(getSubmissionSpy).toHaveBeenCalledTimes(1)
expect(mockRes.status).not.toHaveBeenCalled()
expect(mockRes.json).toHaveBeenCalledWith({
userCount: mockUserCount,
formCount: mockFormCount,
submissionCount: mockSubmissionCount,
})
})

it('should return HTTP 500 when calls to AnalyticsService.getUserCount fail', async () => {
// Arrange
const mockFormCount = 20
const mockSubmissionCount = 100

const mockRes = expressHandler.mockResponse()

const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(TE.of(mockFormCount))
const getSubmissionSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(TE.of(mockSubmissionCount))

// Act
await AnalyticsController.handleGetStatistics(
MOCK_REQ,
mockRes,
jest.fn(),
)

// Assert
expect(getUserSpy).toHaveBeenCalledTimes(1)
expect(getFormSpy).toHaveBeenCalledTimes(1)
expect(getSubmissionSpy).toHaveBeenCalledTimes(1)
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith(
'Unable to retrieve statistics from the database',
)
})

it('should return HTTP 500 when calls to AnalyticsService.getFormCount fails', async () => {
// Arrange
const mockUserCount = 10
const mockSubmissionCount = 100

const mockRes = expressHandler.mockResponse()

const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(TE.of(mockUserCount))
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))
const getSubmissionSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(TE.of(mockSubmissionCount))

// Act
await AnalyticsController.handleGetStatistics(
MOCK_REQ,
mockRes,
jest.fn(),
)

// Assert
expect(getUserSpy).toHaveBeenCalledTimes(1)
expect(getFormSpy).toHaveBeenCalledTimes(1)
expect(getSubmissionSpy).toHaveBeenCalledTimes(1)
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith(
'Unable to retrieve statistics from the database',
)
})

it('should return HTTP 500 when calls to AnalyticsService.getSubmissionCount fails', async () => {
// Arrange
const mockUserCount = 10
const mockFormCount = 20

const mockRes = expressHandler.mockResponse()

const getUserSpy = jest
.spyOn(AnalyticsService, 'getUserCount')
.mockReturnValueOnce(TE.of(mockUserCount))
const getFormSpy = jest
.spyOn(AnalyticsService, 'getFormCount')
.mockReturnValueOnce(TE.of(mockFormCount))
const getSubmissionSpy = jest
.spyOn(AnalyticsService, 'getSubmissionCount')
.mockReturnValueOnce(TE.fromEither(E.left(new DatabaseError())))

// Act
await AnalyticsController.handleGetStatistics(
MOCK_REQ,
mockRes,
jest.fn(),
)

// Assert
expect(getUserSpy).toHaveBeenCalledTimes(1)
expect(getFormSpy).toHaveBeenCalledTimes(1)
expect(getSubmissionSpy).toHaveBeenCalledTimes(1)
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith(
'Unable to retrieve statistics from the database',
)
})
})
})
8 changes: 8 additions & 0 deletions src/app/modules/analytics/__tests__/analytics.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Error thrown only in the analytic module's testing code.
*/
export class AnalyticsTestError extends Error {
constructor(message?: string) {
super(message)
}
}
72 changes: 39 additions & 33 deletions src/app/modules/analytics/__tests__/analytics.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as E from 'fp-ts/lib/Either'
import { times } from 'lodash'
import mongoose, { Query } from 'mongoose'

Expand All @@ -20,6 +21,8 @@ import {
getUserCount,
} from '../analytics.service'

import { AnalyticsTestError } from './analytics.error'

const FormModel = getFormModel(mongoose)
const SubmissionModel = getSubmissionModel(mongoose)
const UserModel = getUserModel(mongoose)
Expand Down Expand Up @@ -49,11 +52,11 @@ describe('analytics.service', () => {
expect(initialCount).toEqual(0)

// Act
const actualResult = await getFormCount()

const actualTE = getFormCount()
const actualE = await actualTE()
// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(0)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(0)
})

it('should return number of forms in the database', async () => {
Expand All @@ -72,11 +75,11 @@ describe('analytics.service', () => {
expect(initialCount).toEqual(expectedNum)

// Act
const actualResult = await getFormCount()

const actualTE = await getFormCount()
const actualE = await actualTE()
// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(expectedNum)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(expectedNum)
})

it('should return DatabaseError when error occurs whilst retrieving form count', async () => {
Expand All @@ -87,12 +90,12 @@ describe('analytics.service', () => {
} as unknown) as Query<number>)

// Act
const actualResult = await getFormCount()

const actualTE = await getFormCount()
const actualE = await actualTE()
// Assert
expect(execSpy).toHaveBeenCalledTimes(1)
expect(actualResult.isErr()).toEqual(true)
expect(actualResult._unsafeUnwrapErr()).toEqual(new DatabaseError())
if (E.isRight(actualE)) throw new AnalyticsTestError()
expect(actualE.left instanceof DatabaseError).toBe(true)
})
})

Expand All @@ -112,11 +115,11 @@ describe('analytics.service', () => {
expect(initialUserCount).toEqual(0)

// Act
const actualResult = await getUserCount()

const actualTE = await getUserCount()
const actualE = await actualTE()
// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(0)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(0)
})

it('should return number of users in the database', async () => {
Expand All @@ -133,11 +136,11 @@ describe('analytics.service', () => {
expect(initialUserCount).toEqual(expectedNumUsers)

// Act
const actualResult = await getUserCount()

const actualTE = await getUserCount()
const actualE = await actualTE()
// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(expectedNumUsers)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(expectedNumUsers)
})

it('should return DatabaseError when error occurs whilst retrieving user count', async () => {
Expand All @@ -148,12 +151,12 @@ describe('analytics.service', () => {
} as unknown) as Query<number>)

// Act
const actualResult = await getUserCount()

const actualTE = await getUserCount()
const actualE = await actualTE()
// Assert
expect(execSpy).toHaveBeenCalledTimes(1)
expect(actualResult.isErr()).toEqual(true)
expect(actualResult._unsafeUnwrapErr()).toEqual(new DatabaseError())
if (E.isRight(actualE)) throw new AnalyticsTestError()
expect(actualE.left instanceof DatabaseError).toBe(true)
})
})

Expand All @@ -164,11 +167,12 @@ describe('analytics.service', () => {
expect(initialSubCount).toEqual(0)

// Act
const actualResult = await getSubmissionCount()
const actualTE = await getSubmissionCount()
const actualE = await actualTE()

// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(0)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(0)
})

it('should return number of submissions in the database', async () => {
Expand All @@ -188,11 +192,12 @@ describe('analytics.service', () => {
expect(initialUserCount).toEqual(expectedNumSubs)

// Act
const actualResult = await getSubmissionCount()
const actualTE = await getSubmissionCount()
const actualE = await actualTE()

// Assert
expect(actualResult.isOk()).toEqual(true)
expect(actualResult._unsafeUnwrap()).toEqual(expectedNumSubs)
if (E.isLeft(actualE)) throw new AnalyticsTestError()
expect(actualE.right).toEqual(expectedNumSubs)
})

it('should return DatabaseError when error occurs whilst retrieving submission count', async () => {
Expand All @@ -205,12 +210,13 @@ describe('analytics.service', () => {
} as unknown) as Query<number>)

// Act
const actualResult = await getSubmissionCount()
const actualTE = await getSubmissionCount()
const actualE = await actualTE()

// Assert
expect(execSpy).toHaveBeenCalledTimes(1)
expect(actualResult.isErr()).toEqual(true)
expect(actualResult._unsafeUnwrapErr()).toEqual(new DatabaseError())
if (E.isRight(actualE)) throw new AnalyticsTestError()
expect(actualE.left instanceof DatabaseError).toBe(true)
})
})
})
Loading

0 comments on commit 8ce6479

Please sign in to comment.