diff --git a/src/app/controllers/email-submissions.server.controller.js b/src/app/controllers/email-submissions.server.controller.js index df59150e23..8e61bafbdb 100644 --- a/src/app/controllers/email-submissions.server.controller.js +++ b/src/app/controllers/email-submissions.server.controller.js @@ -233,44 +233,45 @@ exports.receiveEmailSubmissionUsingBusBoy = function (req, res, next) { exports.validateEmailSubmission = function (req, res, next) { const { form } = req - if (req.body.responses) { - try { - req.body.parsedResponses = getProcessedResponses(form, req.body.responses) - delete req.body.responses // Prevent downstream functions from using responses by deleting it - } catch (err) { - logger.error({ - message: - err instanceof ConflictError - ? 'Conflict - Form has been updated' - : 'Error processing responses', - meta: { - action: 'validateEmailSubmission', - ...createReqMeta(req), - formId: req.form._id, - }, - error: err, + if (!req.body.responses) { + return res.sendStatus(StatusCodes.BAD_REQUEST) + } + + const getProcessedResponsesResult = getProcessedResponses( + form, + req.body.responses, + ) + + if (getProcessedResponsesResult.isErr()) { + const err = getProcessedResponsesResult.error + logger.error({ + message: + err instanceof ConflictError + ? 'Conflict - Form has been updated' + : 'Error processing responses', + meta: { + action: 'validateEmailSubmission', + ...createReqMeta(req), + formId: req.form._id, + }, + error: err, + }) + if (err instanceof ConflictError) { + return res.status(err.status).json({ + message: 'The form has been updated. Please refresh and submit again.', }) - if (err instanceof ConflictError) { - return res.status(err.status).json({ - message: - 'The form has been updated. Please refresh and submit again.', - }) - } else { - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', - }) - } } - - // Creates an array of attachments from the validated responses - req.attachments = mapAttachmentsFromParsedResponses( - req.body.parsedResponses, - ) - return next() - } else { - return res.sendStatus(StatusCodes.BAD_REQUEST) + return res.status(StatusCodes.BAD_REQUEST).json({ + message: + 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', + }) } + + req.body.parsedResponses = getProcessedResponsesResult.value + delete req.body.responses // Prevent downstream functions from using responses by deleting it + // Creates an array of attachments from the validated responses + req.attachments = mapAttachmentsFromParsedResponses(req.body.parsedResponses) + return next() } /** diff --git a/src/app/controllers/encrypt-submissions.server.controller.js b/src/app/controllers/encrypt-submissions.server.controller.js index 01be69d3d1..65c58d5f41 100644 --- a/src/app/controllers/encrypt-submissions.server.controller.js +++ b/src/app/controllers/encrypt-submissions.server.controller.js @@ -32,10 +32,9 @@ const HttpStatus = require('http-status-codes') */ exports.validateEncryptSubmission = function (req, res, next) { const { form } = req - try { - // Check if the encrypted content is base64 - checkIsEncryptedEncoding(req.body.encryptedContent) - } catch (error) { + + const isEncryptedResult = checkIsEncryptedEncoding(req.body.encryptedContent) + if (isEncryptedResult.isErr()) { logger.error({ message: 'Invalid encryption', meta: { @@ -43,43 +42,45 @@ exports.validateEncryptSubmission = function (req, res, next) { ...createReqMeta(req), formId: form._id, }, - error, + error: isEncryptedResult.error, }) return res .status(StatusCodes.BAD_REQUEST) .json({ message: 'Invalid data was found. Please submit again.' }) } - if (req.body.responses) { - try { - req.body.parsedResponses = getProcessedResponses(form, req.body.responses) - delete req.body.responses // Prevent downstream functions from using responses by deleting it - } catch (err) { - logger.error({ - message: 'Error processing responses', - meta: { - action: 'validateEncryptSubmission', - ...createReqMeta(req), - formId: form._id, - }, - error: err, + if (!req.body.responses) { + return res.sendStatus(StatusCodes.BAD_REQUEST) + } + + const getProcessedResponsesResult = getProcessedResponses( + form, + req.body.responses, + ) + if (getProcessedResponsesResult.isErr()) { + const err = getProcessedResponsesResult.error + logger.error({ + message: 'Error processing responses', + meta: { + action: 'validateEncryptSubmission', + ...createReqMeta(req), + formId: form._id, + }, + error: err, + }) + if (err instanceof ConflictError) { + return res.status(err.status).json({ + message: 'The form has been updated. Please refresh and submit again.', }) - if (err instanceof ConflictError) { - return res.status(err.status).json({ - message: - 'The form has been updated. Please refresh and submit again.', - }) - } else { - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', - }) - } } - return next() - } else { - return res.sendStatus(StatusCodes.BAD_REQUEST) + return res.status(StatusCodes.BAD_REQUEST).json({ + message: + 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', + }) } + req.body.parsedResponses = getProcessedResponsesResult.value + delete req.body.responses // Prevent downstream functions from using responses by deleting it + return next() } /** diff --git a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts index 2aa1e8da18..468d5d3371 100644 --- a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts +++ b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts @@ -32,6 +32,12 @@ import { import dbHandler from 'tests/unit/backend/helpers/jest-db' +import { + ConflictError, + ProcessingError, + ValidateFieldError, +} from '../../submission.errors' + const Form = getFormModel(mongoose) const Submission = getSubmissionModel(mongoose) @@ -126,7 +132,7 @@ describe('submission.service', () => { updatedResponses[emailFieldIndex] = newEmailResponse // Act - const actual = SubmissionService.getProcessedResponses( + const actualResult = SubmissionService.getProcessedResponses( defaultEncryptForm, updatedResponses, ) @@ -137,7 +143,8 @@ describe('submission.service', () => { { ...newMobileResponse, isVisible: true }, ] // Should only have email and mobile fields for encrypted forms. - expect(actual).toEqual(expectedParsed) + expect(actualResult.isOk()).toEqual(true) + expect(actualResult._unsafeUnwrap()).toEqual(expectedParsed) }) it('should return list of parsed responses for email form submission successfully', async () => { @@ -160,7 +167,7 @@ describe('submission.service', () => { updatedResponses[decimalFieldIndex] = newDecimalResponse // Act - const actual = SubmissionService.getProcessedResponses( + const actualResult = SubmissionService.getProcessedResponses( defaultEmailForm, updatedResponses, ) @@ -184,10 +191,11 @@ describe('submission.service', () => { expectedParsed.push(expectedProcessed) }) - expect(actual).toEqual(expectedParsed) + expect(actualResult.isOk()).toEqual(true) + expect(actualResult._unsafeUnwrap()).toEqual(expectedParsed) }) - it('should throw error when email form has more fields than responses', async () => { + it('should return error when email form has more fields than responses', async () => { // Arrange const extraFieldForm = cloneDeep(defaultEmailForm) const secondMobileField = cloneDeep( @@ -197,15 +205,19 @@ describe('submission.service', () => { extraFieldForm.form_fields!.push(secondMobileField) // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - extraFieldForm, - defaultEmailResponses, - ), - ).toThrowError('Some form fields are missing') + + const actualResult = SubmissionService.getProcessedResponses( + extraFieldForm, + defaultEmailResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ConflictError('Some form fields are missing'), + ) }) - it('should throw error when encrypt form has more fields than responses', async () => { + it('should return error when encrypt form has more fields than responses', async () => { // Arrange const extraFieldForm = cloneDeep(defaultEncryptForm) const secondMobileField = cloneDeep( @@ -215,15 +227,19 @@ describe('submission.service', () => { extraFieldForm.form_fields!.push(secondMobileField) // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - extraFieldForm, - defaultEncryptResponses, - ), - ).toThrowError('Some form fields are missing') + + const actualResult = SubmissionService.getProcessedResponses( + extraFieldForm, + defaultEncryptResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ConflictError('Some form fields are missing'), + ) }) - it('should throw error when any responses are not valid for encrypted form submission', async () => { + it('should return error when any responses are not valid for encrypted form submission', async () => { // Arrange // Only mobile and email fields are parsed, since the other fields are // e2e encrypted from the browser. @@ -233,15 +249,19 @@ describe('submission.service', () => { requireMobileEncryptForm.form_fields![mobileFieldIndex].required = true // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - requireMobileEncryptForm, - defaultEncryptResponses, - ), - ).toThrowError('Invalid answer submitted') + + const actualResult = SubmissionService.getProcessedResponses( + requireMobileEncryptForm, + defaultEncryptResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) - it('should throw error when any responses are not valid for email form submission', async () => { + it('should return error when any responses are not valid for email form submission', async () => { // Arrange // Set NRIC field in form as required. const nricFieldIndex = TYPE_TO_INDEX_MAP[BasicField.Nric] @@ -249,15 +269,19 @@ describe('submission.service', () => { requireNricEmailForm.form_fields![nricFieldIndex].required = true // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - requireNricEmailForm, - defaultEmailResponses, - ), - ).toThrowError('Invalid answer submitted') + + const actualResult = SubmissionService.getProcessedResponses( + requireNricEmailForm, + defaultEmailResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) - it('should throw error when encrypted form submission is prevented by logic', async () => { + it('should return error when encrypted form submission is prevented by logic', async () => { // Arrange // Mock logic util to return non-empty to check if error is thrown jest @@ -270,15 +294,19 @@ describe('submission.service', () => { } as unknown) as IPreventSubmitLogicSchema) // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - defaultEncryptForm, - defaultEncryptResponses, - ), - ).toThrowError('Submission prevented by form logic') + + const actualResult = SubmissionService.getProcessedResponses( + defaultEncryptForm, + defaultEncryptResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ProcessingError('Submission prevented by form logic'), + ) }) - it('should throw error when email form submission is prevented by logic', async () => { + it('should return error when email form submission is prevented by logic', async () => { // Arrange // Mock logic util to return non-empty to check if error is thrown. const mockReturnLogicUnit = ({ @@ -293,12 +321,16 @@ describe('submission.service', () => { .mockReturnValueOnce(mockReturnLogicUnit) // Act + Assert - expect(() => - SubmissionService.getProcessedResponses( - defaultEmailForm, - defaultEmailResponses, - ), - ).toThrowError('Submission prevented by form logic') + + const actualResult = SubmissionService.getProcessedResponses( + defaultEmailForm, + defaultEmailResponses, + ) + + expect(actualResult.isErr()).toEqual(true) + expect(actualResult._unsafeUnwrapErr()).toEqual( + new ProcessingError('Submission prevented by form logic'), + ) }) }) diff --git a/src/app/modules/submission/submission.errors.ts b/src/app/modules/submission/submission.errors.ts index 8ffd374afa..5ff7c79636 100644 --- a/src/app/modules/submission/submission.errors.ts +++ b/src/app/modules/submission/submission.errors.ts @@ -17,3 +17,30 @@ export class SubmissionNotFoundError extends ApplicationError { super(message) } } + +/** + * A custom error class returned when given submission has invalid encryption encoding + */ +export class InvalidEncodingError extends ApplicationError { + constructor(message = 'Error with encoding.') { + super(message) + } +} + +/** + * A custom error class returned when given submission has response that cannot be processed + */ +export class ProcessingError extends ApplicationError { + constructor(message = 'Error processing response.') { + super(message) + } +} + +/** + * A custom error class returned when given submission has field validation failure + */ +export class ValidateFieldError extends ApplicationError { + constructor(message = 'Error validating field.') { + super(message) + } +} diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts index b39326786c..259dcb25d0 100644 --- a/src/app/modules/submission/submission.service.ts +++ b/src/app/modules/submission/submission.service.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import mongoose from 'mongoose' -import { errAsync, ResultAsync } from 'neverthrow' +import { err, errAsync, ok, Result, ResultAsync } from 'neverthrow' import { createLoggerWithLabel } from '../../../config/logger' import { @@ -13,7 +13,11 @@ import { createQueryWithDateParam, isMalformedDate } from '../../utils/date' import { validateField } from '../../utils/field-validation' import { DatabaseError, MalformedParametersError } from '../core/core.errors' -import { ConflictError } from './submission.errors' +import { + ConflictError, + ProcessingError, + ValidateFieldError, +} from './submission.errors' import { ProcessedFieldResponse } from './submission.types' import { getModeFilter } from './submission.utils' @@ -26,18 +30,20 @@ const SubmissionModel = getSubmissionModel(mongoose) * * @param form The form document * @param responses the responses that corresponds to the given form - * @returns filtered list of allowed responses with duplicates (if any) removed - * @throws ConflictError if the given form's form field ids count do not match given responses' + * @returns neverthrow ok() filtered list of allowed responses with duplicates (if any) removed + * @returns neverthrow err(ConflictError) if the given form's form field ids count do not match given responses' */ const getFilteredResponses = ( form: IFormSchema, responses: FieldResponse[], -) => { +): Result => { const modeFilter = getModeFilter(form.responseMode) + if (!form.form_fields) { + return err(new ConflictError('Form fields are missing')) + } // _id must be transformed to string as form response is jsonified. - // TODO (#317): remove usage of non-null assertion - const fieldIds = modeFilter(form.form_fields!).map((field) => ({ + const fieldIds = modeFilter(form.form_fields).map((field) => ({ _id: String(field._id), })) const uniqueResponses = _.uniqBy(modeFilter(responses), '_id') @@ -47,11 +53,14 @@ const getFilteredResponses = ( const onlyInForm = _.differenceBy(fieldIds, results, '_id').map( ({ _id }) => _id, ) - throw new ConflictError( - `formId="${form._id}" message="Some form fields are missing" onlyInForm="${onlyInForm}"`, + return err( + new ConflictError( + 'Some form fields are missing', + `formId="${form._id}" onlyInForm="${onlyInForm}"`, + ), ) } - return results + return ok(results) } /** @@ -60,14 +69,22 @@ const getFilteredResponses = ( * verified fields are also performed on the response. * @param form The form document corresponding to the responses * @param responses The responses to process and validate - * @returns field responses with additional metadata injected. - * @throws Error if response validation fails + * @returns neverthrow ok() with field responses with additional metadata injected. + * @returns neverthrow err() if response validation fails */ export const getProcessedResponses = ( form: IFormSchema, originalResponses: FieldResponse[], -): ProcessedFieldResponse[] => { - const filteredResponses = getFilteredResponses(form, originalResponses) +): Result< + ProcessedFieldResponse[], + ProcessingError | ConflictError | ValidateFieldError +> => { + const filteredResponsesResult = getFilteredResponses(form, originalResponses) + if (filteredResponsesResult.isErr()) { + return err(filteredResponsesResult.error) + } + + const filteredResponses = filteredResponsesResult.value // Set of all visible fields const visibleFieldIds = getVisibleFieldIds(filteredResponses, form) @@ -75,12 +92,16 @@ export const getProcessedResponses = ( // Guard against invalid form submissions that should have been prevented by // logic. if (getLogicUnitPreventingSubmit(filteredResponses, form, visibleFieldIds)) { - throw new Error('Submission prevented by form logic') + return err(new ProcessingError('Submission prevented by form logic')) } // Create a map keyed by field._id for easier access - // TODO (#317): remove usage of non-null assertion - const fieldMap = form.form_fields!.reduce<{ + + if (!form.form_fields) { + return err(new ProcessingError('Form fields are undefined')) + } + + const fieldMap = form.form_fields.reduce<{ [fieldId: string]: IFieldSchema }>((acc, field) => { acc[field._id] = field @@ -88,11 +109,14 @@ export const getProcessedResponses = ( }, {}) // Validate each field in the form and inject metadata into the responses. - const processedResponses = filteredResponses.map((response) => { + const processedResponses = [] + for (const response of filteredResponses) { const responseId = response._id const formField = fieldMap[responseId] if (!formField) { - throw new Error('Response ID does not match form field IDs') + return err( + new ProcessingError('Response ID does not match form field IDs'), + ) } const processingResponse: ProcessedFieldResponse = { @@ -105,12 +129,19 @@ export const getProcessedResponses = ( processingResponse.isUserVerified = formField.isVerifiable } - // Error will be thrown if the processed response is not valid. - validateField(form._id, formField, processingResponse) - return processingResponse - }) + // Error will be returned if the processed response is not valid. + const validateFieldResult = validateField( + form._id, + formField, + processingResponse, + ) + if (validateFieldResult.isErr()) { + return err(validateFieldResult.error) + } + processedResponses.push(processingResponse) + } - return processedResponses + return ok(processedResponses) } /** diff --git a/src/app/utils/encryption.ts b/src/app/utils/encryption.ts index e0eff43798..8256a11ce7 100644 --- a/src/app/utils/encryption.ts +++ b/src/app/utils/encryption.ts @@ -1,23 +1,35 @@ import { decode as decodeBase64 } from '@stablelib/base64' +import { err, ok, Result } from 'neverthrow' -export const checkIsEncryptedEncoding = (encryptedStr: string): boolean => { +import { InvalidEncodingError } from '../modules/submission/submission.errors' + +export const checkIsEncryptedEncoding = ( + encryptedStr: string, +): Result => { // TODO (#42): Remove this type check once whole backend is in TypeScript. if (typeof encryptedStr !== 'string') { - throw new Error('encryptedStr is not of type `string`') + return err(new InvalidEncodingError('encryptedStr is not of type `string`')) } const [submissionPublicKey, nonceEncrypted] = encryptedStr.split(';') + + if (!nonceEncrypted) { + return err(new InvalidEncodingError('Missing data')) + } const [nonce, encrypted] = nonceEncrypted.split(':') + if (!submissionPublicKey || !nonce || !encrypted) { - throw new Error('Missing data') + return err(new InvalidEncodingError('Missing data')) } try { + // decode throws error if incorrect characters for decoding + // see https://github.com/StableLib/stablelib/blob/2f3b21e8fcee4aaa77872282fd4ac7a7ff1633f5/packages/base64/base64.ts#L111 decodeBase64(submissionPublicKey) decodeBase64(nonce) decodeBase64(encrypted) - return true - } catch (err) { - return false + return ok(true) + } catch (e) { + return err(new InvalidEncodingError('Incorrect characters')) } } diff --git a/src/app/utils/field-validation/index.ts b/src/app/utils/field-validation/index.ts index 390af82456..9775f25d4d 100644 --- a/src/app/utils/field-validation/index.ts +++ b/src/app/utils/field-validation/index.ts @@ -1,4 +1,5 @@ import { Either, isLeft, left, right } from 'fp-ts/lib/Either' +import { err, ok, Result } from 'neverthrow' import { ProcessedFieldResponse, @@ -9,6 +10,7 @@ import { IField } from '../../../types/field/baseField' import { BasicField } from '../../../types/field/fieldTypes' import { FieldResponse } from '../../../types/response' import { isProcessedSingleAnswerResponse } from '../../../types/response/guards' +import { ValidateFieldError } from '../../modules/submission/submission.errors' import { ALLOWED_VALIDATORS, FIELDS_TO_REJECT } from './config' import fieldValidatorFactory from './FieldValidatorFactory.class' // Deprecated @@ -87,15 +89,17 @@ export const validateField = ( formId: string, formField: IField, response: FieldResponse, -): void => { +): Result => { if (!isValidResponseFieldType(response)) { - throw new Error(`Rejected field type "${response.fieldType}"`) + return err( + new ValidateFieldError(`Rejected field type "${response.fieldType}"`), + ) } const fieldTypeEither = doFieldTypesMatch(formField, response) if (isLeft(fieldTypeEither)) { - throw new Error(fieldTypeEither.left) + return err(new ValidateFieldError(fieldTypeEither.left)) } if (isProcessedSingleAnswerResponse(response)) { @@ -110,20 +114,21 @@ export const validateField = ( const validEither = validator(response) if (isLeft(validEither)) { logInvalidAnswer(formId, formField, validEither.left) - throw new Error('Invalid answer submitted') + return err(new ValidateFieldError('Invalid answer submitted')) } - return + return ok(true) } // Fallback for un-migrated single answer validators default: { - classBasedValidation(formId, formField, response) + return classBasedValidation(formId, formField, response) } } } } else { // fallback for processed checkbox/table/attachment responses - classBasedValidation(formId, formField, response) + return classBasedValidation(formId, formField, response) } + return ok(true) } /** @@ -135,7 +140,7 @@ const classBasedValidation = ( formId: string, formField: IField, response: FieldResponse, -) => { +): Result => { const fieldValidator = fieldValidatorFactory.createFieldValidator( formId, formField, @@ -146,7 +151,8 @@ const classBasedValidation = ( // TODO: Remove after soft launch of validation. Should throw Error for all validators // fieldValidator.constructor.name only returns the name of the class if code is not minified! if (ALLOWED_VALIDATORS.includes(fieldValidator.constructor.name)) { - throw new Error('Invalid answer submitted') + return err(new ValidateFieldError('Invalid answer submitted')) } } + return ok(true) } diff --git a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js index 60f54265e7..895af7562e 100644 --- a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js +++ b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js @@ -293,7 +293,7 @@ describe('Encrypt Submissions Controller', () => { // Cannot get the exact verified string since it changes everytime. // Just be content that a string of the correct encoding was // returned - expect(checkIsEncryptedEncoding(res.body.verified)).toBe(true) + expect(checkIsEncryptedEncoding(res.body.verified).value).toBe(true) return done() }) }) @@ -332,7 +332,7 @@ describe('Encrypt Submissions Controller', () => { // Cannot get the exact verified string since it changes everytime. // Just be content that a string of the correct encoding was // returned - expect(checkIsEncryptedEncoding(res.body.verified)).toBe(true) + expect(checkIsEncryptedEncoding(res.body.verified).value).toBe(true) return done() }) }) diff --git a/tests/unit/backend/utils/field-validation/attachment-validation.spec.js b/tests/unit/backend/utils/field-validation/attachment-validation.spec.js index 8cf729f3ea..0bbc601943 100644 --- a/tests/unit/backend/utils/field-validation/attachment-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/attachment-validation.spec.js @@ -1,3 +1,6 @@ +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') @@ -34,36 +37,47 @@ describe('Attachment validation', () => { it('should disallow submission with no attachment if it is required', () => { const formField = makeField(fieldId, '1') const response = makeResponse(fieldId, undefined) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow submission with no attachment if it is not required', () => { const formField = makeField(fieldId, '1', { required: false }) const response = makeResponse(fieldId, undefined, { answer: '' }) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow submission with no answer if it is required', () => { const formField = makeField(fieldId, '1') const response = makeResponse(fieldId, Buffer.alloc(1), { answer: '' }) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow submission with no answer if it is not required', () => { const formField = makeField(fieldId, '1', { required: false }) const response = makeResponse(fieldId, Buffer.alloc(1), { answer: '' }) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow when it is not required but with answer and no attachment', () => { const formField = makeField(fieldId, '1', { required: false }) const response = makeResponse(fieldId, undefined) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) @@ -71,22 +85,27 @@ describe('Attachment validation', () => { it('should allow attachment with valid size', () => { const formField = makeField(fieldId, '1') const response = makeResponse(fieldId, Buffer.alloc(1)) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow attachment that exceeds size', () => { const formField = makeField(fieldId, '1') const response = makeResponse(fieldId, Buffer.alloc(2000000)) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should respect the attachmentSize from formField', () => { const formField = makeField(fieldId, '3') const response = makeResponse(fieldId, Buffer.alloc(2000000)) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) }) diff --git a/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js b/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js index 5abd64cb1b..8cbe784641 100644 --- a/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js @@ -2,6 +2,10 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') + describe('Checkbox validation', () => { const makeCheckboxField = (fieldId, fieldOptions, options) => { const checkbox = { @@ -36,8 +40,11 @@ describe('Checkbox validation', () => { const fieldOptions = ['a', 'b', 'c'] const formField = makeCheckboxField(fieldId, fieldOptions) const response = makeCheckboxResponse(fieldId, []) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty submission if checkbox is optional', () => { const fieldOptions = ['a', 'b', 'c'] @@ -45,8 +52,9 @@ describe('Checkbox validation', () => { required: false, }) const response = makeCheckboxResponse(fieldId, []) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) @@ -55,29 +63,37 @@ describe('Checkbox validation', () => { const fieldOptions = ['a', 'b', 'c'] const formField = makeCheckboxField(fieldId, fieldOptions) const response = makeCheckboxResponse(fieldId, ['a']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow multiple valid options to be selected', () => { const fieldOptions = ['a', 'b', 'c'] const formField = makeCheckboxField(fieldId, fieldOptions) const response = makeCheckboxResponse(fieldId, ['a', 'b']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow answers not in fieldOptions', () => { const fieldOptions = ['a', 'b', 'c'] const formField = makeCheckboxField(fieldId, fieldOptions) const response = makeCheckboxResponse(fieldId, ['a', 'notinoption']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow duplicate answers', () => { const fieldOptions = ['a', 'b', 'c'] const formField = makeCheckboxField(fieldId, fieldOptions) const response = makeCheckboxResponse(fieldId, ['a', 'b', 'a']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow self-configured others options in field options', () => { // This occurs when admins create their own checkboxes with options like ["Others: "] @@ -86,8 +102,9 @@ describe('Checkbox validation', () => { const response = makeCheckboxResponse(fieldId, [ 'Others: ', ]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow Others option to be submitted if field is configured for Others', () => { const fieldOptions = ['a', 'b', 'c'] @@ -95,8 +112,9 @@ describe('Checkbox validation', () => { othersRadioButton: true, }) const response = makeCheckboxResponse(fieldId, ['a', 'Others: xyz']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow Others option to be submitted if field is not configured for Others', () => { const fieldOptions = ['a', 'b', 'c'] @@ -104,8 +122,11 @@ describe('Checkbox validation', () => { othersRadioButton: false, }) const response = makeCheckboxResponse(fieldId, ['a', 'Others: xyz']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow Others option to be submitted with blank answer if field is configured for Others', () => { const fieldOptions = ['a', 'b', 'c'] @@ -113,8 +134,11 @@ describe('Checkbox validation', () => { othersRadioButton: true, }) const response = makeCheckboxResponse(fieldId, ['a', 'Others: ']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) @@ -126,8 +150,11 @@ describe('Checkbox validation', () => { ValidationOptions: { customMax: 2, customMin: null }, }) const response = makeCheckboxResponse(fieldId, ['c', 'd', 'e']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer answers than customMin if selection limits are configured', () => { @@ -137,8 +164,11 @@ describe('Checkbox validation', () => { ValidationOptions: { customMax: null, customMin: 2 }, }) const response = makeCheckboxResponse(fieldId, ['c']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow more answers than customMax if selection limits are not configured', () => { @@ -148,8 +178,9 @@ describe('Checkbox validation', () => { ValidationOptions: { customMax: 2, customMin: null }, }) const response = makeCheckboxResponse(fieldId, ['c', 'd', 'e']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow fewer answers than customMin if selection limits are not configured', () => { @@ -159,8 +190,9 @@ describe('Checkbox validation', () => { ValidationOptions: { customMax: null, customMin: 2 }, }) const response = makeCheckboxResponse(fieldId, ['c']) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow more answers than customMax, and fewer answers than customMin, if selection limits are configured', () => { @@ -171,9 +203,9 @@ describe('Checkbox validation', () => { }) const validResponse = makeCheckboxResponse(fieldId, ['c', 'd', 'e']) - const validTestFunc = () => - validateField(formId, formField, validResponse) - expect(validTestFunc).not.toThrow() + const validateResult = validateField(formId, formField, validResponse) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) const moreAnswers = makeCheckboxResponse(fieldId, [ 'c', @@ -182,14 +214,28 @@ describe('Checkbox validation', () => { 'a', 'b', ]) - const moreAnswersTestFunc = () => - validateField(formId, formField, moreAnswers) - expect(moreAnswersTestFunc).toThrow() + + const validateMoreAnswersResult = validateField( + formId, + formField, + moreAnswers, + ) + expect(validateMoreAnswersResult.isErr()).toBe(true) + expect(validateMoreAnswersResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) const fewerAnswers = makeCheckboxResponse(fieldId, ['c']) - const fewerAnswersTestFunc = () => - validateField(formId, formField, fewerAnswers) - expect(fewerAnswersTestFunc).toThrow() + + const validateFewerAnswersResult = validateField( + formId, + formField, + fewerAnswers, + ) + expect(validateFewerAnswersResult.isErr()).toBe(true) + expect(validateFewerAnswersResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) }) diff --git a/tests/unit/backend/utils/field-validation/date-validation.spec.js b/tests/unit/backend/utils/field-validation/date-validation.spec.js index 0bb0db7968..cb626545f9 100644 --- a/tests/unit/backend/utils/field-validation/date-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/date-validation.spec.js @@ -2,6 +2,10 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') + describe('Date field validation', () => { it('should allow valid date
', () => { const formField = { @@ -15,8 +19,9 @@ describe('Date field validation', () => { isVisible: true, answer: '09 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty string when not required', () => { @@ -31,8 +36,9 @@ describe('Date field validation', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid leap year date', () => { @@ -47,8 +53,9 @@ describe('Date field validation', () => { isVisible: true, answer: '29 Feb 2016', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow 00 date', () => { @@ -63,8 +70,11 @@ describe('Date field validation', () => { isVisible: true, answer: '00 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow date less than 2 char', () => { @@ -79,8 +89,11 @@ describe('Date field validation', () => { isVisible: true, answer: '9 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow date more than 2 char', () => { @@ -95,8 +108,11 @@ describe('Date field validation', () => { isVisible: true, answer: '009 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow date not in month', () => { @@ -111,8 +127,11 @@ describe('Date field validation', () => { isVisible: true, answer: '32 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid month', () => { @@ -127,8 +146,11 @@ describe('Date field validation', () => { isVisible: true, answer: '31 Jon 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow month less then 3 chars', () => { @@ -143,8 +165,11 @@ describe('Date field validation', () => { isVisible: true, answer: '16 Jn 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow month more then 3 chars', () => { @@ -159,8 +184,11 @@ describe('Date field validation', () => { isVisible: true, answer: '03 June 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow text year', () => { @@ -175,8 +203,11 @@ describe('Date field validation', () => { isVisible: true, answer: '31 Jan hello', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow year less than 4 chars', () => { @@ -191,8 +222,11 @@ describe('Date field validation', () => { isVisible: true, answer: '31 Jan 201', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow year more than 4 chars', () => { @@ -207,8 +241,11 @@ describe('Date field validation', () => { isVisible: true, answer: '31 Jan 02019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow empty string when required', () => { @@ -223,8 +260,11 @@ describe('Date field validation', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid leap year date', () => { @@ -239,8 +279,11 @@ describe('Date field validation', () => { isVisible: true, answer: '29 Feb 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow past dates for normal date fields', () => { @@ -259,8 +302,9 @@ describe('Date field validation', () => { answer: '09 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) jasmine.clock().uninstall() }) @@ -281,8 +325,9 @@ describe('Date field validation', () => { answer: '01 Jan 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) jasmine.clock().uninstall() }) @@ -304,8 +349,11 @@ describe('Date field validation', () => { isVisible: true, answer: '29 Feb 2019', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) jasmine.clock().uninstall() }) @@ -326,8 +374,9 @@ describe('Date field validation', () => { answer: '01 Jan 2021', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) jasmine.clock().uninstall() }) @@ -349,8 +398,11 @@ describe('Date field validation', () => { isVisible: true, answer: '01 Jan 2021', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) jasmine.clock().uninstall() }) @@ -375,8 +427,9 @@ describe('Date field validation', () => { isVisible: true, answer: '25 Jun 2020', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) jasmine.clock().uninstall() }) @@ -401,8 +454,11 @@ describe('Date field validation', () => { isVisible: true, answer: '22 Jun 2020', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) jasmine.clock().uninstall() }) diff --git a/tests/unit/backend/utils/field-validation/decimal-validation.spec.js b/tests/unit/backend/utils/field-validation/decimal-validation.spec.js index a1b0a8074b..2eddb758be 100644 --- a/tests/unit/backend/utils/field-validation/decimal-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/decimal-validation.spec.js @@ -2,6 +2,10 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') + describe('Decimal Validation', () => { it('should allow decimal with valid maximum', () => { const formField = { @@ -19,8 +23,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '4', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow decimal with valid maximum (inclusive)', () => { @@ -39,8 +44,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow decimal with invalid maximum', () => { @@ -59,8 +65,11 @@ describe('Decimal Validation', () => { isVisible: true, answer: '6', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow decimal with valid minimum', () => { @@ -79,8 +88,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow decimal with valid minimum (inclusive)', () => { @@ -99,8 +109,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '2', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow decimal with invalid minimum', () => { @@ -119,8 +130,11 @@ describe('Decimal Validation', () => { isVisible: true, answer: '1', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow decimal with no custom validation', () => { @@ -139,8 +153,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty answer with optional field', () => { @@ -159,8 +174,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow answer to be zero', () => { @@ -179,8 +195,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '0', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow negative answers', () => { @@ -199,8 +216,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '-5.0', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow leading zeroes', () => { @@ -220,8 +238,11 @@ describe('Decimal Validation', () => { answer: '001.3', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow decimal points with no leading numbers', () => { @@ -241,8 +262,11 @@ describe('Decimal Validation', () => { answer: '.3', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow negative answers with no leading number', () => { @@ -262,8 +286,11 @@ describe('Decimal Validation', () => { answer: '-.3', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow floats (<16 decimal places) that are out of range (min)', () => { @@ -283,8 +310,11 @@ describe('Decimal Validation', () => { answer: '1.999999999999999', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow floats (<16 decimal places) that are out of range (max)', () => { @@ -304,8 +334,11 @@ describe('Decimal Validation', () => { answer: '2.000000000000001', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow validationOption to be undefined', () => { @@ -320,8 +353,9 @@ describe('Decimal Validation', () => { isVisible: true, answer: '1.0', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow floats less than 0 when customMin is 0', () => { @@ -340,8 +374,11 @@ describe('Decimal Validation', () => { isVisible: true, answer: '-0.2', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow floats more than 0 when customMax is 0', () => { const formField = { @@ -359,7 +396,10 @@ describe('Decimal Validation', () => { isVisible: true, answer: '0.1', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js b/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js index 909f4f9022..6f9bb578bb 100644 --- a/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js @@ -2,6 +2,10 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') + describe('Dropdown validation', () => { it('should allow valid option', () => { const formField = { @@ -14,8 +18,9 @@ describe('Dropdown validation', () => { fieldType: 'dropdown', answer: 'KISS', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow invalid option', () => { @@ -29,8 +34,11 @@ describe('Dropdown validation', () => { fieldType: 'dropdown', answer: 'invalid', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow empty answer when required', () => { @@ -46,8 +54,11 @@ describe('Dropdown validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty answer when not required', () => { @@ -62,8 +73,9 @@ describe('Dropdown validation', () => { fieldType: 'dropdown', answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty answer when it is required but not visible', () => { @@ -79,8 +91,9 @@ describe('Dropdown validation', () => { answer: '', isVisible: false, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow empty answer when it is required and visible', () => { @@ -96,8 +109,11 @@ describe('Dropdown validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow multiple answers', () => { @@ -111,7 +127,10 @@ describe('Dropdown validation', () => { fieldType: 'dropdown', answer: ['KISS', 'DRY'], } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/email-validation.spec.ts b/tests/unit/backend/utils/field-validation/email-validation.spec.ts index 498e435077..843e3beb61 100644 --- a/tests/unit/backend/utils/field-validation/email-validation.spec.ts +++ b/tests/unit/backend/utils/field-validation/email-validation.spec.ts @@ -3,6 +3,8 @@ import EmailValidator from 'src/app/utils/field-validation/validators/EmailValid import { BasicField } from 'src/types/field/fieldTypes' import { ISingleAnswerResponse } from 'src/types/response' +import { ValidateFieldError } from '../../../../../dist/backend/app/modules/submission/submission.errors' + describe('Email field validation', () => { beforeEach(() => { jest @@ -26,8 +28,9 @@ describe('Email field validation', () => { question: 'random', answer: 'valid@email.com', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow invalid emails', () => { @@ -46,8 +49,11 @@ describe('Email field validation', () => { question: 'random', answer: 'invalidemail.com', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty answer for required logic field that is not visible', () => { @@ -67,8 +73,9 @@ describe('Email field validation', () => { isVisible: false, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow email addresses whose email domain belongs to allowedEmailDomains when isVerifiable is true, hasAllowedEmailDomains is true and allowedEmailDomains is not empty', () => { @@ -91,8 +98,9 @@ describe('Email field validation', () => { isVisible: true, answer: 'volunteer-testing@test.gov.sg', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should not allow email addresses whose email domain does not belong to allowedEmailDomains when isVerifiable is true, hasAllowedEmailDomains is true and allowedEmailDomains is not empty', () => { @@ -115,8 +123,11 @@ describe('Email field validation', () => { isVisible: true, answer: 'volunteer-testing@test.gov.sg', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow any valid email address when isVerifiable is true, hasAllowedEmailDomains is true but allowedEmailDomains is empty', () => { @@ -139,8 +150,9 @@ describe('Email field validation', () => { isVisible: true, answer: 'volunteer-testing@test.gov.sg', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow any valid email address when isVerifiable is true and hasAllowedEmailDomains is false, regardless of the cardinality of allowedEmailDomains', () => { @@ -163,8 +175,9 @@ describe('Email field validation', () => { isVisible: true, answer: 'volunteer-testing@test.gov.sg', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow any valid email address when isVerifiable is false and hasAllowedEmailDomains is true, regardless of the cardinality of allowedEmailDomains', () => { @@ -187,7 +200,8 @@ describe('Email field validation', () => { isVisible: true, answer: 'volunteer-testing@test.gov.sg', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) diff --git a/tests/unit/backend/utils/field-validation/nric-validation.spec.js b/tests/unit/backend/utils/field-validation/nric-validation.spec.js index 8df817796c..62591cc85f 100644 --- a/tests/unit/backend/utils/field-validation/nric-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/nric-validation.spec.js @@ -2,6 +2,10 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') + describe('NRIC field validation', () => { it('should allow valid NRIC with S prefix', () => { const formField = { @@ -15,8 +19,9 @@ describe('NRIC field validation', () => { answer: 'S9912345A', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid NRIC with T prefix', () => { @@ -31,8 +36,9 @@ describe('NRIC field validation', () => { answer: 'T1394524H', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid NRIC with F prefix', () => { @@ -47,8 +53,9 @@ describe('NRIC field validation', () => { answer: 'F0477844T', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid NRIC with G prefix', () => { @@ -63,8 +70,9 @@ describe('NRIC field validation', () => { answer: 'G9592927W', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow invalid NRIC with S prefix', () => { @@ -79,8 +87,11 @@ describe('NRIC field validation', () => { answer: 'S9912345B', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid NRIC with T prefix', () => { @@ -95,8 +106,11 @@ describe('NRIC field validation', () => { answer: 'T1394524I', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid NRIC with F prefix', () => { @@ -111,8 +125,11 @@ describe('NRIC field validation', () => { answer: 'F0477844U', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid NRIC with G prefix', () => { @@ -127,8 +144,11 @@ describe('NRIC field validation', () => { answer: 'G9592927X', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty string for optional NRIC', () => { @@ -143,8 +163,9 @@ describe('NRIC field validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow empty string for required NRIC', () => { @@ -159,7 +180,10 @@ describe('NRIC field validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/number-validation.spec.js b/tests/unit/backend/utils/field-validation/number-validation.spec.js index 5454fabea4..5d42e5f611 100644 --- a/tests/unit/backend/utils/field-validation/number-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/number-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Number field validation', () => { it('should allow number with valid maximum', () => { const formField = { @@ -21,8 +23,9 @@ describe('Number field validation', () => { isVisible: false, answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with valid maximum (inclusive)', () => { @@ -43,8 +46,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow number with invalid maximum', () => { @@ -65,8 +69,11 @@ describe('Number field validation', () => { isVisible: false, answer: '555', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow number with valid minimum', () => { @@ -87,8 +94,9 @@ describe('Number field validation', () => { isVisible: false, answer: '555', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with valid minimum (inclusive)', () => { @@ -109,8 +117,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with valid exact', () => { @@ -131,8 +140,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow number with invalid exact', () => { @@ -153,8 +163,11 @@ describe('Number field validation', () => { isVisible: false, answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow number with maximum left undefined', () => { @@ -175,8 +188,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with minimum left undefined', () => { @@ -197,8 +211,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with exact undefined', () => { @@ -219,8 +234,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with no custom validation', () => { @@ -241,8 +257,9 @@ describe('Number field validation', () => { isVisible: false, answer: '55', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with optional answer', () => { @@ -263,8 +280,9 @@ describe('Number field validation', () => { isVisible: false, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow answer to be zero', () => { @@ -285,8 +303,9 @@ describe('Number field validation', () => { isVisible: false, answer: '0', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow negative answers', () => { @@ -307,8 +326,11 @@ describe('Number field validation', () => { isVisible: false, answer: '-5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow leading zeroes in answer', () => { @@ -329,7 +351,8 @@ describe('Number field validation', () => { isVisible: false, answer: '05', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) diff --git a/tests/unit/backend/utils/field-validation/phone-num-validation.spec.js b/tests/unit/backend/utils/field-validation/phone-num-validation.spec.js index 05f6296650..f82171f82b 100644 --- a/tests/unit/backend/utils/field-validation/phone-num-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/phone-num-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Mobile validation tests', () => { it('should allow empty answer for required logic field that is not visible', () => { const formField = { @@ -16,8 +18,9 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty answer for optional field', () => { @@ -33,8 +36,9 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should not allow empty answer for required field', () => { @@ -50,8 +54,11 @@ describe('Mobile validation tests', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow valid mobile numbers for mobile fieldType', () => { @@ -67,8 +74,9 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+6598765432', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid home numbers for homeno fieldType', () => { @@ -83,8 +91,9 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+6565656565', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow mobile numbers without "+" prefix', () => { @@ -100,8 +109,11 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '6598765432', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow home numbers on mobile fieldType', () => { @@ -117,8 +129,11 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+6565656565', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow mobile numbers on homeno fieldType', () => { @@ -134,8 +149,11 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+6598765432', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow international numbers when field does not allow for it', () => { @@ -151,8 +169,11 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+447851315617', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow international numbers when field allows for it', () => { @@ -168,7 +189,8 @@ describe('Mobile validation tests', () => { isVisible: false, answer: '+447851315617', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) diff --git a/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js b/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js index a6684c243e..d1e36da508 100644 --- a/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Radio button validation', () => { it('should allow valid option', () => { const formField = { @@ -14,8 +16,9 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: 'a', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow invalid option', () => { @@ -29,8 +32,11 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: 'invalid', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow empty option when it is required', () => { @@ -46,8 +52,11 @@ describe('Radio button validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty option when not required', () => { @@ -61,8 +70,9 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty option when required and that logic field is not visible', () => { @@ -78,8 +88,9 @@ describe('Radio button validation', () => { answer: '', isVisible: false, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow empty option when required and that it is visible', () => { @@ -95,8 +106,11 @@ describe('Radio button validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty option when not required and that it is visible', () => { @@ -112,8 +126,9 @@ describe('Radio button validation', () => { answer: '', isVisible: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it(`should allow answer that starts with 'Others: ' when others option is selected`, () => { @@ -128,8 +143,9 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: 'Others: hi i am others', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it(`should disallow answer that starts with 'Others: ' when others option is not selected`, () => { @@ -144,8 +160,11 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: 'Others: hi i am others', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it(`should disallow empty answer when others option is selected`, () => { @@ -161,7 +180,10 @@ describe('Radio button validation', () => { fieldType: 'radiobutton', answer: 'Others: ', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/rating-validation.spec.js b/tests/unit/backend/utils/field-validation/rating-validation.spec.js index 3a6e558a86..2021b6b4e3 100644 --- a/tests/unit/backend/utils/field-validation/rating-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/rating-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Rating field validation', () => { it('should allow answer within range', () => { const formField = { @@ -18,8 +20,9 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '4', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with valid maximum (inclusive)', () => { @@ -37,8 +40,9 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow number with invalid maximum', () => { @@ -56,8 +60,11 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '6', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow number with valid minimum (inclusive)', () => { @@ -75,8 +82,9 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '1', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow number with optional answer', () => { @@ -94,8 +102,9 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '5', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow negative answers', () => { @@ -113,8 +122,11 @@ describe('Rating field validation', () => { fieldType: 'rating', answer: '-1', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow leading zeroes in answer', () => { @@ -132,8 +144,11 @@ describe('Rating field validation', () => { isVisible: true, answer: '03', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty answer if optional', () => { @@ -151,8 +166,9 @@ describe('Rating field validation', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow empty answer if required', () => { @@ -170,7 +186,10 @@ describe('Rating field validation', () => { isVisible: true, answer: '', } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/table-validation.spec.js b/tests/unit/backend/utils/field-validation/table-validation.spec.js index df2f9c9bf8..f4e43863c1 100644 --- a/tests/unit/backend/utils/field-validation/table-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/table-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Table validation', () => { const makeTableField = (fieldId, columns, rowsOptions) => { const table = { @@ -50,32 +52,40 @@ describe('Table validation', () => { const columns = [makeDropdownColumn(fieldOptions)] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty submissions for optional columns', () => { const fieldOptions = ['a', 'b', 'c'] const columns = [makeDropdownColumn(fieldOptions, { required: false })] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid submission for dropdown column', () => { const fieldOptions = ['a', 'b', 'c'] const columns = [makeDropdownColumn(fieldOptions)] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['a']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow values not found in field options for dropdown column', () => { const fieldOptions = ['a', 'b', 'c'] const columns = [makeDropdownColumn(fieldOptions)] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['x']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) describe('Textfield column', () => { @@ -83,22 +93,27 @@ describe('Table validation', () => { const columns = [makeTextFieldColumn()] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty submissions for optional columns', () => { const columns = [makeTextFieldColumn({ required: false })] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow valid submission for textfield column', () => { const columns = [makeTextFieldColumn()] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['hello']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) describe('Multiple columns and rows', () => { @@ -107,8 +122,9 @@ describe('Table validation', () => { const columns = [makeDropdownColumn(fieldOptions), makeTextFieldColumn()] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, [['a', 'hello']]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow input with number of columns that do not match', () => { // WRONG @@ -122,8 +138,11 @@ describe('Table validation', () => { const response = makeTableResponse(fieldId, [ ['a', 'text1', 'text2', 'text3'], ]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow valid submissions for multiple rows', () => { const fieldOptions = ['a', 'b', 'c'] @@ -133,8 +152,9 @@ describe('Table validation', () => { ['a', 'hello'], ['b', 'world'], ]) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow invalid submissions for multiple rows', () => { const fieldOptions = ['a', 'b', 'c'] @@ -144,8 +164,11 @@ describe('Table validation', () => { ['a', 'hello'], ['x', 'world'], ]) // Invalid dropdown value for second row - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) describe('Number of rows', () => { @@ -153,16 +176,22 @@ describe('Table validation', () => { const columns = [makeTextFieldColumn()] const formField = makeTableField(fieldId, columns, { minimumRows: 3 }) const response = makeTableResponse(fieldId, Array(2).fill(['hello'])) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow submissions with more rows than min rows if addMoreRows is not set ', () => { const isLogic = false const columns = [makeTextFieldColumn()] const formField = makeTableField(fieldId, columns, { minimumRows: 3 }) const response = makeTableResponse(fieldId, Array(4).fill(['hello'])) - const testFunc = () => validateField(formId, formField, response, isLogic) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response, isLogic) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow submissions with more than max rows if max rows is set and addMoreRows is configured for that field', () => { const columns = [makeTextFieldColumn()] @@ -171,8 +200,11 @@ describe('Table validation', () => { addMoreRows: true, }) const response = makeTableResponse(fieldId, Array(100).fill(['hello'])) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow submissions with unlimited rows if max rows is not set and addMoreRows is configured for that field ', () => { const columns = [makeTextFieldColumn()] @@ -181,8 +213,9 @@ describe('Table validation', () => { addMoreRows: true, }) const response = makeTableResponse(fieldId, Array(100).fill(['hello'])) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) describe('Invalid input', () => { @@ -190,8 +223,11 @@ describe('Table validation', () => { const columns = [makeTextFieldColumn()] const formField = makeTableField(fieldId, columns) const response = makeTableResponse(fieldId, null) - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) }) diff --git a/tests/unit/backend/utils/field-validation/text-validation.spec.js b/tests/unit/backend/utils/field-validation/text-validation.spec.js index 0e9bee8029..72fb5d7920 100644 --- a/tests/unit/backend/utils/field-validation/text-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/text-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Text validation', () => { const makeTextField = (fieldType) => ( fieldId, @@ -42,30 +44,38 @@ describe('Text validation', () => { it('should disallow empty submissions if field is required', () => { const formField = makeShortTextField(fieldId) const response = makeShortTextResponse(fieldId, '') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty submissions if field is optional', () => { const formField = makeShortTextField(fieldId, {}, { required: false }) const response = makeShortTextResponse(fieldId, '') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow any number of characters in submission if selectedValidation is not set', () => { const formField = makeShortTextField(fieldId) const response = makeShortTextResponse(fieldId, 'hello world') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow whitespace answer if field is required', () => { const isLogic = false const formField = makeShortTextField(fieldId, {}, { required: true }) const response = makeShortTextResponse(fieldId, ' ') - const testFunc = () => validateField(formId, formField, response, isLogic) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response, isLogic) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMin if selectedValidation is Exact', () => { @@ -74,8 +84,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeShortTextResponse(fieldId, 'fewer') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMin if selectedValidation is Exact', () => { @@ -84,8 +97,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeShortTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMax if selectedValidation is Exact', () => { @@ -94,8 +110,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeShortTextResponse(fieldId, 'fewer') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMax if selectedValidation is Exact', () => { @@ -104,8 +123,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeShortTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMin if selectedValidation is Minimum', () => { @@ -114,8 +136,11 @@ describe('Text validation', () => { selectedValidation: 'Minimum', }) const response = makeShortTextResponse(fieldId, 'a') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMax if selectedValidation is Maximum', () => { @@ -124,8 +149,11 @@ describe('Text validation', () => { selectedValidation: 'Maximum', }) const response = makeShortTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) @@ -138,30 +166,38 @@ describe('Text validation', () => { it('should disallow empty submissions if field is required', () => { const formField = makeLongTextField(fieldId) const response = makeLongTextResponse(fieldId, '') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should allow empty submissions if field is optional', () => { const formField = makeLongTextField(fieldId, {}, { required: false }) const response = makeLongTextResponse(fieldId, '') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow a valid submission if selectedValidation is not set', () => { const formField = makeLongTextField(fieldId) const response = makeLongTextResponse(fieldId, 'hello world') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow whitespace answer if field is required', () => { const isLogic = false const formField = makeLongTextField(fieldId, {}, { required: true }) const response = makeLongTextResponse(fieldId, ' ') - const testFunc = () => validateField(formId, formField, response, isLogic) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response, isLogic) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMin if selectedValidation is Exact', () => { @@ -170,8 +206,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeLongTextResponse(fieldId, 'fewer') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMin if selectedValidation is Exact', () => { @@ -180,8 +219,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeLongTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMax if selectedValidation is Exact', () => { @@ -190,8 +232,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeLongTextResponse(fieldId, 'fewer') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMax if selectedValidation is Exact', () => { @@ -200,8 +245,11 @@ describe('Text validation', () => { selectedValidation: 'Exact', }) const response = makeLongTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow fewer characters than customMin if selectedValidation is Minimum', () => { @@ -210,8 +258,11 @@ describe('Text validation', () => { selectedValidation: 'Minimum', }) const response = makeLongTextResponse(fieldId, 'a') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow more characters than customMax if selectedValidation is Maximum', () => { @@ -220,8 +271,11 @@ describe('Text validation', () => { selectedValidation: 'Maximum', }) const response = makeLongTextResponse(fieldId, 'many more characters') - const testFunc = () => validateField(formId, formField, response) - expect(testFunc).toThrow() + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) }) }) diff --git a/tests/unit/backend/utils/field-validation/yesno-validation.spec.js b/tests/unit/backend/utils/field-validation/yesno-validation.spec.js index 15e63d9d37..0ac26739c2 100644 --- a/tests/unit/backend/utils/field-validation/yesno-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/yesno-validation.spec.js @@ -1,7 +1,9 @@ const { validateField, } = require('../../../../../dist/backend/app/utils/field-validation') - +const { + ValidateFieldError, +} = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Yes/No field validation', () => { it('should allow yes', () => { const response = { @@ -14,8 +16,9 @@ describe('Yes/No field validation', () => { fieldType: 'yes_no', required: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow no', () => { @@ -29,8 +32,9 @@ describe('Yes/No field validation', () => { fieldType: 'yes_no', required: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should allow empty string when not required', () => { @@ -44,8 +48,9 @@ describe('Yes/No field validation', () => { fieldType: 'yes_no', required: false, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).not.toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isOk()).toBe(true) + expect(validateResult._unsafeUnwrap()).toEqual(true) }) it('should disallow empty string when required', () => { @@ -60,8 +65,11 @@ describe('Yes/No field validation', () => { fieldType: 'yes_no', required: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) it('should disallow invalid input', () => { @@ -75,7 +83,10 @@ describe('Yes/No field validation', () => { fieldType: 'yes_no', required: true, } - const testFunc = () => validateField('formId', formField, response) - expect(testFunc).toThrow() + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Invalid answer submitted'), + ) }) })