Skip to content
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
6 changes: 4 additions & 2 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ def duration_non_negative(cls, v):
@field_validator("url")
@classmethod
def url_valid(cls, v):
if v is not None and not _URL_RE.match(v):
# Empty string means "no recording"; producers send it, treat it as absent.
if v and not _URL_RE.match(v):
raise ValueError(f"url does not look like a valid URL: {v!r}")
return v

Expand Down Expand Up @@ -355,7 +356,8 @@ class PartyEntry(BaseModel):
@field_validator("tel")
@classmethod
def tel_valid(cls, v):
if v is not None and not _TEL_RE.match(v):
# Empty string means "no number"; treat it as absent. "Anonymous" etc. still fail.
if v and not _TEL_RE.match(v):
raise ValueError(f"tel has invalid format: {v!r}")
return v

Expand Down
25 changes: 25 additions & 0 deletions common/tests/test_ingest_empty_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Empty-string url/tel are accepted as absent at ingest; malformed values still fail.

Producers in the field send dialog.url "" for calls with no recording and party
tel "" for unknown numbers. Rejecting those bounced about 4.6% of one
production day's vCons with 422.
"""

import pytest
from pydantic import ValidationError

from api import DialogEntry, PartyEntry


def test_empty_url_and_tel_accepted_unchanged():
assert DialogEntry(url="").url == ""
assert PartyEntry(tel="").tel == ""


@pytest.mark.parametrize("model,field,value", [
(DialogEntry, "url", "not-a-url"),
(PartyEntry, "tel", "Anonymous"),
])
def test_malformed_values_still_rejected(model, field, value):
with pytest.raises(ValidationError):
model(**{field: value})
Loading