Skip to content

Commit ab21127

Browse files
committed
fix(responses): NCMEC file IDs are hex strings, not integers
The /finish reportDoneResponse returns 32-char hex fileId values (e.g. 3a1d4fd4106b82499b7c93442aa7dca4). They were typed as list[int], so parsing a real finished-report response raised 7 validation errors and the caller saw a 502 — even though NCMEC had accepted and filed the report. - _Files.file_id: list[int] -> list[str]; file_ids property returns list[str]. - Regression test uses the real production payload (report 250799407, 7 hex ids). - Fix the two prior tests that baked in the wrong int assumption (why it shipped). Bump 0.1.0 -> 0.1.1.
1 parent 1c63982 commit ab21127

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ncmec-cybertip"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "A fully typed async client for the NCMEC CyberTipline Reporting API"
55
readme = "README.md"
66
license = "MIT"

src/ncmec_cybertip/responses.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ReportResponse(Base, tag="reportResponse"):
2424
class _Files(Base, tag="files"):
2525
"""Wrapper for the list of file IDs in a done response."""
2626

27-
file_id: list[int] = element(tag="fileId", default_factory=list)
27+
file_id: list[str] = element(tag="fileId", default_factory=list)
2828

2929

3030
class ReportDoneResponse(Base, tag="reportDoneResponse"):
@@ -35,6 +35,11 @@ class ReportDoneResponse(Base, tag="reportDoneResponse"):
3535
files: _Files = element(default_factory=_Files)
3636

3737
@property
38-
def file_ids(self) -> list[int]:
39-
"""The file IDs of files successfully uploaded to the finished report."""
38+
def file_ids(self) -> list[str]:
39+
"""The file IDs of files successfully uploaded to the finished report.
40+
41+
NCMEC assigns each uploaded file a 32-char hex identifier (e.g.
42+
``3a1d4fd4106b82499b7c93442aa7dca4``) — these are opaque strings, not
43+
integers.
44+
"""
4045
return self.files.file_id

tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
_OK_FINISH = (
3434
b"<reportDoneResponse><responseCode>0</responseCode>"
3535
b"<reportId>4564654</reportId>"
36-
b"<files><fileId>1</fileId></files></reportDoneResponse>"
36+
b"<files><fileId>b0754af766b426f2928a02c651ed4b99</fileId></files></reportDoneResponse>"
3737
)
3838

3939

@@ -130,7 +130,7 @@ async def test_finish() -> None:
130130
)
131131
async with _client() as client:
132132
done = await client.finish(4564654)
133-
assert done.file_ids == [1]
133+
assert done.file_ids == ["b0754af766b426f2928a02c651ed4b99"]
134134
assert b"id=4564654" in route.calls.last.request.content
135135

136136

tests/test_models.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,35 @@ def test_report_response_parses() -> None:
252252

253253

254254
def test_report_done_response_exposes_file_ids() -> None:
255+
# NCMEC returns 32-char hex file IDs (opaque strings), not integers.
256+
# Regression: report 250799407's real /finish response 502'd because these
257+
# were previously typed as list[int]. This is the actual production payload.
255258
xml = (
259+
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
256260
b"<reportDoneResponse><responseCode>0</responseCode>"
257-
b"<reportId>4564654</reportId>"
258-
b"<files><fileId>1</fileId><fileId>2</fileId></files>"
261+
b"<reportId>250799407</reportId>"
262+
b"<files>"
263+
b"<fileId>3a1d4fd4106b82499b7c93442aa7dca4</fileId>"
264+
b"<fileId>23eb425911143eadc4987a2b3e221c09</fileId>"
265+
b"<fileId>abd6aa21e080470e22c4c8c9cb8cf2af</fileId>"
266+
b"<fileId>01d21d1cc0a6c34885973d6cda08f537</fileId>"
267+
b"<fileId>d6aed6fe40d471c011640a6781b4a648</fileId>"
268+
b"<fileId>3c62d4a8c475524b7ecd3f4764eeed94</fileId>"
269+
b"<fileId>084fe41db96ac651eb0b8b2e0b400de0</fileId>"
270+
b"</files>"
259271
b"</reportDoneResponse>"
260272
)
261273
done = ReportDoneResponse.from_xml(xml)
262-
assert done.report_id == 4564654
263-
assert done.file_ids == [1, 2]
274+
assert done.report_id == 250799407
275+
assert done.file_ids == [
276+
"3a1d4fd4106b82499b7c93442aa7dca4",
277+
"23eb425911143eadc4987a2b3e221c09",
278+
"abd6aa21e080470e22c4c8c9cb8cf2af",
279+
"01d21d1cc0a6c34885973d6cda08f537",
280+
"d6aed6fe40d471c011640a6781b4a648",
281+
"3c62d4a8c475524b7ecd3f4764eeed94",
282+
"084fe41db96ac651eb0b8b2e0b400de0",
283+
]
264284

265285

266286
def test_report_done_response_empty_files() -> None:

0 commit comments

Comments
 (0)