From 72be5a9bc0063358b97a09414dc96743d2d2f598 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Mon, 20 Jul 2026 12:24:50 -0700 Subject: [PATCH] test(e2e): cover tag creation persisting for spend categorization (#34018) --- tests/e2e/management/management_client.py | 34 +++++++++++++++++++++ tests/e2e/management/test_management_e2e.py | 24 ++++++++++++++- tests/e2e/models.py | 23 ++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/tests/e2e/management/management_client.py b/tests/e2e/management/management_client.py index 6ce6405c2be..438a4db098c 100644 --- a/tests/e2e/management/management_client.py +++ b/tests/e2e/management/management_client.py @@ -26,6 +26,10 @@ from models import ( OrgInfoResponse, OrgNewBody, OrgNewResponse, + TagDeleteBody, + TagListEntry, + TagListResponse, + TagNewBody, TeamData, TeamDeleteBody, TeamInfoParams, @@ -259,6 +263,36 @@ class ManagementClient: ) ) + def create_tag(self, body: TagNewBody) -> None: + _ = unwrap( + self.proxy.transport.post( + "/tag/new", + headers=self.proxy.transport.master, + json=body, + response_type=NoBody, + ) + ) + + def delete_tag(self, name: str) -> None: + _ = self.proxy.transport.post( + "/tag/delete", + headers=self.proxy.transport.master, + json=TagDeleteBody(name=name), + response_type=NoBody, + ) + + def tag_list(self) -> tuple[TagListEntry, ...]: + return tuple( + unwrap( + self.proxy.transport.get( + "/tag/list", + headers=self.proxy.transport.master, + params=NoBody(), + response_type=TagListResponse, + ) + ).root + ) + def chat_status(self, key: str, model: str, content: str) -> StreamingResponse: return self.proxy.transport.send( "/chat/completions", diff --git a/tests/e2e/management/test_management_e2e.py b/tests/e2e/management/test_management_e2e.py index de34ee69ded..51817b3916f 100644 --- a/tests/e2e/management/test_management_e2e.py +++ b/tests/e2e/management/test_management_e2e.py @@ -22,7 +22,7 @@ from management_client import ( ROUTE_NOT_ALLOWED_MARKER, ManagementClient, ) -from models import KeyGenerateBody, OrgNewBody, TeamNewBody, UserNewBody +from models import KeyGenerateBody, OrgNewBody, TagListEntry, TagNewBody, TeamNewBody, UserNewBody pytestmark = pytest.mark.e2e @@ -271,6 +271,28 @@ class TestOrganizationRoutes: ) +class TestTagRoutes: + @pytest.mark.covers("mgmt.tag.new.happy_path") + def test_new_persists_to_tag_list(self, client: ManagementClient, resources: ResourceManager) -> None: + name = f"e2e-mgmt-tag-{unique_marker()}" + description = "Tag for spend categorization" + + assert all(entry.name != name for entry in client.tag_list()), ( + f"tag {name!r} was already listed by /tag/list before /tag/new created it" + ) + + client.create_tag(TagNewBody(name=name, description=description)) + resources.defer(lambda: client.delete_tag(name)) + + def listed() -> TagListEntry | None: + return next((entry for entry in client.tag_list() if entry.name == name), None) + + entry = _poll(client, listed, f"/tag/list never listed {name!r} after /tag/new") + assert entry.description == description, ( + f"/tag/list reports description {entry.description!r} for {name!r}, configured {description!r}" + ) + + def _assert_route_forbidden(route: str, outcome: StreamingResponse) -> None: assert outcome.status_code == 403, ( f"llm-only key POSTing {route} must be denied exactly 403, got {outcome.status_code}: {outcome.body[:300]}" diff --git a/tests/e2e/models.py b/tests/e2e/models.py index bdcfd080b8e..b7a95f9714c 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -699,3 +699,26 @@ class OrgInfoResponse(BaseModel): class OrgDeleteBody(BaseModel): organization_ids: list[str] + + +# ---------- tags (management) ---------- + + +class TagNewBody(BaseModel): + name: str + description: str | None = None + + +class TagDeleteBody(BaseModel): + name: str + + +class TagListEntry(BaseModel): + name: str + description: str | None = None + + +class TagListResponse(RootModel[list[TagListEntry]]): + """GET /tag/list answers with a bare array of tag configs (the stored tags plus + any dynamically-seen spend tags), not an object wrapping them. Read the rows off + .root."""