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

test(adminsubmissionservice): unit tests for download methods #2129

Merged
merged 2 commits into from
Jun 10, 2021
Merged
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
155 changes: 144 additions & 11 deletions src/public/services/__tests__/AdminSubmissionsService.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { decode } from '@stablelib/base64'
import axios from 'axios'
import JSZip from 'jszip'
import { mocked } from 'ts-jest/utils'

import { SubmissionMetadataList } from 'src/types'

import {
countFormSubmissions,
getEncryptedResponse,
getSubmissionMetadataById,
getSubmissionsMetadataByPage,
} from '../AdminSubmissionsService'
import * as AdminSubmissionService from '../AdminSubmissionsService'
import * as formsSdk from '../FormSgSdkService'
import { ADMIN_FORM_ENDPOINT } from '../UpdateFormService'

jest.mock('axios')
const MockAxios = mocked(axios, true)

jest.mock('../FormSgSdkService')

const mockFormSgSdk = mocked(formsSdk.FormSgSdk, true)

jest.mock('@stablelib/base64')
const mockDecodeBase64 = mocked(decode)

describe('AdminSubmissionsService', () => {
describe('countFormSubmissions', () => {
const MOCK_FORM_ID = 'mock–form-id'
Expand All @@ -25,7 +30,7 @@ describe('AdminSubmissionsService', () => {
MockAxios.get.mockResolvedValueOnce({ data: 123 })

// Act
const actual = await countFormSubmissions({
const actual = await AdminSubmissionService.countFormSubmissions({
formId: MOCK_FORM_ID,
dates: { startDate: MOCK_START_DATE, endDate: MOCK_END_DATE },
})
Expand All @@ -48,7 +53,7 @@ describe('AdminSubmissionsService', () => {
MockAxios.get.mockResolvedValueOnce({ data: 123 })

// Act
const actual = await countFormSubmissions({
const actual = await AdminSubmissionService.countFormSubmissions({
formId: MOCK_FORM_ID,
})

Expand Down Expand Up @@ -79,7 +84,7 @@ describe('AdminSubmissionsService', () => {
MockAxios.get.mockResolvedValueOnce({ data: MOCK_RESPONSE })

// Act
const actual = await getSubmissionsMetadataByPage({
const actual = await AdminSubmissionService.getSubmissionsMetadataByPage({
formId: MOCK_FORM_ID,
pageNum: MOCK_PAGE_NUM,
})
Expand Down Expand Up @@ -116,7 +121,7 @@ describe('AdminSubmissionsService', () => {
MockAxios.get.mockResolvedValueOnce({ data: MOCK_RESPONSE })

// Act
const actual = await getSubmissionMetadataById({
const actual = await AdminSubmissionService.getSubmissionMetadataById({
formId: MOCK_FORM_ID,
submissionId: MOCK_SUBMISSION_ID,
})
Expand Down Expand Up @@ -150,7 +155,7 @@ describe('AdminSubmissionsService', () => {
MockAxios.get.mockResolvedValueOnce({ data: MOCK_RESPONSE })

// Act
const actual = await getEncryptedResponse({
const actual = await AdminSubmissionService.getEncryptedResponse({
formId: MOCK_FORM_ID,
submissionId: MOCK_SUBMISSION_ID,
})
Expand All @@ -162,4 +167,132 @@ describe('AdminSubmissionsService', () => {
)
})
})

describe('downloadAndDecryptAttachment', () => {
const MOCK_URL = 'www.decryptme.com'
const MOCK_SECRET_KEY = 'this is a secret'
const MOCK_PUBLIC_KEY = 'some public key'
const MOCK_NONCE = 'use me once and throw away pls'
const MOCK_BINARY =
'01101001 00100000 01100001 01101101 00100000 01100001 00100000 01101000 01110101 01101101 01100001 01101110'

it('should decrypt successfully when there is data', async () => {
seaerchin marked this conversation as resolved.
Show resolved Hide resolved
// Arrange
mockFormSgSdk.crypto.decryptFile.mockReturnValueOnce('great decryption')
const MOCK_ENCRYPTED_ATTACHMENT = {
encryptedFile: {
submissionPublicKey: MOCK_PUBLIC_KEY,
nonce: MOCK_NONCE,
binary: MOCK_BINARY,
},
}
const MOCK_ARRAY = Uint8Array.from([1, 2, 3, 4])
MockAxios.get.mockResolvedValueOnce({ data: MOCK_ENCRYPTED_ATTACHMENT })
mockDecodeBase64.mockReturnValueOnce(MOCK_ARRAY)

// Act
const actual = await AdminSubmissionService.downloadAndDecryptAttachment(
MOCK_URL,
MOCK_SECRET_KEY,
)

// Arrange
expect(mockDecodeBase64).toHaveBeenCalledWith(MOCK_BINARY)
expect(mockFormSgSdk.crypto.decryptFile).toHaveBeenCalledWith(
MOCK_SECRET_KEY,
MOCK_ENCRYPTED_ATTACHMENT.encryptedFile,
)
expect(actual).toBe('great decryption')
})

it('should return null when the sdk returns null', async () => {
// Arrange
mockFormSgSdk.crypto.decryptFile.mockResolvedValueOnce(null)
const MOCK_ENCRYPTED_ATTACHMENT = {
encryptedFile: {
submissionPublicKey: MOCK_PUBLIC_KEY,
nonce: MOCK_NONCE,
binary: MOCK_BINARY,
},
}
const MOCK_ARRAY = Uint8Array.from([1, 2, 3, 4])
MockAxios.get.mockResolvedValueOnce({ data: MOCK_ENCRYPTED_ATTACHMENT })
mockDecodeBase64.mockReturnValueOnce(MOCK_ARRAY)

// Act
const actual = await AdminSubmissionService.downloadAndDecryptAttachment(
MOCK_URL,
MOCK_SECRET_KEY,
)

// Arrange
expect(mockDecodeBase64).toHaveBeenCalledWith(MOCK_BINARY)
expect(mockFormSgSdk.crypto.decryptFile).toHaveBeenCalledWith(
MOCK_SECRET_KEY,
MOCK_ENCRYPTED_ATTACHMENT.encryptedFile,
)
expect(actual).toBe(null)
})
})

describe('downloadAndDecryptAttachmentsAsZip', () => {
const MOCK_DOWNLOAD_URLS = new Map([
[1, { url: 'something', filename: '1' }],
[2, { url: 'something', filename: '1' }],
])
const MOCK_SECRET_KEY = 'keep this secret'
const MOCK_ARRAY = Uint8Array.from([1, 2, 3])

it('should return the zipfile when downloads are successful', async () => {
// Arrange
const decryptSpy = jest.spyOn(
AdminSubmissionService,
'downloadAndDecryptAttachment',
)
decryptSpy.mockResolvedValue(MOCK_ARRAY)
const MOCK_ZIP = new JSZip()
const zipPromises = Array.from(MOCK_DOWNLOAD_URLS).map(
async ([questionNum, { filename }]) => {
const fileName = `Question ${questionNum} - ${filename}`
return MOCK_ZIP.file(fileName, MOCK_ARRAY)
},
)
const expected = await Promise.all(zipPromises).then(() => {
return MOCK_ZIP.generateAsync({ type: 'blob' })
})

// Act
const actual =
await AdminSubmissionService.downloadAndDecryptAttachmentsAsZip(
MOCK_DOWNLOAD_URLS,
MOCK_SECRET_KEY,
)

// Assert
// Check that function is called with correct params for each element in the map
MOCK_DOWNLOAD_URLS.forEach(({ url }) => {
expect(decryptSpy).toHaveBeenCalledWith(url, MOCK_SECRET_KEY)
})
expect(actual).toEqual(expected)
})

it('should reject when any single download failed', async () => {
// Arrange
const decryptSpy = jest.spyOn(
AdminSubmissionService,
'downloadAndDecryptAttachment',
)
// NOTE: 2 urls but only 1 single rejection
decryptSpy.mockRejectedValueOnce('oh no i failed')

// Act
const actual = AdminSubmissionService.downloadAndDecryptAttachmentsAsZip(
MOCK_DOWNLOAD_URLS,
MOCK_SECRET_KEY,
)

// Assert
await expect(actual).rejects.toEqual('oh no i failed')
})
})
})