Skip to content

Commit d48212e

Browse files
committed
fix: validate engine_id and normalize api_url for vNext support
1 parent 379a32c commit d48212e

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

src/lingodotdev/engine.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ class EngineConfig(BaseModel):
2323
batch_size: int = Field(default=25, ge=1, le=250)
2424
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)
2525

26+
@validator("engine_id", pre=True, always=True)
27+
@classmethod
28+
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
29+
if v is None:
30+
return None
31+
v = v.strip()
32+
return v if v else None
33+
2634
@validator("api_url", pre=True, always=True)
2735
@classmethod
2836
def validate_api_url(cls, v: Optional[str], values: Dict[str, Any]) -> str:
@@ -34,7 +42,7 @@ def validate_api_url(cls, v: Optional[str], values: Dict[str, Any]) -> str:
3442
return "https://engine.lingo.dev"
3543
if not v.startswith(("http://", "https://")):
3644
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
37-
return v
45+
return v.rstrip("/")
3846

3947

4048
class LocalizationParams(BaseModel):

tests/test_engine.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -652,18 +652,51 @@ def setup_method(self):
652652
self.config = {"api_key": "test_api_key", "engine_id": "my-engine-id"}
653653
self.engine = LingoDotDevEngine(self.config)
654654

655+
def test_engine_id_empty_string_treated_as_none(self):
656+
"""Test that empty engine_id is treated as None"""
657+
engine = LingoDotDevEngine({"api_key": "key", "engine_id": ""})
658+
assert engine.config.engine_id is None
659+
assert engine._is_vnext is False
660+
assert engine.config.api_url == "https://engine.lingo.dev"
661+
662+
def test_engine_id_whitespace_treated_as_none(self):
663+
"""Test that whitespace-only engine_id is treated as None"""
664+
engine = LingoDotDevEngine({"api_key": "key", "engine_id": " "})
665+
assert engine.config.engine_id is None
666+
assert engine._is_vnext is False
667+
assert engine.config.api_url == "https://engine.lingo.dev"
668+
669+
def test_engine_id_stripped(self):
670+
"""Test that engine_id is stripped of whitespace"""
671+
engine = LingoDotDevEngine({"api_key": "key", "engine_id": " eng_123 "})
672+
assert engine.config.engine_id == "eng_123"
673+
assert engine._is_vnext is True
674+
675+
def test_api_url_trailing_slash_stripped(self):
676+
"""Test that trailing slash is stripped from api_url"""
677+
engine = LingoDotDevEngine(
678+
{
679+
"api_key": "key",
680+
"engine_id": "eng",
681+
"api_url": "https://custom.api.com/",
682+
}
683+
)
684+
assert engine.config.api_url == "https://custom.api.com"
685+
655686
def test_engine_id_default_api_url(self):
656687
"""Test that engine_id switches default api_url to api.lingo.dev"""
657688
assert self.engine.config.api_url == "https://api.lingo.dev"
658689
assert self.engine.config.engine_id == "my-engine-id"
659690

660691
def test_engine_id_with_explicit_api_url(self):
661692
"""Test that explicit api_url is preserved with engine_id"""
662-
engine = LingoDotDevEngine({
663-
"api_key": "key",
664-
"engine_id": "eng",
665-
"api_url": "https://custom.api.com",
666-
})
693+
engine = LingoDotDevEngine(
694+
{
695+
"api_key": "key",
696+
"engine_id": "eng",
697+
"api_url": "https://custom.api.com",
698+
}
699+
)
667700
assert engine.config.api_url == "https://custom.api.com"
668701

669702
def test_is_vnext_true(self):
@@ -672,7 +705,9 @@ def test_is_vnext_true(self):
672705

673706
def test_is_vnext_false_without_engine_id(self):
674707
"""Test _is_vnext is False without engine_id"""
675-
engine = LingoDotDevEngine({"api_key": "key", "api_url": "https://api.test.com"})
708+
engine = LingoDotDevEngine(
709+
{"api_key": "key", "api_url": "https://api.test.com"}
710+
)
676711
assert engine._is_vnext is False
677712

678713
def test_session_id_generated(self):
@@ -690,7 +725,9 @@ async def test_vnext_ensure_client_uses_x_api_key(self):
690725

691726
async def test_classic_ensure_client_uses_bearer(self):
692727
"""Test that classic engine uses Bearer auth header"""
693-
engine = LingoDotDevEngine({"api_key": "test_key", "api_url": "https://api.test.com"})
728+
engine = LingoDotDevEngine(
729+
{"api_key": "test_key", "api_url": "https://api.test.com"}
730+
)
694731
await engine._ensure_client()
695732
assert engine._client is not None
696733
assert engine._client.headers.get("authorization") == "Bearer test_key"
@@ -706,7 +743,11 @@ async def test_vnext_localize_chunk_url_and_body(self, mock_post):
706743
mock_post.return_value = mock_response
707744

708745
await self.engine._localize_chunk(
709-
"en", "es", {"data": {"key": "value"}, "reference": {"es": {"key": "ref"}}}, "wf", True
746+
"en",
747+
"es",
748+
{"data": {"key": "value"}, "reference": {"es": {"key": "ref"}}},
749+
"wf",
750+
True,
710751
)
711752

712753
call_args = mock_post.call_args

0 commit comments

Comments
 (0)