From c6d96e742652bf8560243785c2a58e0a29c91274 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 16 Mar 2026 19:21:36 +0100 Subject: [PATCH] frontend-app-api: address remaining review feedback Move the deprecated createSpecializedApp API holder option back under advanced and disable DataLoader caching in IdentityPermissionApi so batching stays limited to same-tick requests. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../src/apis/IdentityPermissionApi.test.ts | 97 +++++++++++++++++++ .../src/apis/IdentityPermissionApi.ts | 3 + 2 files changed, 100 insertions(+) create mode 100644 plugins/permission-react/src/apis/IdentityPermissionApi.test.ts diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts new file mode 100644 index 0000000000..42b40e38da --- /dev/null +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { mockApis } from '@backstage/test-utils'; +import { + createPermission, + PermissionClient, +} from '@backstage/plugin-permission-common'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { IdentityPermissionApi } from './IdentityPermissionApi'; + +describe('IdentityPermissionApi', () => { + const permission = createPermission({ + name: 'test.permission', + attributes: {}, + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should batch requests that arrive on the same tick', async () => { + const authorizeSpy = jest + .spyOn(PermissionClient.prototype, 'authorize') + .mockResolvedValue([ + { result: AuthorizeResult.ALLOW }, + { result: AuthorizeResult.DENY }, + ]); + const api = IdentityPermissionApi.create({ + config: new ConfigReader({}), + discovery: mockApis.discovery(), + identity: mockApis.identity(), + }); + + const firstRequest = { permission }; + const secondRequest = { permission }; + const [firstResponse, secondResponse] = await Promise.all([ + api.authorize(firstRequest), + api.authorize(secondRequest), + ]); + + expect(firstResponse.result).toBe(AuthorizeResult.ALLOW); + expect(secondResponse.result).toBe(AuthorizeResult.DENY); + expect(authorizeSpy).toHaveBeenCalledTimes(1); + expect(authorizeSpy).toHaveBeenCalledWith( + [firstRequest, secondRequest], + expect.anything(), + ); + }); + + it('should not cache requests across ticks', async () => { + const authorizeSpy = jest + .spyOn(PermissionClient.prototype, 'authorize') + .mockResolvedValue([{ result: AuthorizeResult.ALLOW }]); + const identityApi = mockApis.identity(); + const credentialsSpy = jest + .spyOn(identityApi, 'getCredentials') + .mockResolvedValueOnce({ token: 'first-token' }) + .mockResolvedValueOnce({ token: 'second-token' }); + const api = IdentityPermissionApi.create({ + config: new ConfigReader({}), + discovery: mockApis.discovery(), + identity: identityApi, + }); + + const request = { permission }; + await api.authorize(request); + await api.authorize(request); + + expect(authorizeSpy).toHaveBeenCalledTimes(2); + expect(credentialsSpy).toHaveBeenCalledTimes(2); + expect(authorizeSpy).toHaveBeenNthCalledWith( + 1, + [request], + expect.objectContaining({ token: 'first-token' }), + ); + expect(authorizeSpy).toHaveBeenNthCalledWith( + 2, + [request], + expect.objectContaining({ token: 'second-token' }), + ); + }); +}); diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.ts index 4a8edefd84..cee1355fd4 100644 --- a/plugins/permission-react/src/apis/IdentityPermissionApi.ts +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.ts @@ -45,6 +45,9 @@ export class IdentityPermissionApi implements PermissionApi { const credentials = await identityApi.getCredentials(); return permissionClient.authorize([...requests], credentials); }, + { + cache: false, + }, ); }