Merge pull request #1354 from dod-ccpo/alpha-numeric-to-numbers
New TO Number Rules
This commit is contained in:
commit
e1ff093651
@ -10,11 +10,13 @@ from wtforms.fields.html5 import DateField
|
|||||||
from wtforms.validators import (
|
from wtforms.validators import (
|
||||||
Required,
|
Required,
|
||||||
Length,
|
Length,
|
||||||
|
Optional,
|
||||||
NumberRange,
|
NumberRange,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
import numbers
|
import numbers
|
||||||
|
|
||||||
from atst.forms.validators import Number, AlphaNumeric
|
from atst.forms.validators import Number, AlphaNumeric
|
||||||
|
|
||||||
from .data import JEDI_CLIN_TYPES
|
from .data import JEDI_CLIN_TYPES
|
||||||
@ -60,6 +62,14 @@ def validate_date_in_range(form, field):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_dashes(value):
|
||||||
|
return value.replace("-", "") if value else None
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_upper(value):
|
||||||
|
return value.upper() if value else None
|
||||||
|
|
||||||
|
|
||||||
class CLINForm(FlaskForm):
|
class CLINForm(FlaskForm):
|
||||||
jedi_clin_type = SelectField(
|
jedi_clin_type = SelectField(
|
||||||
translate("task_orders.form.clin_type_label"),
|
translate("task_orders.form.clin_type_label"),
|
||||||
@ -149,8 +159,8 @@ class AttachmentForm(BaseForm):
|
|||||||
class TaskOrderForm(BaseForm):
|
class TaskOrderForm(BaseForm):
|
||||||
number = StringField(
|
number = StringField(
|
||||||
label=translate("forms.task_order.number_description"),
|
label=translate("forms.task_order.number_description"),
|
||||||
filters=[remove_empty_string],
|
filters=[remove_empty_string, remove_dashes, coerce_upper],
|
||||||
validators=[Number(), Length(max=13)],
|
validators=[AlphaNumeric(), Length(min=13, max=17), Optional()],
|
||||||
)
|
)
|
||||||
pdf = FormField(
|
pdf = FormField(
|
||||||
AttachmentForm,
|
AttachmentForm,
|
||||||
|
98
js/components/__tests__/text_input.test.js
Normal file
98
js/components/__tests__/text_input.test.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
import textinput from '../text_input'
|
||||||
|
|
||||||
|
import { makeTestWrapper } from '../../test_utils/component_test_helpers'
|
||||||
|
|
||||||
|
const ToNumberWrapperComponent = makeTestWrapper({
|
||||||
|
components: {
|
||||||
|
textinput,
|
||||||
|
},
|
||||||
|
templatePath: 'text_input_to_number.html',
|
||||||
|
data: function() {
|
||||||
|
const { validation, initialValue } = this.initialData
|
||||||
|
return { validation, initialValue }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TextInput Validates Correctly', () => {
|
||||||
|
describe('taskOrderNumber validator', () => {
|
||||||
|
it('Should initialize with the validator and no validation icon', () => {
|
||||||
|
const wrapper = mount(ToNumberWrapperComponent, {
|
||||||
|
propsData: {
|
||||||
|
name: 'testTextInput',
|
||||||
|
initialData: {
|
||||||
|
validation: 'taskOrderNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.contains('.usa-input--success')).toBe(false)
|
||||||
|
expect(wrapper.contains('.usa-input--error')).toBe(false)
|
||||||
|
expect(wrapper.contains('.usa-input--validation--taskOrderNumber')).toBe(
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should allow valid TO numbers', () => {
|
||||||
|
const wrapper = mount(ToNumberWrapperComponent, {
|
||||||
|
propsData: {
|
||||||
|
name: 'testTextInput',
|
||||||
|
initialData: {
|
||||||
|
validation: 'taskOrderNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
var textInputField = wrapper.find('input[id="number"]')
|
||||||
|
var hiddenField = wrapper.find('input[name="number"]')
|
||||||
|
const validToNumbers = [
|
||||||
|
'12345678901234567',
|
||||||
|
'1234567890123',
|
||||||
|
'abc1234567890', // pragma: allowlist secret
|
||||||
|
'abc-1234567890',
|
||||||
|
'DC12-123-1234567890',
|
||||||
|
'fg34-987-1234567890',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const number of validToNumbers) {
|
||||||
|
// set value to be a valid TO number
|
||||||
|
textInputField.setValue(number)
|
||||||
|
// manually trigger change event in hidden fields
|
||||||
|
hiddenField.trigger('change')
|
||||||
|
// check for validation classes
|
||||||
|
expect(wrapper.contains('.usa-input--success')).toBe(true)
|
||||||
|
expect(wrapper.contains('.usa-input--error')).toBe(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not allow invalid TO numbers', () => {
|
||||||
|
const wrapper = mount(ToNumberWrapperComponent, {
|
||||||
|
propsData: {
|
||||||
|
name: 'testTextInput',
|
||||||
|
initialData: {
|
||||||
|
validation: 'taskOrderNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
var textInputField = wrapper.find('input[id="number"]')
|
||||||
|
var hiddenField = wrapper.find('input[name="number"]')
|
||||||
|
const invalidToNumbers = [
|
||||||
|
'1234567890',
|
||||||
|
'12345678901234567890', // pragma: allowlist secret
|
||||||
|
'123:4567890123',
|
||||||
|
'123_1234567890',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const number of invalidToNumbers) {
|
||||||
|
// set value to be a valid TO number
|
||||||
|
textInputField.setValue(number)
|
||||||
|
// manually trigger change event in hidden fields
|
||||||
|
hiddenField.trigger('change')
|
||||||
|
// check for validation classes
|
||||||
|
expect(wrapper.contains('.usa-input--success')).toBe(false)
|
||||||
|
expect(wrapper.contains('.usa-input--error')).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -106,9 +106,9 @@ export default {
|
|||||||
},
|
},
|
||||||
taskOrderNumber: {
|
taskOrderNumber: {
|
||||||
mask: false,
|
mask: false,
|
||||||
match: /^.{13}$/,
|
match: /(^[0-9a-zA-Z]{13,17}$)/,
|
||||||
unmask: [],
|
unmask: ['-'],
|
||||||
validationError: 'TO number must be 13 digits',
|
validationError: 'TO number must be between 13 and 17 characters',
|
||||||
},
|
},
|
||||||
usPhone: {
|
usPhone: {
|
||||||
mask: [
|
mask: [
|
||||||
|
@ -71,7 +71,7 @@ def test_update_adds_clins():
|
|||||||
|
|
||||||
def test_update_does_not_duplicate_clins():
|
def test_update_does_not_duplicate_clins():
|
||||||
task_order = TaskOrderFactory.create(
|
task_order = TaskOrderFactory.create(
|
||||||
number="3453453456", create_clins=[{"number": "123"}, {"number": "456"}]
|
number="3453453456123", create_clins=[{"number": "123"}, {"number": "456"}]
|
||||||
)
|
)
|
||||||
clins = [
|
clins = [
|
||||||
{
|
{
|
||||||
@ -93,7 +93,7 @@ def test_update_does_not_duplicate_clins():
|
|||||||
]
|
]
|
||||||
task_order = TaskOrders.update(
|
task_order = TaskOrders.update(
|
||||||
task_order_id=task_order.id,
|
task_order_id=task_order.id,
|
||||||
number="0000000000",
|
number="0000000000000",
|
||||||
clins=clins,
|
clins=clins,
|
||||||
pdf={"filename": "sample.pdf", "object_name": "1234567"},
|
pdf={"filename": "sample.pdf", "object_name": "1234567"},
|
||||||
)
|
)
|
||||||
@ -170,3 +170,11 @@ def test_update_enforces_unique_number():
|
|||||||
dupe_task_order = TaskOrderFactory.create()
|
dupe_task_order = TaskOrderFactory.create()
|
||||||
with pytest.raises(AlreadyExistsError):
|
with pytest.raises(AlreadyExistsError):
|
||||||
TaskOrders.update(dupe_task_order.id, task_order.number, [], None)
|
TaskOrders.update(dupe_task_order.id, task_order.number, [], None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_alphanumeric_number():
|
||||||
|
portfolio = PortfolioFactory.create()
|
||||||
|
valid_to_numbers = ["1234567890123", "ABC1234567890"]
|
||||||
|
|
||||||
|
for number in valid_to_numbers:
|
||||||
|
assert TaskOrders.create(portfolio.id, number, [], None)
|
||||||
|
@ -112,3 +112,37 @@ def test_no_number():
|
|||||||
http_request_form_data = {}
|
http_request_form_data = {}
|
||||||
form = TaskOrderForm(http_request_form_data)
|
form = TaskOrderForm(http_request_form_data)
|
||||||
assert form.data["number"] is None
|
assert form.data["number"] is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_allows_alphanumeric():
|
||||||
|
valid_to_numbers = ["1234567890123", "ABC1234567890"]
|
||||||
|
|
||||||
|
for number in valid_to_numbers:
|
||||||
|
form = TaskOrderForm({"number": number})
|
||||||
|
assert form.validate()
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_allows_between_13_and_17_characters():
|
||||||
|
valid_to_numbers = ["123456789012345", "ABCDEFG1234567890"]
|
||||||
|
|
||||||
|
for number in valid_to_numbers:
|
||||||
|
form = TaskOrderForm({"number": number})
|
||||||
|
assert form.validate()
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_strips_dashes():
|
||||||
|
valid_to_numbers = ["123-456789-012345", "ABCD-EFG12345-67890"]
|
||||||
|
|
||||||
|
for number in valid_to_numbers:
|
||||||
|
form = TaskOrderForm({"number": number})
|
||||||
|
assert form.validate()
|
||||||
|
assert not "-" in form.number.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_case_coerces_all_caps():
|
||||||
|
valid_to_numbers = ["12345678012345", "AbcEFg1234567890"]
|
||||||
|
|
||||||
|
for number in valid_to_numbers:
|
||||||
|
form = TaskOrderForm({"number": number})
|
||||||
|
assert form.validate()
|
||||||
|
assert form.number.data == number.upper()
|
||||||
|
@ -35,6 +35,7 @@ class TaskOrderPdfForm(Form):
|
|||||||
|
|
||||||
class TaskOrderForm(Form):
|
class TaskOrderForm(Form):
|
||||||
pdf = FormField(TaskOrderPdfForm, label="task_order_pdf")
|
pdf = FormField(TaskOrderPdfForm, label="task_order_pdf")
|
||||||
|
number = StringField(label="task_order_number", default="number")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -63,6 +64,12 @@ def multi_checkbox_input_macro(env):
|
|||||||
return getattr(multi_checkbox_template.module, "MultiCheckboxInput")
|
return getattr(multi_checkbox_template.module, "MultiCheckboxInput")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def text_input_macro(env):
|
||||||
|
text_input_template = env.get_template("components/text_input.html")
|
||||||
|
return getattr(text_input_template.module, "TextInput")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def initial_value_form(scope="function"):
|
def initial_value_form(scope="function"):
|
||||||
return InitialValueForm()
|
return InitialValueForm()
|
||||||
@ -170,3 +177,10 @@ def test_make_pop_date_range(env, app):
|
|||||||
index=1,
|
index=1,
|
||||||
)
|
)
|
||||||
write_template(pop_date_range, "pop_date_range.html")
|
write_template(pop_date_range, "pop_date_range.html")
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_text_input_template(text_input_macro, task_order_form):
|
||||||
|
text_input_to_number = text_input_macro(
|
||||||
|
task_order_form.number, validation="taskOrderNumber"
|
||||||
|
)
|
||||||
|
write_template(text_input_to_number, "text_input_to_number.html")
|
||||||
|
@ -158,7 +158,7 @@ def test_task_orders_form_step_two_add_number(client, user_session, task_order):
|
|||||||
|
|
||||||
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
|
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
|
||||||
user_session(task_order.portfolio.owner)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {"number": "1234567890"}
|
form_data = {"number": "abc-1234567890"}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for(
|
url_for(
|
||||||
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
|
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
|
||||||
@ -167,7 +167,7 @@ def test_task_orders_submit_form_step_two_add_number(client, user_session, task_
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert task_order.number == "1234567890"
|
assert task_order.number == "ABC1234567890" # pragma: allowlist secret
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_submit_form_step_two_enforces_unique_number(
|
def test_task_orders_submit_form_step_two_enforces_unique_number(
|
||||||
@ -194,7 +194,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to(
|
|||||||
client, user_session, task_order
|
client, user_session, task_order
|
||||||
):
|
):
|
||||||
user_session(task_order.portfolio.owner)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {"number": "0000000000"}
|
form_data = {"number": "0000000000000"}
|
||||||
original_number = task_order.number
|
original_number = task_order.number
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for(
|
url_for(
|
||||||
@ -203,7 +203,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to(
|
|||||||
data=form_data,
|
data=form_data,
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert task_order.number == "0000000000"
|
assert task_order.number == "0000000000000"
|
||||||
assert task_order.number != original_number
|
assert task_order.number != original_number
|
||||||
|
|
||||||
|
|
||||||
|
@ -663,7 +663,7 @@ def test_task_orders_new_get_routes(get_url_assert_status):
|
|||||||
def test_task_orders_new_post_routes(post_url_assert_status):
|
def test_task_orders_new_post_routes(post_url_assert_status):
|
||||||
post_routes = [
|
post_routes = [
|
||||||
("task_orders.submit_form_step_one_add_pdf", {"pdf": ""}),
|
("task_orders.submit_form_step_one_add_pdf", {"pdf": ""}),
|
||||||
("task_orders.submit_form_step_two_add_number", {"number": "1234567890"}),
|
("task_orders.submit_form_step_two_add_number", {"number": "1234567890123"}),
|
||||||
(
|
(
|
||||||
"task_orders.submit_form_step_three_add_clins",
|
"task_orders.submit_form_step_three_add_clins",
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user