From bcf12535fa6e0b73f0cf6cb805cff8985e733582 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 06:14:43 +0100 Subject: [PATCH] Cover API token refresh lifecycle in E2E --- .../tests/13-api-token-scope.spec.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/integration/tests/13-api-token-scope.spec.ts b/tests/integration/tests/13-api-token-scope.spec.ts index cdc91dc4d..2db55ec3d 100644 --- a/tests/integration/tests/13-api-token-scope.spec.ts +++ b/tests/integration/tests/13-api-token-scope.spec.ts @@ -11,6 +11,7 @@ import { type APITokenRecord = { id: string; + name?: string; scopes?: string[]; ownerUserId?: string; }; @@ -20,6 +21,10 @@ type APITokenCreateResponse = { record?: APITokenRecord; }; +type APITokenListResponse = { + tokens?: APITokenRecord[]; +}; + const bearerHeaders = (token: string, extraHeaders: Record = {}) => ({ Authorization: `Bearer ${token}`, ...extraHeaders, @@ -91,6 +96,58 @@ test.describe.serial('API token scope and assignment gate', () => { expect(staleReadRes.status()).toBe(401); }); + test('keeps rename and revoke reflected across fresh inventory reads', async ({ page }) => { + await ensureSessionAuthenticated(page); + + let tokenID = ''; + const createRes = await apiRequest(page, '/api/security/tokens', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: { + name: `e2e-refresh-original-${Date.now()}`, + scopes: ['settings:read'], + }, + }); + expect(createRes.ok(), await createRes.text()).toBeTruthy(); + + const createPayload = (await createRes.json()) as APITokenCreateResponse; + tokenID = createPayload.record?.id || ''; + expect(tokenID).toBeTruthy(); + + try { + const renamedToken = `e2e-refresh-renamed-${Date.now()}`; + const renameRes = await apiRequest(page, `/api/security/tokens/${encodeURIComponent(tokenID)}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + data: { name: renamedToken }, + }); + expect(renameRes.ok(), await renameRes.text()).toBeTruthy(); + expect(((await renameRes.json()) as APITokenCreateResponse).record?.name).toBe(renamedToken); + + const listAfterRenameRes = await apiRequest(page, '/api/security/tokens'); + expect(listAfterRenameRes.ok(), await listAfterRenameRes.text()).toBeTruthy(); + const listAfterRename = (await listAfterRenameRes.json()) as APITokenListResponse; + expect(listAfterRename.tokens?.find((candidate) => candidate.id === tokenID)?.name).toBe(renamedToken); + + const deleteRes = await apiRequest(page, `/api/security/tokens/${encodeURIComponent(tokenID)}`, { + method: 'DELETE', + }); + expect(deleteRes.status()).toBe(204); + + const listAfterRevokeRes = await apiRequest(page, '/api/security/tokens'); + expect(listAfterRevokeRes.ok(), await listAfterRevokeRes.text()).toBeTruthy(); + const listAfterRevoke = (await listAfterRevokeRes.json()) as APITokenListResponse; + expect(listAfterRevoke.tokens?.some((candidate) => candidate.id === tokenID)).toBe(false); + tokenID = ''; + } finally { + if (tokenID) { + await apiRequest(page, `/api/security/tokens/${encodeURIComponent(tokenID)}`, { + method: 'DELETE', + }); + } + } + }); + test('org-bound token stays inside the issuing org', async ({ page }) => { await ensureSessionAuthenticated(page);