diff --git a/tests/e2e/e2e_http.py b/tests/e2e/e2e_http.py index 692f951d08e..ce801ef81fb 100644 --- a/tests/e2e/e2e_http.py +++ b/tests/e2e/e2e_http.py @@ -264,6 +264,26 @@ def delete[R: BaseModel]( return _classify(resp, response_type) +def patch[R: BaseModel]( + url: URL, + *, + headers: BaseModel, + json: BaseModel, + response_type: type[R], + timeout: float = 30.0, +) -> Result[R]: + try: + resp = requests.patch( + str(url), + headers=_headers(headers), + json=json.model_dump(by_alias=True, exclude_none=True), + timeout=timeout, + ) + except requests.RequestException as exc: + return NetworkError(message=str(exc)) + return _classify(resp, response_type) + + def probe( url: URL, *, headers: BaseModel, params: BaseModel, timeout: float = 30.0 ) -> ProbeResult: diff --git a/tests/e2e/management/management_client.py b/tests/e2e/management/management_client.py index 0d68098d2d2..b2c9eb6a29b 100644 --- a/tests/e2e/management/management_client.py +++ b/tests/e2e/management/management_client.py @@ -28,6 +28,7 @@ from models import ( OrgInfoResponse, OrgNewBody, OrgNewResponse, + OrgUpdateBody, TagDeleteBody, TagListEntry, TagListResponse, @@ -327,6 +328,16 @@ class ManagementClient: ) ).organization_id + def update_org(self, body: OrgUpdateBody) -> None: + _ = unwrap( + self.proxy.transport.patch( + "/organization/update", + headers=self.proxy.transport.master, + json=body, + response_type=NoBody, + ) + ) + def delete_org(self, organization_id: str) -> None: _ = self.proxy.transport.delete( "/organization/delete", diff --git a/tests/e2e/management/test_management_e2e.py b/tests/e2e/management/test_management_e2e.py index 4e9270bb9e5..b0a12220341 100644 --- a/tests/e2e/management/test_management_e2e.py +++ b/tests/e2e/management/test_management_e2e.py @@ -23,7 +23,7 @@ from management_client import ( ROUTE_NOT_ALLOWED_MARKER, ManagementClient, ) -from models import KeyGenerateBody, OrgNewBody, TagListEntry, TagNewBody, TeamNewBody, TeamUpdateBody, UserNewBody, UserUpdateBody, LiteLLMParamsBody, ModelInfoEntry +from models import KeyGenerateBody, OrgInfoResponse, OrgNewBody, OrgUpdateBody, TagListEntry, TagNewBody, TeamNewBody, TeamUpdateBody, UserNewBody, UserUpdateBody, LiteLLMParamsBody, ModelInfoEntry pytestmark = pytest.mark.e2e @@ -342,6 +342,24 @@ class TestOrganizationRoutes: f"/organization/info reports models {info.models}, configured ['gemini-2.5-flash']" ) + @pytest.mark.covers("mgmt.organization.update.persists") + def test_update_alias_persists_to_organization_info( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + org_id = client.create_org(OrgNewBody(organization_alias=f"e2e-mgmt-org-{unique_marker()}")) + resources.defer(lambda: client.delete_org(org_id)) + + new_alias = f"e2e-mgmt-org-{unique_marker()}" + client.update_org(OrgUpdateBody(organization_id=org_id, organization_alias=new_alias)) + + def attempt() -> OrgInfoResponse | None: + info = client.org_info(org_id) + return info if info.organization_alias == new_alias else None + + _ = _poll( + client, attempt, f"/organization/info never reflected updated alias {new_alias!r} before the deadline" + ) + @pytest.mark.covers("mgmt.organization.delete.persists") def test_delete_removes_from_organization_info( self, client: ManagementClient, resources: ResourceManager diff --git a/tests/e2e/models.py b/tests/e2e/models.py index db140a918fa..9e773120846 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -727,6 +727,11 @@ class OrgNewResponse(BaseModel): organization_id: str +class OrgUpdateBody(BaseModel): + organization_id: str + organization_alias: str + + class OrgInfoParams(BaseModel): organization_id: str diff --git a/tests/e2e/transport.py b/tests/e2e/transport.py index 10e090f07a9..64fe6406ff7 100644 --- a/tests/e2e/transport.py +++ b/tests/e2e/transport.py @@ -55,6 +55,10 @@ class Transport(Protocol): self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] ) -> Result[R]: ... + def patch[R: BaseModel]( + self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] + ) -> Result[R]: ... + def probe(self, path: str, *, params: BaseModel) -> ProbeResult: ... def upload[R: BaseModel]( @@ -131,6 +135,17 @@ class HttpTransport: timeout=self.request_timeout, ) + def patch[R: BaseModel]( + self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] + ) -> Result[R]: + return e2e_http.patch( + self._url(path), + headers=headers, + json=json, + response_type=response_type, + timeout=self.request_timeout, + ) + def stream( self, path: str, *, headers: BaseModel, json: BaseModel ) -> StreamingResponse: @@ -271,6 +286,13 @@ class SplitTransport: path, headers=headers, json=json, response_type=response_type ) + def patch[R: BaseModel]( + self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] + ) -> Result[R]: + return self._route(path).patch( + path, headers=headers, json=json, response_type=response_type + ) + def stream( self, path: str, *, headers: BaseModel, json: BaseModel ) -> StreamingResponse: