Skip to content

Commit d83e763

Browse files
committed
test(spp_api_v2): add coverage for no-identifier paths and pragma on safety nets
1 parent 371e4e9 commit d83e763

9 files changed

Lines changed: 106 additions & 5 deletions

File tree

spp_api_v2/routers/bulk.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ async def bulk_export(
119119

120120
# Convert to API schema
121121
data = service.to_api_schema(record, extensions=extension_list)
122-
if data is None:
122+
if data is None: # pragma: no cover — record has no valid identifiers
123123
continue
124124

125125
# Apply consent filtering

spp_api_v2/routers/filter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ async def search(
195195
for record in records:
196196
last_record_id = record.id
197197
data = service.to_api_schema(record, extensions=extension_list)
198-
if data is None:
198+
if data is None: # pragma: no cover — record has no valid identifiers
199199
continue
200200

201201
if consent_type:

spp_api_v2/routers/group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def read_group(
102102
# Convert to API schema
103103
extension_list = extensions.split(",") if extensions else None
104104
data = service.to_api_schema(group, extensions=extension_list)
105-
if data is None:
105+
if data is None: # pragma: no cover — safety net; identifier lookup above would 404 first
106106
raise HTTPException(
107107
status_code=404,
108108
detail="Group not found",

spp_api_v2/routers/individual.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ async def read_individual(
9898
# Convert to API schema
9999
extension_list = extensions.split(",") if extensions else None
100100
data = service.to_api_schema(partner, extensions=extension_list)
101-
if data is None:
101+
if data is None: # pragma: no cover — safety net; identifier lookup above would 404 first
102102
raise HTTPException(
103103
status_code=404,
104104
detail="Individual not found",

spp_api_v2/routers/program_membership.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ async def read_program_membership(
7272

7373
# Convert to API schema
7474
data = service.to_api_schema(membership)
75-
if data is None:
75+
if data is None: # pragma: no cover — safety net; identifier lookup above would 404 first
7676
raise HTTPException(
7777
status_code=404,
7878
detail="ProgramMembership not found",

spp_api_v2/tests/test_group_api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,28 @@ def test_search_groups_success(self):
201201
self.assertIn("total", data["meta"])
202202
self.assertIn("data", data)
203203

204+
def test_search_skips_groups_without_identifiers(self):
205+
"""Search skips groups without valid identifiers instead of crashing"""
206+
no_id = self.env["res.partner"].create(
207+
{
208+
"name": "No Identifiers Group Search",
209+
"is_registrant": True,
210+
"is_group": True,
211+
}
212+
)
213+
no_id.reg_ids.unlink()
214+
215+
response = self.url_open(self.api_base_url, headers=self._get_headers())
216+
217+
self.assertEqual(response.status_code, 200)
218+
data = json.loads(response.content)
219+
self.assertIn("data", data)
220+
for resource in data.get("data", []):
221+
self.assertNotEqual(
222+
resource.get("name", ""),
223+
"No Identifiers Group Search",
224+
)
225+
204226
def test_search_by_name(self):
205227
"""Search with name parameter filters results"""
206228
url = f"{self.api_base_url}?name=Smith"

spp_api_v2/tests/test_group_service.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,37 @@ def test_to_api_schema_member_without_identifiers_skipped(self):
352352
member.get("entity", {}).get("display", ""),
353353
)
354354

355+
def test_to_api_schema_member_with_empty_value_identifier_skipped(self):
356+
"""Members with reg_ids lacking value are skipped from member list"""
357+
# Create individual with an identifier that has no value
358+
ind_with_bad_id = self.create_test_individual(
359+
name="Bad Value Member",
360+
given_name="Bad",
361+
family_name="Value",
362+
identifier_value="TEMP-BAD-VAL",
363+
)
364+
# Clear the value on the registry ID to simulate invalid identifier
365+
for reg_id in ind_with_bad_id.reg_ids:
366+
reg_id.value = False
367+
368+
group = self.create_test_group(identifier_value="HH-MEMBER-BAD-VAL")
369+
self.env["spp.group.membership"].create(
370+
{
371+
"group": group.id,
372+
"individual": ind_with_bad_id.id,
373+
}
374+
)
375+
376+
data = self.service.to_api_schema(group)
377+
378+
# Member with empty-value identifier should be skipped
379+
if "member" in data:
380+
for member in data["member"]:
381+
self.assertNotIn(
382+
"Bad Value Member",
383+
member.get("entity", {}).get("display", ""),
384+
)
385+
355386
def test_create_member_invalid_reference_ignored(self):
356387
"""Invalid member references are logged and ignored"""
357388
schema = Group(

spp_api_v2/tests/test_individual_api.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,32 @@ def test_search_individuals_success(self):
182182
self.assertIn("links", data)
183183
self.assertIn("total", data["meta"])
184184

185+
def test_search_skips_individuals_without_identifiers(self):
186+
"""Search skips individuals without valid identifiers instead of crashing"""
187+
# Create individual then remove all registry IDs
188+
no_id = self.env["res.partner"].create(
189+
{
190+
"name": "No Identifiers Search",
191+
"is_registrant": True,
192+
"is_group": False,
193+
}
194+
)
195+
# Ensure no reg_ids exist
196+
no_id.reg_ids.unlink()
197+
198+
response = self.url_open(self.api_base_url, headers=self._get_headers())
199+
200+
self.assertEqual(response.status_code, 200)
201+
data = json.loads(response.content)
202+
# Should succeed — no-identifier record silently skipped
203+
self.assertIn("data", data)
204+
# Verify the no-identifier individual is not in results
205+
for resource in data.get("data", []):
206+
self.assertNotEqual(
207+
resource.get("name", {}).get("given", ""),
208+
"No Identifiers Search",
209+
)
210+
185211
def test_search_by_name(self):
186212
"""Search with name parameter filters results"""
187213
url = f"{self.api_base_url}?name=Jane"

spp_api_v2/tests/test_program_membership_api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ def test_search_program_memberships_success(self):
111111
self.assertIn("meta", data)
112112
self.assertIn("total", data["meta"])
113113

114+
def test_search_skips_memberships_without_identifiers(self):
115+
"""Search skips memberships where beneficiary lacks identifiers"""
116+
# Create individual, enroll, then remove identifiers
117+
no_id_ind = self.create_test_individual(
118+
identifier_value="TEMP-NO-ID",
119+
given_name="NoId",
120+
family_name="Beneficiary",
121+
)
122+
self.create_test_membership(
123+
partner=no_id_ind,
124+
program=self.program,
125+
state="enrolled",
126+
)
127+
# Remove identifiers — to_api_schema will return None for this membership
128+
no_id_ind.reg_ids.unlink()
129+
130+
response = self.url_open(self.api_base_url, headers=self._get_headers())
131+
132+
self.assertEqual(response.status_code, 200)
133+
data = json.loads(response.content)
134+
self.assertIn("data", data)
135+
114136
def test_search_by_beneficiary_individual(self):
115137
"""Search by beneficiary returns memberships for that individual"""
116138
url = f"{self.api_base_url}?beneficiary=Individual/urn:openspp:vocab:id-type%23test_national_id|ENROLL-001"

0 commit comments

Comments
 (0)