Merge pull request #80 from dod-ccpo/158874759-validate-pe-number
Validate PE number
This commit is contained in:
commit
ef1f2224e9
22
atst/app.py
22
atst/app.py
@ -58,19 +58,31 @@ def make_app(config, deps, **kwargs):
|
||||
url(
|
||||
r"/requests/new",
|
||||
RequestNew,
|
||||
{"page": "requests_new", "requests_client": deps["requests_client"]},
|
||||
{
|
||||
"page": "requests_new",
|
||||
"requests_client": deps["requests_client"],
|
||||
"fundz_client": deps["fundz_client"],
|
||||
},
|
||||
name="request_new",
|
||||
),
|
||||
url(
|
||||
r"/requests/new/([0-9])",
|
||||
RequestNew,
|
||||
{"page": "requests_new", "requests_client": deps["requests_client"]},
|
||||
{
|
||||
"page": "requests_new",
|
||||
"requests_client": deps["requests_client"],
|
||||
"fundz_client": deps["fundz_client"],
|
||||
},
|
||||
name="request_form_new",
|
||||
),
|
||||
url(
|
||||
r"/requests/new/([0-9])/(\S+)",
|
||||
RequestNew,
|
||||
{"page": "requests_new", "requests_client": deps["requests_client"]},
|
||||
{
|
||||
"page": "requests_new",
|
||||
"requests_client": deps["requests_client"],
|
||||
"fundz_client": deps["fundz_client"],
|
||||
},
|
||||
name="request_form_update",
|
||||
),
|
||||
url(
|
||||
@ -128,6 +140,10 @@ def make_deps(config):
|
||||
api_version="v1",
|
||||
validate_cert=validate_cert,
|
||||
),
|
||||
"fundz_client": ApiClient(
|
||||
config["default"]["FUNDZ_BASE_URL"],
|
||||
validate_cert=validate_cert,
|
||||
),
|
||||
"requests_client": ApiClient(
|
||||
config["default"]["REQUESTS_QUEUE_BASE_URL"],
|
||||
api_version="v1",
|
||||
|
@ -1,12 +1,40 @@
|
||||
import tornado
|
||||
from tornado.gen import Return
|
||||
from wtforms.fields.html5 import EmailField
|
||||
from wtforms.fields import StringField, SelectField
|
||||
from wtforms.form import Form
|
||||
from wtforms.validators import Required, Email
|
||||
from wtforms_tornado import Form
|
||||
|
||||
from .fields import NewlineListField
|
||||
from .forms import ValidatedForm
|
||||
|
||||
|
||||
class FinancialForm(Form):
|
||||
@tornado.gen.coroutine
|
||||
def validate_pe_id(field, existing_request, fundz_client):
|
||||
response = yield fundz_client.get(
|
||||
"/pe-number/{}".format(field.data),
|
||||
raise_error=False,
|
||||
)
|
||||
if not response.ok:
|
||||
field.errors.append(
|
||||
"We couldn't find that PE number, but if you have double checked "
|
||||
"it you can submit anyway. Your request will need to go through a "
|
||||
"manual review."
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class FinancialForm(ValidatedForm):
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def perform_extra_validation(self, existing_request, fundz_client):
|
||||
valid = True
|
||||
if not existing_request or existing_request.get('pe_id') != self.pe_id.data:
|
||||
valid = yield validate_pe_id(self.pe_id, existing_request, fundz_client)
|
||||
raise Return(valid)
|
||||
|
||||
task_order_id = StringField(
|
||||
"Task Order Number associated with this request.", validators=[Required()]
|
||||
)
|
||||
|
12
atst/forms/forms.py
Normal file
12
atst/forms/forms.py
Normal file
@ -0,0 +1,12 @@
|
||||
import tornado
|
||||
from tornado.gen import Return
|
||||
from wtforms_tornado import Form
|
||||
|
||||
|
||||
class ValidatedForm(Form):
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def perform_extra_validation(self, *args, **kwargs):
|
||||
"""A coroutine that performs any applicable extra validation. Must
|
||||
return True if the form is valid or False otherwise."""
|
||||
raise Return(True)
|
@ -1,13 +1,13 @@
|
||||
from wtforms.fields.html5 import EmailField, TelField
|
||||
from wtforms.fields import RadioField, StringField
|
||||
from wtforms.validators import Required, Email
|
||||
from wtforms_tornado import Form
|
||||
import pendulum
|
||||
from .fields import DateField
|
||||
from .forms import ValidatedForm
|
||||
from .validators import DateRange, PhoneNumber, Alphabet
|
||||
|
||||
|
||||
class OrgForm(Form):
|
||||
class OrgForm(ValidatedForm):
|
||||
fname_request = StringField("First Name", validators=[Required(), Alphabet()])
|
||||
lname_request = StringField("Last Name", validators=[Required(), Alphabet()])
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
from wtforms.fields import StringField
|
||||
from wtforms.validators import Required, Email, Length
|
||||
from wtforms_tornado import Form
|
||||
from .forms import ValidatedForm
|
||||
from .validators import IsNumber, Alphabet
|
||||
|
||||
|
||||
class POCForm(Form):
|
||||
class POCForm(ValidatedForm):
|
||||
fname_poc = StringField("POC First Name", validators=[Required(), Alphabet()])
|
||||
lname_poc = StringField("POC Last Name", validators=[Required(), Alphabet()])
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
from wtforms.fields.html5 import IntegerField
|
||||
from wtforms.fields import RadioField, StringField, TextAreaField
|
||||
from wtforms.validators import NumberRange, InputRequired
|
||||
from wtforms_tornado import Form
|
||||
from .fields import DateField
|
||||
from .forms import ValidatedForm
|
||||
from .validators import DateRange
|
||||
import pendulum
|
||||
|
||||
|
||||
class RequestForm(Form):
|
||||
class RequestForm(ValidatedForm):
|
||||
|
||||
# Details of Use: Overall Request Details
|
||||
dollar_value = IntegerField(
|
||||
|
@ -1,6 +1,7 @@
|
||||
from wtforms.fields import BooleanField
|
||||
from wtforms_tornado import Form
|
||||
|
||||
from .forms import ValidatedForm
|
||||
|
||||
|
||||
class ReviewForm(Form):
|
||||
class ReviewForm(ValidatedForm):
|
||||
reviewed = BooleanField("I have reviewed this data and it is correct.")
|
||||
|
@ -10,9 +10,17 @@ from atst.forms.financial import FinancialForm
|
||||
|
||||
|
||||
class RequestNew(BaseHandler):
|
||||
def initialize(self, page, requests_client):
|
||||
def initialize(self, page, requests_client, fundz_client):
|
||||
self.page = page
|
||||
self.requests_client = requests_client
|
||||
self.fundz_client = fundz_client
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def get_existing_request(self, request_id):
|
||||
if request_id is None:
|
||||
return {}
|
||||
request = yield self.requests_client.get("/requests/{}".format(request_id))
|
||||
return request.json
|
||||
|
||||
@tornado.web.authenticated
|
||||
@tornado.gen.coroutine
|
||||
@ -20,22 +28,19 @@ class RequestNew(BaseHandler):
|
||||
self.check_xsrf_cookie()
|
||||
screen = int(screen)
|
||||
post_data = self.request.arguments
|
||||
current_user = self.get_current_user()
|
||||
existing_request = yield self.get_existing_request(request_id)
|
||||
jedi_flow = JEDIRequestFlow(
|
||||
self.requests_client, screen, post_data=post_data, request_id=request_id
|
||||
self.requests_client,
|
||||
self.fundz_client,
|
||||
screen,
|
||||
post_data=post_data,
|
||||
request_id=request_id,
|
||||
current_user=current_user,
|
||||
existing_request=existing_request,
|
||||
)
|
||||
|
||||
if jedi_flow.validate():
|
||||
response = yield jedi_flow.create_or_update_request(self.get_current_user())
|
||||
if response.ok:
|
||||
where = self.application.default_router.reverse_url(
|
||||
"request_form_update", str(screen + 1), jedi_flow.request_id
|
||||
)
|
||||
self.redirect(where)
|
||||
else:
|
||||
self.set_status(response.code)
|
||||
else:
|
||||
self.render(
|
||||
"requests/screen-%d.html.to" % int(screen),
|
||||
rerender_args = dict(
|
||||
f=jedi_flow.form,
|
||||
data=post_data,
|
||||
page=self.page,
|
||||
@ -45,6 +50,31 @@ class RequestNew(BaseHandler):
|
||||
request_id=jedi_flow.request_id,
|
||||
)
|
||||
|
||||
if jedi_flow.validate():
|
||||
response = yield jedi_flow.create_or_update_request()
|
||||
if response.ok:
|
||||
valid = yield jedi_flow.validate_warnings()
|
||||
if valid:
|
||||
if jedi_flow.next_screen >= len(jedi_flow.screens):
|
||||
where = "/requests"
|
||||
else:
|
||||
where = self.application.default_router.reverse_url(
|
||||
"request_form_update", jedi_flow.next_screen, jedi_flow.request_id
|
||||
)
|
||||
self.redirect(where)
|
||||
else:
|
||||
self.render(
|
||||
"requests/screen-%d.html.to" % int(screen),
|
||||
**rerender_args
|
||||
)
|
||||
else:
|
||||
self.set_status(response.code)
|
||||
else:
|
||||
self.render(
|
||||
"requests/screen-%d.html.to" % int(screen),
|
||||
**rerender_args
|
||||
)
|
||||
|
||||
@tornado.web.authenticated
|
||||
@tornado.gen.coroutine
|
||||
def get(self, screen=1, request_id=None):
|
||||
@ -60,7 +90,7 @@ class RequestNew(BaseHandler):
|
||||
request = response.json
|
||||
|
||||
jedi_flow = JEDIRequestFlow(
|
||||
self.requests_client, screen, request, request_id=request_id
|
||||
self.requests_client, self.fundz_client, screen, request, request_id=request_id
|
||||
)
|
||||
|
||||
self.render(
|
||||
@ -80,12 +110,16 @@ class JEDIRequestFlow(object):
|
||||
def __init__(
|
||||
self,
|
||||
requests_client,
|
||||
fundz_client,
|
||||
current_step,
|
||||
request=None,
|
||||
post_data=None,
|
||||
request_id=None,
|
||||
current_user=None,
|
||||
existing_request=None,
|
||||
):
|
||||
self.requests_client = requests_client
|
||||
self.fundz_client = fundz_client
|
||||
|
||||
self.current_step = current_step
|
||||
self.request = request
|
||||
@ -96,6 +130,9 @@ class JEDIRequestFlow(object):
|
||||
self.request_id = request_id
|
||||
self.form = self._form()
|
||||
|
||||
self.current_user = current_user
|
||||
self.existing_request = existing_request
|
||||
|
||||
def _form(self):
|
||||
if self.is_post:
|
||||
return self.form_class()(self.post_data)
|
||||
@ -107,6 +144,14 @@ class JEDIRequestFlow(object):
|
||||
def validate(self):
|
||||
return self.form.validate()
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def validate_warnings(self):
|
||||
valid = yield self.form.perform_extra_validation(
|
||||
self.existing_request.get('body', {}).get(self.form_section),
|
||||
self.fundz_client,
|
||||
)
|
||||
return valid
|
||||
|
||||
@property
|
||||
def current_screen(self):
|
||||
return self.screens[self.current_step - 1]
|
||||
@ -185,9 +230,9 @@ class JEDIRequestFlow(object):
|
||||
]
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def create_or_update_request(self, user):
|
||||
def create_or_update_request(self):
|
||||
request_data = {
|
||||
"creator_id": user["id"],
|
||||
"creator_id": self.current_user["id"],
|
||||
"request": {self.form_section: self.form.data},
|
||||
}
|
||||
if self.request_id:
|
||||
|
@ -4,6 +4,7 @@ ENVIRONMENT = dev
|
||||
DEBUG = true
|
||||
AUTHZ_BASE_URL = http://localhost:8002
|
||||
AUTHNID_BASE_URL= https://localhost:8001
|
||||
FUNDZ_BASE_URL= http://localhost:8004
|
||||
COOKIE_SECRET = some-secret-please-replace
|
||||
SECRET = change_me_into_something_secret
|
||||
CAC_URL = https://localhost:8001
|
||||
|
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from atst.app import make_app, make_deps, make_config
|
||||
from tests.mocks import MockApiClient, MockRequestsClient, MockAuthzClient
|
||||
from tests.mocks import MockApiClient, MockFundzClient, MockRequestsClient, MockAuthzClient
|
||||
from atst.sessions import DictSessions
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ def app():
|
||||
"authz_client": MockAuthzClient("authz"),
|
||||
"requests_client": MockRequestsClient("requests"),
|
||||
"authnid_client": MockApiClient("authnid"),
|
||||
"fundz_client": MockFundzClient("fundz"),
|
||||
"sessions": DictSessions(),
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
import re
|
||||
import pytest
|
||||
import tornado
|
||||
import urllib
|
||||
from tests.mocks import MOCK_REQUEST, MOCK_VALID_PE_ID
|
||||
|
||||
ERROR_CLASS = "usa-input-error-message"
|
||||
MOCK_USER = {
|
||||
@ -47,3 +50,82 @@ def test_submit_valid_request_form(monkeypatch, http_client, base_url):
|
||||
body="meaning=42",
|
||||
)
|
||||
assert "/requests/new/2" in response.effective_url
|
||||
|
||||
|
||||
class TestPENumberInForm:
|
||||
|
||||
required_data = {
|
||||
"pe_id": "123",
|
||||
"task_order_id": "1234567899C0001",
|
||||
"fname_co": "Contracting",
|
||||
"lname_co": "Officer",
|
||||
"email_co": "jane@mail.mil",
|
||||
"office_co": "WHS",
|
||||
"fname_cor": "Officer",
|
||||
"lname_cor": "Representative",
|
||||
"email_cor": "jane@mail.mil",
|
||||
"office_cor": "WHS",
|
||||
"funding_type": "RDTE",
|
||||
"funding_type_other": "other",
|
||||
"clin_0001": "50,000",
|
||||
"clin_0003": "13,000",
|
||||
"clin_1001": "30,000",
|
||||
"clin_1003": "7,000",
|
||||
"clin_2001": "30,000",
|
||||
"clin_2003": "7,000",
|
||||
}
|
||||
|
||||
def _set_monkeypatches(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"atst.handlers.request_new.RequestNew.get_current_user", lambda s: MOCK_USER
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"atst.handlers.request_new.RequestNew.check_xsrf_cookie", lambda s: True
|
||||
)
|
||||
monkeypatch.setattr("atst.forms.request.RequestForm.validate", lambda s: True)
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def submit_data(self, http_client, base_url, data):
|
||||
response = yield http_client.fetch(
|
||||
base_url + "/requests/new/5/{}".format(MOCK_REQUEST["id"]),
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
body=urllib.parse.urlencode(data),
|
||||
follow_redirects=False,
|
||||
raise_error=False,
|
||||
)
|
||||
return response
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_submit_request_form_with_invalid_pe_id(self, monkeypatch, http_client, base_url):
|
||||
self._set_monkeypatches(monkeypatch)
|
||||
|
||||
response = yield self.submit_data(http_client, base_url, self.required_data)
|
||||
|
||||
assert "We couldn\'t find that PE number" in response.body.decode()
|
||||
assert response.code == 200
|
||||
assert "/requests/new/5" in response.effective_url
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_submit_request_form_with_unchanged_pe_id(self, monkeypatch, http_client, base_url):
|
||||
self._set_monkeypatches(monkeypatch)
|
||||
|
||||
data = dict(self.required_data)
|
||||
data['pe_id'] = MOCK_REQUEST['body']['financial_verification']['pe_id']
|
||||
|
||||
response = yield self.submit_data(http_client, base_url, data)
|
||||
|
||||
assert response.code == 302
|
||||
assert response.headers.get("Location") == "/requests"
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_submit_request_form_with_new_valid_pe_id(self, monkeypatch, http_client, base_url):
|
||||
self._set_monkeypatches(monkeypatch)
|
||||
|
||||
data = dict(self.required_data)
|
||||
data['pe_id'] = MOCK_VALID_PE_ID
|
||||
|
||||
response = yield self.submit_data(http_client, base_url, data)
|
||||
|
||||
assert response.code == 302
|
||||
assert response.headers.get("Location") == "/requests"
|
||||
|
@ -43,26 +43,39 @@ class MockApiClient(ApiClient):
|
||||
return response
|
||||
|
||||
|
||||
MOCK_REQUEST = {
|
||||
"id": "66b8ef71-86d3-48ef-abc2-51bfa1732b6b",
|
||||
"creator": "49903ae7-da4a-49bf-a6dc-9dff5d004238",
|
||||
"body": {
|
||||
"financial_verification": {
|
||||
"pe_id": "0203752A",
|
||||
},
|
||||
},
|
||||
"status": "incomplete"
|
||||
}
|
||||
|
||||
class MockRequestsClient(MockApiClient):
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def get(self, path, **kwargs):
|
||||
json = {
|
||||
"id": "66b8ef71-86d3-48ef-abc2-51bfa1732b6b",
|
||||
"creator": "49903ae7-da4a-49bf-a6dc-9dff5d004238",
|
||||
"body": {},
|
||||
"status": "incomplete",
|
||||
}
|
||||
return self._get_response("GET", path, 200, json=json)
|
||||
return self._get_response("GET", path, 200, json=MOCK_REQUEST)
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def post(self, path, **kwargs):
|
||||
json = {
|
||||
"id": "66b8ef71-86d3-48ef-abc2-51bfa1732b6b",
|
||||
"creator": "49903ae7-da4a-49bf-a6dc-9dff5d004238",
|
||||
"body": {},
|
||||
}
|
||||
return self._get_response("POST", path, 202, json=json)
|
||||
return self._get_response("POST", path, 202, json=MOCK_REQUEST)
|
||||
|
||||
|
||||
MOCK_VALID_PE_ID = "8675309U"
|
||||
|
||||
|
||||
class MockFundzClient(MockApiClient):
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def get(self, path, **kwargs):
|
||||
if path.endswith(MOCK_VALID_PE_ID):
|
||||
return self._get_response("GET", path, 200)
|
||||
else:
|
||||
return self._get_response("GET", path, 404)
|
||||
|
||||
|
||||
class MockAuthzClient(MockApiClient):
|
||||
|
Loading…
x
Reference in New Issue
Block a user