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: skip qty validation when multiqty is disabled #6624

Merged
merged 7 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ export const ProductModal = ({
const validateMin = !!val && dollarsToCents(val) >= minPaymentAmountCents
// Repeat the check on minPaymentAmountCents for correct typing
if (!!minPaymentAmountCents && !validateMin) {
return `Please enter a payment amount above ${formatCurrency(
return `The maximum amount is ${formatCurrency(
Number(centsToDollars(minPaymentAmountCents)),
)}`
}

const validateMax = !!val && dollarsToCents(val) <= maxPaymentAmountCents
// Repeat the check on maxPaymentAmountCents for correct typing
if (!!maxPaymentAmountCents && !validateMax) {
return `Please enter a payment amount below ${formatCurrency(
return `The maximum amount is ${formatCurrency(
Number(centsToDollars(maxPaymentAmountCents)),
)}`
}
Expand All @@ -120,6 +120,7 @@ export const ProductModal = ({

const minQtyValidation: RegisterOptions<ProductInput, typeof MIN_QTY_KEY> = {
validate: (val) => {
if (!getValues('multi_qty')) return true
if (val <= 0) {
return 'Please enter a value greater than 0'
}
Expand All @@ -131,6 +132,7 @@ export const ProductModal = ({
}
const maxQtyValidation: RegisterOptions<ProductInput, typeof MAX_QTY_KEY> = {
validate: (val) => {
if (!getValues('multi_qty')) return true
if (val <= 0) {
return 'Please enter a value greater than 0'
}
Expand Down Expand Up @@ -218,10 +220,21 @@ export const ProductModal = ({
</FormControl>
<Box>
<FormControl>
<Toggle
{...register('multi_qty')}
label="Quantity limit"
description="Set the minimum and maximum quantities respondents can select"
<Controller
name={'multi_qty'}
control={control}
render={({ field: { value, onChange, ...rest } }) => (
<Toggle
{...rest}
isChecked={value}
onChange={(e) => {
onChange(e)
trigger([MIN_QTY_KEY, MAX_QTY_KEY, DISPLAY_AMOUNT_KEY])
}}
label="Quantity limit"
description="Set the minimum and maximum quantities respondents can select"
/>
)}
/>
</FormControl>
<FormControl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { ObjectId } from 'bson-ext'
import mongoose from 'mongoose'
import { PaymentsUpdateDto, PaymentType } from 'shared/types'
import {
PaymentsProductUpdateDto,
PaymentsUpdateDto,
PaymentType,
} from 'shared/types'

import * as PaymentConfig from 'src/app/config/features/payment.config'
import { getEncryptedFormModel } from 'src/app/models/form.server.model'
Expand Down Expand Up @@ -52,7 +56,7 @@ describe('admin-form.payment.service', () => {
)

// Assert
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
InvalidPaymentAmountError,
)
Expand All @@ -76,7 +80,7 @@ describe('admin-form.payment.service', () => {
)

// Assert
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
InvalidPaymentAmountError,
)
Expand All @@ -99,7 +103,7 @@ describe('admin-form.payment.service', () => {
// Assert
expect(putSpy).toHaveBeenCalledWith(mockFormId, updatedPaymentSettings)

expect(actualResult.isOk()).toEqual(true)
expect(actualResult.isOk()).toBeTrue()
// Should equal updatedPaymentSettings obj
expect(actualResult._unsafeUnwrap()).toEqual(updatedPaymentSettings)
})
Expand All @@ -118,7 +122,7 @@ describe('admin-form.payment.service', () => {

// Assert
expect(putSpy).toHaveBeenCalledWith(mockFormId, updatedPaymentSettings)
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(DatabaseError)
})

Expand All @@ -136,7 +140,7 @@ describe('admin-form.payment.service', () => {

// Assert
expect(putSpy).toHaveBeenCalledWith(mockFormId, updatedPaymentSettings)
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
FormNotFoundError,
)
Expand Down Expand Up @@ -182,7 +186,7 @@ describe('admin-form.payment.service', () => {
mockFormId,
updatedPaymentSettingsMaxAboveMin,
)
expect(actualResult.isOk()).toEqual(true)
expect(actualResult.isOk()).toBeTrue()
expect(actualResult._unsafeUnwrap()).toEqual(
updatedPaymentSettingsMaxAboveMin,
)
Expand Down Expand Up @@ -215,7 +219,7 @@ describe('admin-form.payment.service', () => {
mockFormId,
updatedPaymentSettingsMaxAboveMin,
)
expect(actualResult.isOk()).toEqual(true)
expect(actualResult.isOk()).toBeTrue()
expect(actualResult._unsafeUnwrap()).toEqual(
updatedPaymentSettingsMaxAboveMin,
)
Expand All @@ -237,7 +241,7 @@ describe('admin-form.payment.service', () => {

// Assert
expect(putSpy).not.toHaveBeenCalled()
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
InvalidPaymentAmountError,
)
Expand All @@ -254,19 +258,157 @@ describe('admin-form.payment.service', () => {
max_amount: 1000,
} as PaymentsUpdateDto

const putSpy = jest.spyOn(EncryptFormModel, 'updatePaymentsById')
// Act
const actualResult = await AdminFormPaymentService.updatePayments(
mockFormId,
updatedPaymentSettingsBelow,
)

const putSpy = jest.spyOn(EncryptFormModel, 'updatePaymentsById')
expect(putSpy).not.toHaveBeenCalled()
expect(actualResult.isErr()).toEqual(true)
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
InvalidPaymentAmountError,
)
})
})
})

describe('updatePaymentsProduct', () => {
const mockFormId = new ObjectId().toString()

describe('with multi qty enabled', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should allow updates when product * max qty is below max amount', async () => {
jest.replaceProperty(PaymentConfig, 'paymentConfig', {
...PaymentConfig.paymentConfig,
maxPaymentAmountCents: 100,
})
const updatedProducts = [
{
multi_qty: true,
max_qty: 9,
amount_cents: 10,
},
] as PaymentsProductUpdateDto

const putSpy = jest
.spyOn(EncryptFormModel, 'updatePaymentsProductById')
.mockResolvedValueOnce({
payments_field: {},
} as unknown as IEncryptedFormDocument)

// Act
const actualResult =
await AdminFormPaymentService.updatePaymentsProduct(
mockFormId,
updatedProducts,
)

expect(putSpy).toHaveBeenCalledOnce()
expect(actualResult.isOk()).toBeTrue()
})

it('should allow updates when product * max qty is at max amount', async () => {
jest.replaceProperty(PaymentConfig, 'paymentConfig', {
...PaymentConfig.paymentConfig,
maxPaymentAmountCents: 100,
})
const updatedProducts = [
{
multi_qty: true,
max_qty: 10,
amount_cents: 10,
},
] as PaymentsProductUpdateDto

const putSpy = jest
.spyOn(EncryptFormModel, 'updatePaymentsProductById')
.mockResolvedValueOnce({
payments_field: {},
} as unknown as IEncryptedFormDocument)

// Act
const actualResult =
await AdminFormPaymentService.updatePaymentsProduct(
mockFormId,
updatedProducts,
)

expect(putSpy).toHaveBeenCalled()
expect(actualResult.isOk()).toBeTrue()
})

it('should disallow updates when product * max qty exceeds max amount', async () => {
jest.replaceProperty(PaymentConfig, 'paymentConfig', {
...PaymentConfig.paymentConfig,
maxPaymentAmountCents: 100,
})
const updatedProducts = [
{
multi_qty: true,
max_qty: 11,
amount_cents: 10,
},
] as PaymentsProductUpdateDto

const putSpy = jest
.spyOn(EncryptFormModel, 'updatePaymentsProductById')
.mockResolvedValueOnce({
payments_field: {},
} as unknown as IEncryptedFormDocument)

// Act
const actualResult =
await AdminFormPaymentService.updatePaymentsProduct(
mockFormId,
updatedProducts,
)

expect(putSpy).not.toHaveBeenCalled()
expect(actualResult.isErr()).toBeTrue()
expect(actualResult._unsafeUnwrapErr()).toBeInstanceOf(
InvalidPaymentAmountError,
)
})
})

describe('with multi qty disabled', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('should allow updates when product * max qty possibly exceeds max amount', async () => {
jest.replaceProperty(PaymentConfig, 'paymentConfig', {
...PaymentConfig.paymentConfig,
maxPaymentAmountCents: 100,
})
const updatedProducts = [
{
multi_qty: true,
KenLSM marked this conversation as resolved.
Show resolved Hide resolved
max_qty: 11,
amount_cents: 10,
},
] as PaymentsProductUpdateDto

const putSpy = jest
.spyOn(EncryptFormModel, 'updatePaymentsProductById')
.mockResolvedValueOnce({
payments_field: {},
} as unknown as IEncryptedFormDocument)

// Act
const actualResult =
await AdminFormPaymentService.updatePaymentsProduct(
mockFormId,
updatedProducts,
)

expect(putSpy).not.toHaveBeenCalled()
expect(actualResult.isOk()).toBeTrue()
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ export const updatePaymentsProduct = (
PossibleDatabaseError | FormNotFoundError | InvalidPaymentAmountError
> => {
for (const product of newProducts) {
const maximumSelectableQtyCost = product.max_qty * product.amount_cents
// treat as a single item purchase if multi_qty is false
const qtyModifier = product.multi_qty ? product.max_qty : 1
const maximumSelectableQtyCost = qtyModifier * product.amount_cents
if (maximumSelectableQtyCost > paymentConfig.maxPaymentAmountCents) {
return errAsync(
new InvalidPaymentAmountError(
Expand Down
Loading