Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: migrate get single encrypt submission flow to TypeScript #670

Merged
merged 12 commits into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions src/app/controllers/encrypt-submissions.server.controller.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
'use strict'
const crypto = require('crypto')
const moment = require('moment-timezone')
const { StatusCodes } = require('http-status-codes')

const mongoose = require('mongoose')
const errorHandler = require('../utils/handle-mongo-error')
const {
getEncryptSubmissionModel,
} = require('../models/submission.server.model')
const getSubmissionModel = require('../models/submission.server.model').default
const Submission = getSubmissionModel(mongoose)
const EncryptSubmission = getEncryptSubmissionModel(mongoose)

const { checkIsEncryptedEncoding } = require('../utils/encryption')
Expand Down Expand Up @@ -268,66 +265,3 @@ exports.getMetadata = function (req, res) {
})
}
}

/**
* Return actual encrypted form responses matching submission id
* @param {Object} req - Express request object
* @param {String} req.query.submissionId - submission to return data for
* @param {Object} req.form - the form
* @param {Object} res - Express response object
*/
exports.getEncryptedResponse = function (req, res) {
let { submissionId } = req.query || {}

Submission.findOne(
{
form: req.form._id,
_id: submissionId,
submissionType: 'encryptSubmission',
},
{
encryptedContent: 1,
verifiedContent: 1,
attachmentMetadata: 1,
created: 1,
},
).exec(async (err, response) => {
if (err || !response) {
logger.error({
message: 'Failure retrieving encrypted submission from database',
meta: {
action: 'getEncryptedResponse',
...createReqMeta(req),
},
error: err,
})
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: errorHandler.getMongoErrorMessage(err),
})
} else {
const entry = {
refNo: response._id,
submissionTime: moment(response.created)
.tz('Asia/Singapore')
.format('ddd, D MMM YYYY, hh:mm:ss A'),
content: response.encryptedContent,
verified: response.verifiedContent,
}
// make sure client obtains S3 presigned URLs to download attachments
if (response.attachmentMetadata) {
const attachmentMetadata = {}
for (let [key, objectPath] of response.attachmentMetadata) {
attachmentMetadata[key] = await s3.getSignedUrlPromise('getObject', {
Bucket: attachmentS3Bucket,
Key: objectPath,
Expires: req.session.cookie.maxAge / 1000, // Remaining login duration in seconds
})
}
entry.attachmentMetadata = attachmentMetadata
} else {
entry.attachmentMetadata = {}
}
return res.json(entry)
}
})
}
19 changes: 19 additions & 0 deletions src/app/models/submission.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,25 @@ const getSubmissionCursorByFormId: IEncryptSubmissionModel['getSubmissionCursorB

EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = getSubmissionCursorByFormId

EncryptSubmissionSchema.statics.findEncryptedSubmissionById = function (
this: IEncryptSubmissionModel,
formId: string,
submissionId: string,
) {
return this.findOne({
_id: submissionId,
form: formId,
submissionType: SubmissionType.Encrypt,
})
.select({
encryptedContent: 1,
verifiedContent: 1,
attachmentMetadata: 1,
created: 1,
})
.exec()
}

const compileSubmissionModel = (db: Mongoose): ISubmissionModel => {
const Submission = db.model('Submission', SubmissionSchema)
Submission.discriminator(SubmissionType.Email, EmailSubmissionSchema)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { errAsync, okAsync } from 'neverthrow'
import { mocked } from 'ts-jest/utils'

import { DatabaseError } from 'src/app/modules/core/core.errors'
import { CreatePresignedUrlError } from 'src/app/modules/form/admin-form/admin-form.errors'
import { SubmissionData } from 'src/types'

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

import { SubmissionNotFoundError } from '../../submission.errors'
import { handleGetEncryptedResponse } from '../encrypt-submission.controller'
import * as EncryptSubmissionService from '../encrypt-submission.service'

jest.mock('../encrypt-submission.service')
const MockEncryptSubService = mocked(EncryptSubmissionService)

describe('encrypt-submission.controller', () => {
describe('handleGetEncryptedResponse', () => {
const MOCK_REQ = expressHandler.mockRequest({
params: { formId: 'mockFormId' },
query: { submissionId: 'mockSubmissionId' },
session: {
cookie: {
maxAge: 20000,
mantariksh marked this conversation as resolved.
Show resolved Hide resolved
},
},
})

it('should return 200 with encrypted response', async () => {
// Arrange
const mockSubData: SubmissionData = {
_id: 'some id',
encryptedContent: 'some encrypted content',
verifiedContent: 'some verified content',
created: new Date('2020-10-10'),
} as SubmissionData
const mockSignedUrls = {
someKey1: 'some-signed-url',
someKey2: 'another-signed-url',
}
const mockRes = expressHandler.mockResponse()

// Mock service responses.
MockEncryptSubService.getEncryptedSubmissionData.mockReturnValueOnce(
okAsync(mockSubData),
)
MockEncryptSubService.transformAttachmentMetasToSignedUrls.mockReturnValueOnce(
okAsync(mockSignedUrls),
)

// Act
await handleGetEncryptedResponse(MOCK_REQ, mockRes, jest.fn())

// Assert
const expected = {
refNo: mockSubData._id,
submissionTime: 'Sat, 10 Oct 2020, 08:00:00 AM',
content: mockSubData.encryptedContent,
verified: mockSubData.verifiedContent,
attachmentMetadata: mockSignedUrls,
}
expect(mockRes.json).toHaveBeenCalledWith(expected)
})

it('should return 404 when submissionId cannot be found in the database', async () => {
// Arrange
const mockErrorString = 'not found'
MockEncryptSubService.getEncryptedSubmissionData.mockReturnValueOnce(
errAsync(new SubmissionNotFoundError(mockErrorString)),
)
const mockRes = expressHandler.mockResponse()

// Act
await handleGetEncryptedResponse(MOCK_REQ, mockRes, jest.fn())

// Assert
expect(mockRes.status).toHaveBeenCalledWith(404)
expect(mockRes.json).toHaveBeenCalledWith({ message: mockErrorString })
})

it('should return 500 when database error occurs', async () => {
// Arrange
const mockErrorString = 'database error occurred'
MockEncryptSubService.getEncryptedSubmissionData.mockReturnValueOnce(
errAsync(new DatabaseError(mockErrorString)),
)
const mockRes = expressHandler.mockResponse()

// Act
await handleGetEncryptedResponse(MOCK_REQ, mockRes, jest.fn())

// Assert
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith({ message: mockErrorString })
})

it('should return 500 when error occurs when generating presigned URLs', async () => {
// Arrange
const mockErrorString = 'presigned url error occured'
MockEncryptSubService.getEncryptedSubmissionData.mockReturnValueOnce(
okAsync({} as SubmissionData),
)
MockEncryptSubService.transformAttachmentMetasToSignedUrls.mockReturnValueOnce(
errAsync(new CreatePresignedUrlError(mockErrorString)),
)

const mockRes = expressHandler.mockResponse()

// Act
await handleGetEncryptedResponse(MOCK_REQ, mockRes, jest.fn())

// Assert
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith({ message: mockErrorString })
})
})
})
Loading