Skip to content

Commit

Permalink
Merge pull request #1737 from GSA-TTS/tadhg/fix-validation-depth
Browse files Browse the repository at this point in the history
Make tests match the actual shape of the data, change validation functions to handle that shape
  • Loading branch information
jadudm authored Aug 7, 2023
2 parents 6624aa0 + 52c41a7 commit 0af885d
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 12 deletions.
6 changes: 2 additions & 4 deletions backend/audit/cross_validation/additional_ueis.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ def additional_ueis(sac_dict):
auditee_uei = all_sections["general_information"].get("auditee_uei")
addl_ueis = []
if all_sections.get("additional_ueis"):
addl_ueis = (
all_sections.get("additional_ueis", {})
.get("AdditionalUEIs", {})
.get("additional_ueis_entries")
addl_ueis = all_sections.get("additional_ueis", {}).get(
"additional_ueis_entries"
)
if addl_ueis_checked:
"""
Expand Down
71 changes: 65 additions & 6 deletions backend/audit/cross_validation/sac_validation_shape.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,78 @@
camel_to_snake = {
"AdditionalUEIs": "additional_ueis",
"AuditInformation": "audit_information",
"CorrectiveActionPlan": "corrective_action_plan",
"FederalAwards": "federal_awards",
"FindingsText": "findings_text",
"FindingsUniformGuidance": "findings_uniform_guidance",
"GeneralInformation": "general_information",
"NotesToSefa": "notes_to_sefa",
}
snake_to_camel = {v: k for k, v in camel_to_snake.items()}
at_root_sections = ("audit_information", "general_information")


def get_shaped_section(sac, section_name):
"""Extract either None or the appropriate dict from the section."""
true_name = camel_to_snake.get(section_name, section_name)
section = getattr(sac, true_name, None)
if true_name in at_root_sections:
return section

if section:
return section.get(snake_to_camel.get(true_name), {})

return None


def sac_validation_shape(sac):
"""
Takes an instance of SingleAuditChecklist and converts it to the shape
expected by the validation functions.
This function exists so that as either the SingleAuditChecklist or the
validation shape changes we only have to make adjustments in one place.
The sections that have spreadsheet workbooks all have root-level properties that
are the camel-case names of those sections. This function eliminates these names
and moves the actual values to the top level as part of returning a structure
that's appropriate for passing to the validation functions.
For example, if the Audit Information and Notes to SEFA sections have content,
this function wil return something like:
{
"sf_sac_sections": {
"audit_information": {
"dollar_threshold": ...,
"is_going_concern_included": ...,
"is_internal_control_deficiency_disclosed": ...,
"is_internal_control_material_weakness_disclosed": ...,
"is_material_noncompliance_disclosed": ...,
"is_aicpa_audit_guide_included": ...,
"is_low_risk_auditee": ...,
[other audit_information fields]
},
"notes_to_sefa": {
"auditee_uei": ...,
"accounting_policies": ...,
"is_minimis_rate_used": ...,
"rate_explained": ...,
"notes_to_sefa_entries": ...,
[other notes_to_sefa fields]
},
"federal_awards": None,
...
},
"sf_sac_meta": { ... },
}
"""

shape = {
"sf_sac_sections": {
"general_information": sac.general_information,
"federal_awards": sac.federal_awards,
"corrective_action_plan": sac.corrective_action_plan,
"findings_text": sac.findings_text,
"findings_uniform_guidance": sac.findings_uniform_guidance,
"additional_ueis": sac.additional_ueis,
k: get_shaped_section(sac, k) for k in camel_to_snake.values()
},
"sf_sac_meta": {
"submitted_by": sac.submitted_by,
Expand Down
140 changes: 140 additions & 0 deletions backend/audit/cross_validation/test_additional_ueis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from django.test import TestCase
from model_bakery import baker
from audit.models import SingleAuditChecklist

from .additional_ueis import additional_ueis
from .errors import (
err_additional_ueis_empty,
err_additional_ueis_has_auditee_uei,
err_additional_ueis_not_empty,
)
from .sac_validation_shape import sac_validation_shape

ERROR_EMPTY = {"error": err_additional_ueis_empty()}
ERROR_PRESENT = {"error": err_additional_ueis_not_empty()}
ERROR_AUEI = {"error": err_additional_ueis_has_auditee_uei()}


class AdditionalUEIsTests(TestCase):
"""
General Information asks if there are additional UEIs; this answer needs to be
consistent with the Additional UEIs section.
"""

def test_general_information_no_addl_ueis(self):
"""
For a SAC with General Information and a no answer, there should be no
additonal UEIs in that section. "No" answer plus no section = valid.
"""
sac = baker.make(SingleAuditChecklist)
sac.general_information = {"multiple_ueis_covered": False}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [])

def test_general_information_no_addl_ueis_ueis_present(self):
"""
For a SAC with General Information and a no answer, there should be no
additonal UEIs in that section.
"No" answer plus data in section: invalid
"""
sac = baker.make(SingleAuditChecklist)
sac.general_information = {"multiple_ueis_covered": False}
sac.additional_ueis = {
"AdditionalUEIs": {
"auditee_uei": "123456789",
"additional_ueis_entries": [{"additional_uei": "987654321"}],
}
}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [ERROR_PRESENT])

def test_general_information_addl_ueis_no_addl_ueis_section(self):
"""
For a SAC with General Information and a yes answer, and no Additional UEIs
data, generate errors.
"""
sac = baker.make(SingleAuditChecklist)
sac.general_information = {"multiple_ueis_covered": True}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [ERROR_EMPTY])

def test_general_information_addl_ueis_no_addl_ueis_in_section(self):
"""
For a SAC with General Information and a yes answer, and no Additional UEIs
listed in the data, generate errors.
"""
sac = baker.make(SingleAuditChecklist)
sac.general_information = {"multiple_ueis_covered": True}
sac.additional_ueis = {}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [ERROR_EMPTY])

sac.additional_ueis = {
"AdditionalUEIs": {
"auditee_uei": "123456789",
"additional_ueis_entries": [],
}
}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [ERROR_EMPTY])

def test_general_information_addl_ueis_addl_ueis_in_section(self):
"""
For a SAC with General Information and a yes answer, and Additional UEIs
listed in the data, do not generate errors.
"""
sac = baker.make(SingleAuditChecklist)

sac.general_information = {"multiple_ueis_covered": True}
sac.additional_ueis = {
"AdditionalUEIs": {
"auditee_uei": "123456789",
"additional_ueis_entries": [{"additional_uei": "987654321"}],
}
}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [])

def test_auditee_ueis_in_addl_ueis(self):
"""
For a SAC with General Information and a yes answer, if the auditee_uei is
found in the additonal_ueis, generate an error.
"""
sac = baker.make(SingleAuditChecklist)

sac.general_information = {
"auditee_uei": "123456789",
"multiple_ueis_covered": True,
}
sac.additional_ueis = {
"AdditionalUEIs": {
"auditee_uei": "123456789",
"additional_ueis_entries": [
{"additional_uei": "987654321"},
{"additional_uei": "123456789"},
],
}
}

shaped_sac = sac_validation_shape(sac)
validation_result = additional_ueis(shaped_sac)

self.assertEqual(validation_result, [ERROR_AUEI])
4 changes: 2 additions & 2 deletions backend/audit/cross_validation/test_auditee_ueis_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_multiple_matching_auditee_ueis(self):
sac = baker.make(SingleAuditChecklist)

sac.general_information = {"auditee_uei": "123456789"}
sac.federal_awards = {"auditee_uei": "123456789"}
sac.federal_awards = {"FederalAwards": {"auditee_uei": "123456789"}}

shaped_sac = sac_validation_shape(sac)

Expand All @@ -49,7 +49,7 @@ def test_multiple_mismatched_auditee_ueis(self):
sac = baker.make(SingleAuditChecklist)

sac.general_information = {"auditee_uei": "123456789"}
sac.federal_awards = {"auditee_uei": "123456780"}
sac.federal_awards = {"FederalAwards": {"auditee_uei": "123456780"}}

shaped_sac = sac_validation_shape(sac)

Expand Down

0 comments on commit 0af885d

Please sign in to comment.