Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: backend validation does not prevent responses on hidden fields #809

Merged
merged 20 commits into from
Dec 21, 2020

Conversation

tshuli
Copy link
Contributor

@tshuli tshuli commented Dec 7, 2020

Problem

Builds on #736
Closes #732
Closes #779

Solution

  • Add check for isVisible in validateField() for email mode submissions
  • Also adds / updates unit tests

Tests

  • Create a email mode form with Yes/No field, and a Number Field hidden behind it. Attempt to submit a number response programatically with the number field still hidden. Submission should fail.
  • Now unhide the Number Field. Attempt to submit a response on the number field normally. Submission should pass.
  • Create email mode form with all field types (including Yes/No) hidden behind a Yes/No field. Submit while keeping the fields hidden. Submission should pass.
  • Create storage mode form with a mobile, email and short text field hidden behind a Yes/No field. Unhide the mobile and email fields and submit a response. Submission should pass.
  • Create storage mode form with a mobile, email and short text field hidden behind a Yes/No field. Submit a response without unhiding the fields. Submission should pass.

@tshuli tshuli force-pushed the fix/form-logic-validation branch 3 times, most recently from 4b9ac70 to 21ea9d7 Compare December 8, 2020 14:29
@tshuli
Copy link
Contributor Author

tshuli commented Dec 8, 2020

@mantariksh In this PR, I limited the scope of the hidden field response check to email mode forms only; see/src/app/utils/field-validation/index.ts:

  if (
    responseMode === ResponseMode.Email &&
    isResponsePresentOnHiddenField(response)
  ) {
    return err(
      new ValidateFieldError(`Attempted to submit response on a hidden field`),
    )
  }

We discussed that validateField is not appropriate to do logic validation, and we should instead set isVisible to be true for encrypt mode. This can be done in src/app/modules/submission/submission.service.ts:

    const processingResponse: ProcessedFieldResponse = {
      ...response,
      isVisible:
        // Set isVisible as true for Encrypt mode because we cannot tell
        // Prevents downstream validateField from incorrectly preventing
        // encrypt mode submissions with responses on hidden fields
        form.responseMode === ResponseMode.Encrypt
          ? true
          : visibleFieldIds.has(responseId),
      question: formField.getQuestion(),
    }

However for required fields, if the field is visible, we also check if the field is non-empty, for both email and encrypt mode forms - see src/app/utils/field-validation/index.ts. Setting isVisible to be true for all encrypted mode fields leads to an error here as there is no response for the required, 'visible' field. This means that we now instead have to check, in singleAnswerRequiresValidation(), if the form is an email or encrypt mode form.

const singleAnswerRequiresValidation = (
  formField: IField,
  response: ProcessedSingleAnswerResponse,
) => (formField.required && response.isVisible) || response.answer.trim() !== ''

Basically, there is no way around this issue because fundamentally both email and encrypt mode submissions are processed in the same way today . For the scope of this PR which is to implement a bug fix for responses in email mode submissions, I suggest we stick to the more straightforward implementation which is to check for responses on hidden fields only on email mode submissions. When the sharding is done in #780, we can do away with isVisible for encrypt mode and this will solve the problem more fundamentally.

For your consideration

@tshuli
Copy link
Contributor Author

tshuli commented Dec 9, 2020

@mantariksh as discussed, i've changed it to set isVisible = true for encrypt mode if there are responses. thanks for reviewing!

Copy link
Contributor

@mantariksh mantariksh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main question is about deleted jasmine tests

*/
const isResponsePresentOnHiddenField = (response: FieldResponse): boolean => {
if (isProcessedSingleAnswerResponse(response)) {
if (!response.isVisible && response.answer.trim() !== '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will be much clearer if you return early at the start: if (response.isVisible) return false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! edited

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah but then you can also remove all the `!response.isVisible. subsequently!

mobileField,
'+6587654321',
)
mobileResponse.isVisible = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this made me realise that the tests are no longer behaving as intended. the object which we pass into getProcessedResponses should be a FieldResponse, not some form of ProcessedResponse. might it be better to write a generateFieldResponse function which doesn't include isVisible or isUserVerified? or even better, rename generateSingleAnswerResponse to generateProcessedSingleAnswerResponse?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edited

@@ -25,7 +25,7 @@ export interface ICheckboxResponse extends IBaseResponse {
answerArray: string[]
}

export type ITableRow = string[]
export type ITableRow = Array<string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we standardise to string[]? that's how it's written everywhere else in the codebase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edited

@@ -1068,89 +1058,6 @@ describe('Email Submissions Controller', () => {
prepareSubmissionThenCompare(expected, done)
})

it('excludes field if isVisible is false for autoReplyData', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are all these tests deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for highlighting. I removed them initially as they were invalid since they were inputting responses to hidden fields, but on review, I've instead fixed the faulty values

@liangyuanruo liangyuanruo removed their request for review December 15, 2020 09:16
Copy link
Contributor

@mantariksh mantariksh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for the comprehensive work on the tests!

*/
const isResponsePresentOnHiddenField = (response: FieldResponse): boolean => {
if (isProcessedSingleAnswerResponse(response)) {
if (!response.isVisible && response.answer.trim() !== '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah but then you can also remove all the `!response.isVisible. subsequently!

@tshuli tshuli merged commit ae8f5df into develop Dec 21, 2020
@tshuli tshuli mentioned this pull request Dec 22, 2020
@liangyuanruo liangyuanruo deleted the fix/form-logic-validation branch January 26, 2021 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants