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

2023-10-11 | MAIN --> PROD | DEV (94d1f48) --> STAGING #2447

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
106 changes: 79 additions & 27 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,40 +72,92 @@ def validate_user_provided_organization_type(self, value):


class UEISerializer(serializers.Serializer):
"""
Does a UEI request against the SAM.gov API and returns a flattened shape
containing only the fields we're interested in.

The below operations are nested and mixed among functions, rather than done
serially, but the approximate order of operations is:

+ Assemble the parameters to pass to the API.
Mostly in api.uei.get_uei_info_from_sam_gov.
+ Make the API request.
api.uei.call_sam_api
+ Check for high-level errors.
api.uei.get_uei_info_from_sam_gov
+ Extract the JSON for the individual record out of the response and check
for some other errors.
api.uei.parse_sam_uei_json
+ For a specific class of error, retry the API call with different
parameters.
api.uei.get_uei_info_from_sam_gov
api.uei.call_sam_api
api.uei.parse_sam_uei_json
+ If we don't have errors by that point, flatten the data.
api.serializsers.UEISerializer.validate_auditee_uei
+ If we don't encounter errors at that point, return the flattened data.
api.serializsers.UEISerializer.validate_auditee_uei

"""

auditee_uei = serializers.CharField()

def validate_auditee_uei(self, value):
"""
Flattens the UEI response info and returns this shape:

{
"auditee_uei": …,
"auditee_name": …,
"auditee_fiscal_year_end_date": …,
"auditee_address_line_1": …,
"auditee_city": …,
"auditee_state": …,
"auditee_zip": …,
}

Will provide default error-message-like values (such as “No address in SAM.gov)
if the keys are missing, but if the SAM.gov fields are present but empty, we
return the empty strings.

"""
sam_response = get_uei_info_from_sam_gov(value)
if sam_response.get("errors"):
raise serializers.ValidationError(sam_response.get("errors"))
return json.dumps(
{
"auditee_uei": value,
"auditee_name": sam_response.get("response")
.get("entityRegistration")
.get("legalBusinessName"),
"auditee_fiscal_year_end_date": sam_response.get("response")
.get("coreData")
.get("entityInformation")
.get("fiscalYearEndCloseDate"),
"auditee_address_line_1": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("addressLine1"),
"auditee_city": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("city"),
"auditee_state": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("stateOrProvinceCode"),
"auditee_zip": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("zipCode"),

entity_registration = sam_response.get("response")["entityRegistration"]
core = sam_response.get("response")["coreData"]

basic_data = {
"auditee_uei": value,
"auditee_name": entity_registration.get("legalBusinessName"),
}
addr_key = "mailingAddress" if "mailingAddress" in core else "physicalAddress"

mailing_data = {
"auditee_address_line_1": "No address in SAM.gov.",
"auditee_city": "No address in SAM.gov.",
"auditee_state": "No address in SAM.gov.",
"auditee_zip": "No address in SAM.gov.",
}

if addr_key in core:
mailing_data = {
"auditee_address_line_1": core.get(addr_key).get("addressLine1"),
"auditee_city": core.get(addr_key).get("city"),
"auditee_state": core.get(addr_key).get("stateOrProvinceCode"),
"auditee_zip": core.get(addr_key).get("zipCode"),
}
)

# 2023-10-10: Entities with a samRegistered value of No may be missing
# some fields from coreData entirely.
entity_information = core.get("entityInformation", {})
extra_data = {
"auditee_fiscal_year_end_date": entity_information.get(
"fiscalYearEndCloseDate", "No fiscal year end date in SAM.gov."
),
}
return json.dumps(basic_data | mailing_data | extra_data)


class AuditeeInfoSerializer(serializers.Serializer):
Expand Down
54 changes: 54 additions & 0 deletions backend/api/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,60 @@ def test_invalid_uei_payload(self):
# Invalid
self.assertFalse(UEISerializer(data=invalid).is_valid())

def test_quirky_uei_payload(self):
"""
It turns out that some entries can be missing fields that we thought would
always be present.

"""
quirky = {
"totalRecords": 1,
"entityData": [
{
"entityRegistration": {
"samRegistered": "No",
"ueiSAM": "ZQGGHJH74DW7",
"cageCode": None,
"legalBusinessName": "Some organization",
"registrationStatus": "ID Assigned",
"evsSource": "X&Y",
"ueiStatus": "Active",
"ueiExpirationDate": None,
"ueiCreationDate": "2023-04-01",
"publicDisplayFlag": "Y",
"dnbOpenData": None,
},
"coreData": {
"physicalAddress": {
"addressLine1": "SOME RD",
"addressLine2": None,
"city": "SOME CITY",
"stateOrProvinceCode": "AL",
"zipCode": "36659",
"zipCodePlus4": "3903",
"countryCode": "USA",
}
},
}
],
"links": {
"selfLink": "https://api.sam.gov/entity-information/v3/entities?api_key=REPLACE_WITH_API_KEY&ueiSAM=ZQGGHJH74DW7&samRegistered=No&page=0&size=10"
},
}
empty = {"totalRecords": 0, "entityData": []}
data = {"auditee_uei": "ZQGGHJH74DW7"}
with patch("api.uei.SESSION.get") as mock_get:
mock_get.return_value.status_code = 200
# First time, it should return zero results and retry:
mock_get.return_value.json.return_value = empty
self.assertFalse(UEISerializer(data=data).is_valid())
self.assertEqual(mock_get.call_count, 2)
# Second time, it should return the samRegistered No result and only
# call the get method once:
mock_get.return_value.json.return_value = quirky
self.assertTrue(UEISerializer(data=data).is_valid())
self.assertEqual(mock_get.call_count, 3)


class AuditeeInfoStepTests(SimpleTestCase):
def test_valid_auditee_info_with_uei(self):
Expand Down
Loading